Overview
This integration guide shows you how to how to update your existing project to:- install the ButterCMS package
- instantiate ButterCMS
- create components to fetch and display each of the three ButterCMS content types: Pages, Collections, and Blog Posts.
In order for the snippets to work, you’ll need to setup your dashboard content schemas inside of ButterCMS first.
Starter project
Or, you can jump directly to the starter project below, which will allow you to clone, install, run, and deploy a fully working starter project that’s integrated with content already inside of your ButterCMS account.Angular Starter Project
Hit the ground running with a pre-configured Angular + ButterCMS setup.
Installation
- npm
- yarn
- pnpm
npm install buttercms
yarn add buttercms
pnpm add buttercms
environment.ts:
// src/environments/environment.ts
export const environment = {
production: false,
butterCmsApiKey: 'your_api_token'
};
Initialize the client
Create a reusable service:// src/app/services/buttercms.service.ts
import { Injectable } from '@angular/core';
import Butter from 'buttercms';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root'
})
export class ButterCMSService {
private butter = Butter(environment.butterCmsApiKey);
async getPage<T>(pageType: string, slug: string): Promise<T> {
const response = await this.butter.page.retrieve(pageType, slug);
return response.data.data;
}
async getPages<T>(pageType: string, params = {}): Promise<T[]> {
const response = await this.butter.page.list(pageType, params);
return response.data.data;
}
async getCollection<T>(keys: string[]): Promise<T> {
const response = await this.butter.content.retrieve(keys);
return response.data.data;
}
async getPosts(params = {}) {
const response = await this.butter.post.list(params);
return { posts: response.data.data, meta: response.data.meta };
}
async getPost(slug: string) {
const response = await this.butter.post.retrieve(slug);
return response.data.data;
}
}
For complete SDK documentation including all available methods and configuration options, see the JavaScript SDK Reference.
Pages
- Standalone Component (Angular 14+)
- NgModule Component
// src/app/pages/landing/landing.component.ts
import { Component, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute } from '@angular/router';
import { ButterCMSService } from '../../services/buttercms.service';
interface PageFields {
headline: string;
subheadline: string;
hero_image: string;
body: string;
}
@Component({
selector: 'app-landing',
standalone: true,
imports: [CommonModule],
template: `
<main *ngIf="page">
<h1>{{ page.fields.headline }}</h1>
<p>{{ page.fields.subheadline }}</p>
<img *ngIf="page.fields.hero_image"
[src]="page.fields.hero_image"
[alt]="page.fields.headline" />
<div [innerHTML]="page.fields.body"></div>
</main>
<div *ngIf="loading">Loading...</div>
<div *ngIf="error">{{ error }}</div>
`
})
export class LandingComponent implements OnInit {
private route = inject(ActivatedRoute);
private butterCMS = inject(ButterCMSService);
page: { fields: PageFields } | null = null;
loading = true;
error: string | null = null;
async ngOnInit() {
const slug = this.route.snapshot.paramMap.get('slug') || 'home';
try {
this.page = await this.butterCMS.getPage('landing-page', slug);
} catch {
this.error = 'Page not found';
} finally {
this.loading = false;
}
}
}
// src/app/pages/landing/landing.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ButterCMSService } from '../../services/buttercms.service';
interface PageFields {
headline: string;
subheadline: string;
hero_image: string;
body: string;
}
@Component({
selector: 'app-landing',
template: `
<main *ngIf="page">
<h1>{{ page.fields.headline }}</h1>
<p>{{ page.fields.subheadline }}</p>
<img *ngIf="page.fields.hero_image"
[src]="page.fields.hero_image"
[alt]="page.fields.headline" />
<div [innerHTML]="page.fields.body"></div>
</main>
<div *ngIf="loading">Loading...</div>
<div *ngIf="error">{{ error }}</div>
`
})
export class LandingComponent implements OnInit {
page: { fields: PageFields } | null = null;
loading = true;
error: string | null = null;
constructor(
private route: ActivatedRoute,
private butterCMS: ButterCMSService
) {}
async ngOnInit() {
const slug = this.route.snapshot.paramMap.get('slug') || 'home';
try {
this.page = await this.butterCMS.getPage('landing-page', slug);
} catch {
this.error = 'Page not found';
} finally {
this.loading = false;
}
}
}
Collections
- Standalone Component (Angular 14+)
- NgModule Component
// src/app/pages/brands/brands.component.ts
import { Component, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ButterCMSService } from '../../services/buttercms.service';
interface Brand {
name: string;
logo: string;
description: string;
}
@Component({
selector: 'app-brands',
standalone: true,
imports: [CommonModule],
template: `
<main>
<h1>Our Brands</h1>
<ul>
<li *ngFor="let brand of brands">
<img [src]="brand.logo" [alt]="brand.name" />
<h2>{{ brand.name }}</h2>
<div [innerHTML]="brand.description"></div>
</li>
</ul>
</main>
`
})
export class BrandsComponent implements OnInit {
private butterCMS = inject(ButterCMSService);
brands: Brand[] = [];
async ngOnInit() {
const content = await this.butterCMS.getCollection<{ brands: Brand[] }>(['brands']);
this.brands = content.brands;
}
}
// src/app/pages/brands/brands.component.ts
import { Component, OnInit } from '@angular/core';
import { ButterCMSService } from '../../services/buttercms.service';
interface Brand {
name: string;
logo: string;
description: string;
}
@Component({
selector: 'app-brands',
template: `
<main>
<h1>Our Brands</h1>
<ul>
<li *ngFor="let brand of brands">
<img [src]="brand.logo" [alt]="brand.name" />
<h2>{{ brand.name }}</h2>
<div [innerHTML]="brand.description"></div>
</li>
</ul>
</main>
`
})
export class BrandsComponent implements OnInit {
brands: Brand[] = [];
constructor(private butterCMS: ButterCMSService) {}
async ngOnInit() {
const content = await this.butterCMS.getCollection<{ brands: Brand[] }>(['brands']);
this.brands = content.brands;
}
}
Dynamic components
Component Renderer
// src/app/components/component-renderer/component-renderer.component.ts
import { Component, Input, Type, ViewChild, ViewContainerRef, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeroComponent } from '../hero/hero.component';
import { FeaturesComponent } from '../features/features.component';
import { CTAComponent } from '../cta/cta.component';
interface ButterComponent {
type: string;
fields: Record<string, any>;
}
const COMPONENT_MAP: Record<string, Type<any>> = {
hero: HeroComponent,
features: FeaturesComponent,
cta: CTAComponent,
};
@Component({
selector: 'app-component-renderer',
standalone: true,
imports: [CommonModule],
template: `<ng-container #container></ng-container>`
})
export class ComponentRendererComponent implements OnInit {
@Input() components: ButterComponent[] = [];
@ViewChild('container', { read: ViewContainerRef, static: true })
container!: ViewContainerRef;
ngOnInit() {
this.renderComponents();
}
private renderComponents() {
this.container.clear();
for (const component of this.components) {
const componentType = COMPONENT_MAP[component.type];
if (componentType) {
const componentRef = this.container.createComponent(componentType);
Object.assign(componentRef.instance, component.fields);
}
}
}
}
Example Component
// src/app/components/hero/hero.component.ts
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-hero',
standalone: true,
imports: [CommonModule],
template: `
<section class="hero">
<h1>{{ headline }}</h1>
<p>{{ subheadline }}</p>
<a *ngIf="button_label" [href]="button_url" class="btn">{{ button_label }}</a>
<img *ngIf="image" [src]="image" [alt]="headline" />
</section>
`
})
export class HeroComponent {
@Input() headline = '';
@Input() subheadline = '';
@Input() image = '';
@Input() button_label = '';
@Input() button_url = '';
}
Using in Pages
// src/app/pages/component-page/component-page.component.ts
import { Component, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute } from '@angular/router';
import { ButterCMSService } from '../../services/buttercms.service';
import { ComponentRendererComponent } from '../../components/component-renderer/component-renderer.component';
@Component({
selector: 'app-component-page',
standalone: true,
imports: [CommonModule, ComponentRendererComponent],
template: `
<main *ngIf="page">
<app-component-renderer [components]="page.fields.body"></app-component-renderer>
</main>
<div *ngIf="loading">Loading...</div>
`
})
export class ComponentPageComponent implements OnInit {
private route = inject(ActivatedRoute);
private butterCMS = inject(ButterCMSService);
page: any = null;
loading = true;
async ngOnInit() {
const slug = this.route.snapshot.paramMap.get('slug') || 'home';
try {
this.page = await this.butterCMS.getPage('landing-page', slug);
} finally {
this.loading = false;
}
}
}
Blog
- Blog Post List
- Single Blog Post
- Standalone Component (Angular 14+)
- NgModule Component
// src/app/pages/blog/blog-list.component.ts
import { Component, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { ButterCMSService } from '../../services/buttercms.service';
@Component({
selector: 'app-blog-list',
standalone: true,
imports: [CommonModule, RouterModule],
template: `
<main>
<h1>Blog</h1>
<ul>
<li *ngFor="let post of posts">
<h2><a [routerLink]="['/blog', post.slug]">{{ post.title }}</a></h2>
<p [innerHTML]="post.summary"></p>
<span>By {{ post.author.first_name }} {{ post.author.last_name }}</span>
</li>
</ul>
<a *ngIf="meta?.next_page"
[routerLink]="['/blog']"
[queryParams]="{page: meta.next_page}">
Next Page
</a>
</main>
`
})
export class BlogListComponent implements OnInit {
private butterCMS = inject(ButterCMSService);
posts: any[] = [];
meta: any = null;
async ngOnInit() {
const result = await this.butterCMS.getPosts({ page: 1, page_size: 10 });
this.posts = result.posts;
this.meta = result.meta;
}
}
// src/app/pages/blog/blog-list.component.ts
import { Component, OnInit } from '@angular/core';
import { ButterCMSService } from '../../services/buttercms.service';
@Component({
selector: 'app-blog-list',
template: `
<main>
<h1>Blog</h1>
<ul>
<li *ngFor="let post of posts">
<h2><a [routerLink]="['/blog', post.slug]">{{ post.title }}</a></h2>
<p [innerHTML]="post.summary"></p>
<span>By {{ post.author.first_name }} {{ post.author.last_name }}</span>
</li>
</ul>
<a *ngIf="meta?.next_page"
[routerLink]="['/blog']"
[queryParams]="{page: meta.next_page}">
Next Page
</a>
</main>
`
})
export class BlogListComponent implements OnInit {
posts: any[] = [];
meta: any = null;
constructor(private butterCMS: ButterCMSService) {}
async ngOnInit() {
const result = await this.butterCMS.getPosts({ page: 1, page_size: 10 });
this.posts = result.posts;
this.meta = result.meta;
}
}
- Standalone Component (Angular 14+)
- NgModule Component
// src/app/pages/blog/blog-post.component.ts
import { Component, OnInit, inject } from '@angular/core';
import { CommonModule, DatePipe } from '@angular/common';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { ButterCMSService } from '../../services/buttercms.service';
@Component({
selector: 'app-blog-post',
standalone: true,
imports: [CommonModule, RouterModule, DatePipe],
template: `
<article *ngIf="post">
<h1>{{ post.title }}</h1>
<p>
By {{ post.author.first_name }} {{ post.author.last_name }}
on {{ post.published | date:'mediumDate' }}
</p>
<img *ngIf="post.featured_image"
[src]="post.featured_image"
[alt]="post.title" />
<div [innerHTML]="post.body"></div>
<a routerLink="/blog">Back to Posts</a>
</article>
`
})
export class BlogPostComponent implements OnInit {
private route = inject(ActivatedRoute);
private butterCMS = inject(ButterCMSService);
post: any = null;
async ngOnInit() {
const slug = this.route.snapshot.paramMap.get('slug')!;
this.post = await this.butterCMS.getPost(slug);
}
}
// src/app/pages/blog/blog-post.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ButterCMSService } from '../../services/buttercms.service';
@Component({
selector: 'app-blog-post',
template: `
<article *ngIf="post">
<h1>{{ post.title }}</h1>
<p>
By {{ post.author.first_name }} {{ post.author.last_name }}
on {{ post.published | date:'mediumDate' }}
</p>
<img *ngIf="post.featured_image"
[src]="post.featured_image"
[alt]="post.title" />
<div [innerHTML]="post.body"></div>
<a routerLink="/blog">Back to Posts</a>
</article>
`
})
export class BlogPostComponent implements OnInit {
post: any = null;
constructor(
private route: ActivatedRoute,
private butterCMS: ButterCMSService
) {}
async ngOnInit() {
const slug = this.route.snapshot.paramMap.get('slug')!;
this.post = await this.butterCMS.getPost(slug);
}
}
Caching
Use a caching service for better performance:// src/app/services/butter-cache.service.ts
import { Injectable } from '@angular/core';
import { ButterCMSService } from './buttercms.service';
interface CacheEntry<T> {
data: T;
timestamp: number;
}
@Injectable({
providedIn: 'root'
})
export class ButterCacheService {
private cache = new Map<string, CacheEntry<any>>();
private ttl = 3600000; // 1 hour in milliseconds
constructor(private butterCMS: ButterCMSService) {}
async getPage<T>(pageType: string, slug: string): Promise<T> {
const cacheKey = `page_${pageType}_${slug}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
const data = await this.butterCMS.getPage<T>(pageType, slug);
this.cache.set(cacheKey, { data, timestamp: Date.now() });
return data;
}
async getPosts(params = {}) {
const cacheKey = `posts_${JSON.stringify(params)}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
const data = await this.butterCMS.getPosts(params);
this.cache.set(cacheKey, { data, timestamp: Date.now() });
return data;
}
invalidateCache(pattern?: string) {
if (pattern) {
for (const key of this.cache.keys()) {
if (key.includes(pattern)) {
this.cache.delete(key);
}
}
} else {
this.cache.clear();
}
}
}
SEO
// src/app/services/seo.service.ts
import { Injectable, inject } from '@angular/core';
import { Meta, Title } from '@angular/platform-browser';
@Injectable({
providedIn: 'root'
})
export class SeoService {
private meta = inject(Meta);
private title = inject(Title);
updateFromPage(page: any) {
const seo = page.fields.seo || {};
const headline = page.fields.headline || '';
this.title.setTitle(seo.title || headline);
this.meta.updateTag({ name: 'description', content: seo.description || '' });
// Open Graph
this.meta.updateTag({ property: 'og:title', content: seo.og_title || seo.title || headline });
this.meta.updateTag({ property: 'og:description', content: seo.og_description || seo.description || '' });
if (seo.og_image) {
this.meta.updateTag({ property: 'og:image', content: seo.og_image });
}
}
}
// Usage in component
@Component({...})
export class LandingComponent implements OnInit {
private seoService = inject(SeoService);
async ngOnInit() {
const page = await this.butterCMS.getPage('landing-page', 'home');
this.seoService.updateFromPage(page);
}
}
Routing
// src/app/app.routes.ts (Standalone)
import { Routes } from '@angular/router';
export const routes: Routes = [
{ path: '', loadComponent: () => import('./pages/landing/landing.component').then(m => m.LandingComponent) },
{ path: 'page/:slug', loadComponent: () => import('./pages/landing/landing.component').then(m => m.LandingComponent) },
{ path: 'brands', loadComponent: () => import('./pages/brands/brands.component').then(m => m.BrandsComponent) },
{ path: 'blog', loadComponent: () => import('./pages/blog/blog-list.component').then(m => m.BlogListComponent) },
{ path: 'blog/:slug', loadComponent: () => import('./pages/blog/blog-post.component').then(m => m.BlogPostComponent) },
];
Resources
Angular Starter
Pre-configured starter project
JavaScript SDK
Complete SDK reference
GitHub Repository
View source code
Content API
REST API documentation