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.
Installation
- npm
- yarn
- pnpm
npm install buttercms
yarn add buttercms
pnpm add buttercms
environment.ts:
// src/environments/environment.ts
export const environment = {
production: false,
butterCmsApiToken: 'your_api_token',
};
Initialize the client
Create an Angular service to wrap the ButterCMS SDK:// src/app/services/butter.service.ts
import { Injectable } from '@angular/core';
import Butter from 'buttercms';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
})
export class ButterService {
private butter = Butter(environment.butterCmsApiToken);
getPage<Fields extends object>(pageType: string, slug: string, params?: object) {
return this.butter.page.retrieve<Fields>(pageType, slug, params);
}
getContent(keys: string[], params?: object) {
return this.butter.content.retrieve(keys, params);
}
getPosts(params?: object) {
return this.butter.post.list(params);
}
getPost(slug: string) {
return this.butter.post.retrieve(slug);
}
}
For complete SDK documentation including all available methods and configuration options, see the JavaScript SDK Reference.
Pages
// src/app/pages/landing/landing.page.ts
import { Component, OnInit, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ButterService } from '../../services/butter.service';
interface PageFields {
headline: string;
subheadline: string;
hero_image: string;
body: string;
}
@Component({
selector: 'app-landing',
standalone: false,
templateUrl: './landing.page.html',
})
export class LandingPage implements OnInit {
page = signal<{ fields: PageFields } | null>(null);
loading = signal(true);
error = signal<string | null>(null);
constructor(
private route: ActivatedRoute,
private butter: ButterService
) {}
async ngOnInit() {
const slug = this.route.snapshot.paramMap.get('slug') || 'home';
try {
const response = await this.butter.getPage<PageFields>('landing_page', slug);
if (!response.data?.data) {
throw new Error('Failed to load page from ButterCMS');
}
this.page.set(response.data.data);
} catch {
this.error.set('Page not found');
} finally {
this.loading.set(false);
}
}
}
<!-- src/app/pages/landing/landing.page.html -->
<ion-header>
<ion-toolbar>
<ion-title>{{ page()?.fields?.headline }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div *ngIf="loading()" class="ion-text-center ion-padding">
<ion-spinner></ion-spinner>
</div>
<div *ngIf="error() as err" class="ion-padding">
<p>{{ err }}</p>
</div>
<div *ngIf="page() as p" class="ion-padding">
<ng-container *ngIf="!loading()">
<h1>{{ p.fields.headline }}</h1>
<p>{{ p.fields.subheadline }}</p>
<img
*ngIf="p.fields.hero_image"
[src]="p.fields.hero_image"
[alt]="p.fields.headline"
/>
<div [innerHTML]="p.fields.body"></div>
</ng-container>
</div>
</ion-content>
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{
path: 'page/:slug',
loadChildren: () =>
import('./pages/landing/landing.module').then((m) => m.LandingPageModule),
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
Collections
// src/app/pages/brands/brands.page.ts
import { Component, OnInit, signal } from '@angular/core';
import { ButterService } from '../../services/butter.service';
interface Brand {
name: string;
logo: string;
description: string;
}
@Component({
selector: 'app-brands',
standalone: false,
templateUrl: './brands.page.html',
})
export class BrandsPage implements OnInit {
brands = signal<Brand[]>([]);
loading = signal(true);
constructor(private butter: ButterService) {}
async ngOnInit() {
try {
const response = await this.butter.getContent(['brands']);
if (!response.data?.data) {
throw new Error('Failed to load brands from ButterCMS');
}
this.brands.set(response.data.data['brands'] as unknown as Brand[]);
} finally {
this.loading.set(false);
}
}
}
<!-- src/app/pages/brands/brands.page.html -->
<ion-header>
<ion-toolbar>
<ion-title>Our Brands</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div *ngIf="loading()" class="ion-text-center ion-padding">
<ion-spinner></ion-spinner>
</div>
<ion-list *ngIf="!loading()">
<ion-item *ngFor="let brand of brands()">
<ion-thumbnail slot="start">
<img [src]="brand.logo" [alt]="brand.name" />
</ion-thumbnail>
<ion-label>
<h2>{{ brand.name }}</h2>
<p [innerHTML]="brand.description"></p>
</ion-label>
</ion-item>
</ion-list>
</ion-content>
Dynamic components
Component registry
// src/app/components/component-renderer/component-renderer.component.ts
import {
Component,
Input,
ComponentRef,
Type,
ViewChild,
ViewContainerRef,
OnChanges,
SimpleChanges,
} from '@angular/core';
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, unknown>;
}
const componentMap: Record<string, Type<unknown>> = {
hero: HeroComponent,
features: FeaturesComponent,
cta: CtaComponent,
};
@Component({
selector: 'app-component-renderer',
standalone: false,
template: `<ng-container #container></ng-container>`,
})
export class ComponentRendererComponent implements OnChanges {
@Input() components: ButterComponent[] = [];
@ViewChild('container', { read: ViewContainerRef, static: true })
container!: ViewContainerRef;
ngOnChanges(changes: SimpleChanges) {
if (changes['components']) {
this.renderComponents();
}
}
private renderComponents() {
this.container.clear();
this.components.forEach((item) => {
const componentClass = componentMap[item.type];
if (!componentClass) return;
const ref: ComponentRef<unknown> = this.container.createComponent(componentClass);
Object.assign(ref.instance as object, item.fields);
});
}
}
Example Component
// src/app/components/hero/hero.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-hero',
standalone: false,
template: `
<section class="hero ion-padding">
<h1>{{ headline }}</h1>
<p>{{ subheadline }}</p>
<ion-button *ngIf="button_label" [href]="button_url">{{ button_label }}</ion-button>
<img *ngIf="image" [src]="image" [alt]="headline" />
</section>
`,
})
export class HeroComponent {
@Input() headline = '';
@Input() subheadline = '';
@Input() image = '';
@Input() button_label = '';
@Input() button_url = '';
}
Blog
- Blog Post List
- Single Blog Post
// src/app/pages/blog/blog.page.ts
import { Component, OnInit, signal } from '@angular/core';
import { ButterService } from '../../services/butter.service';
interface Post {
slug: string;
title: string;
summary: string;
published: string | null;
author?: { first_name?: string; last_name?: string };
}
@Component({
selector: 'app-blog',
standalone: false,
templateUrl: './blog.page.html',
})
export class BlogPage implements OnInit {
posts = signal<Post[]>([]);
nextPage = signal<number | null>(null);
loading = signal(true);
constructor(private butter: ButterService) {}
async ngOnInit() {
await this.loadPosts(1);
}
async loadPosts(page: number | null) {
if (page === null) {
return;
}
this.loading.set(true);
try {
const response = await this.butter.getPosts({ page, page_size: 10 });
if (!response.data?.data) {
throw new Error('Failed to load posts from ButterCMS');
}
this.posts.set(response.data.data);
this.nextPage.set(response.data.meta?.next_page ?? null);
} finally {
this.loading.set(false);
}
}
}
<!-- src/app/pages/blog/blog.page.html -->
<ion-header>
<ion-toolbar>
<ion-title>Blog</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div *ngIf="loading()" class="ion-text-center ion-padding">
<ion-spinner></ion-spinner>
</div>
<ion-list *ngIf="!loading()">
<ion-item
*ngFor="let post of posts()"
[routerLink]="['/blog', post.slug]"
button
>
<ion-label>
<h2>{{ post.title }}</h2>
<p>By {{ post.author?.first_name }} {{ post.author?.last_name }}</p>
<p [innerHTML]="post.summary"></p>
</ion-label>
</ion-item>
</ion-list>
<div *ngIf="nextPage()" class="ion-padding">
<ion-button expand="block" (click)="loadPosts(nextPage())">Load More</ion-button>
</div>
</ion-content>
// src/app/pages/blog-post/blog-post.page.ts
import { Component, OnInit, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ButterService } from '../../services/butter.service';
interface Post {
title: string;
body?: string;
published: string | null;
featured_image: string | null;
author?: { first_name?: string; last_name?: string };
}
@Component({
selector: 'app-blog-post',
standalone: false,
templateUrl: './blog-post.page.html',
})
export class BlogPostPage implements OnInit {
post = signal<Post | null>(null);
loading = signal(true);
error = signal<string | null>(null);
constructor(
private route: ActivatedRoute,
private butter: ButterService
) {}
async ngOnInit() {
const slug = this.route.snapshot.paramMap.get('slug');
if (!slug) {
this.error.set('Post not found');
this.loading.set(false);
return;
}
try {
const response = await this.butter.getPost(slug);
if (!response.data?.data) {
throw new Error('Failed to load post from ButterCMS');
}
this.post.set(response.data.data);
} catch {
this.error.set('Post not found');
} finally {
this.loading.set(false);
}
}
}
<!-- src/app/pages/blog-post/blog-post.page.html -->
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button defaultHref="/blog"></ion-back-button>
</ion-buttons>
<ion-title>{{ post()?.title }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div *ngIf="loading()" class="ion-text-center ion-padding">
<ion-spinner></ion-spinner>
</div>
<div *ngIf="error() as err" class="ion-padding">
<p>{{ err }}</p>
</div>
<article *ngIf="post() as p" class="ion-padding">
<ng-container *ngIf="!loading()">
<h1>{{ p.title }}</h1>
<p>
By {{ p.author?.first_name }} {{ p.author?.last_name }}
</p>
<img
*ngIf="p.featured_image"
[src]="p.featured_image"
[alt]="p.title"
style="width: 100%"
/>
<div [innerHTML]="p.body"></div>
</ng-container>
</article>
</ion-content>
Resources
JavaScript SDK
Complete SDK reference
GitHub Repository
View source code
Content API
REST API documentation