Skip to main content

Overview

This integration guide shows you how to how to update your existing project to:
  1. install the ButterCMS package
  2. instantiate ButterCMS
  3. 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 install buttercms
Add your API token to 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(pageType: string, slug: string, params?: object) {
    return this.butter.page.retrieve(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 } 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',
  templateUrl: './landing.page.html',
})
export class LandingPage implements OnInit {
  page: { fields: PageFields } | null = null;
  loading = true;
  error: 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('landing-page', slug);
      this.page = response.data.data;
    } catch {
      this.error = 'Page not found';
    } finally {
      this.loading = 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" class="ion-padding">
    <p>{{ error }}</p>
  </div>

  <div *ngIf="page && !loading" class="ion-padding">
    <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>
  </div>
</ion-content>
Register the route in your app module:
// 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 } from '@angular/core';
import { ButterService } from '../../services/butter.service';

interface Brand {
  name: string;
  logo: string;
  description: string;
}

@Component({
  selector: 'app-brands',
  templateUrl: './brands.page.html',
})
export class BrandsPage implements OnInit {
  brands: Brand[] = [];
  loading = true;

  constructor(private butter: ButterService) {}

  async ngOnInit() {
    try {
      const response = await this.butter.getContent(['brands']);
      this.brands = response.data.data['brands'];
    } finally {
      this.loading = 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,
  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, any>;
}

const componentMap: Record<string, any> = {
  hero: HeroComponent,
  features: FeaturesComponent,
  cta: CtaComponent,
};

@Component({
  selector: 'app-component-renderer',
  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<any> = this.container.createComponent(componentClass);
      Object.assign(ref.instance, item.fields);
    });
  }
}

Example Component

// src/app/components/hero/hero.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-hero',
  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

// src/app/pages/blog/blog.page.ts
import { Component, OnInit } from '@angular/core';
import { ButterService } from '../../services/butter.service';

interface Post {
  slug: string;
  title: string;
  summary: string;
  published: string;
  author: { first_name: string; last_name: string };
}

@Component({
  selector: 'app-blog',
  templateUrl: './blog.page.html',
})
export class BlogPage implements OnInit {
  posts: Post[] = [];
  nextPage: number | null = null;
  loading = true;

  constructor(private butter: ButterService) {}

  async ngOnInit() {
    await this.loadPosts(1);
  }

  async loadPosts(page: number) {
    this.loading = true;
    try {
      const response = await this.butter.getPosts({ page, page_size: 10 });
      this.posts = response.data.data;
      this.nextPage = response.data.meta.next_page;
    } finally {
      this.loading = 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 in 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>

Resources

JavaScript SDK

Complete SDK reference

GitHub Repository

View source code

Content API

REST API documentation