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.

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 install buttercms
Add your API token to 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

// 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;
    }
  }
}

Collections

// 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;
  }
}

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

// 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;
  }
}

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