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.Node.js Starter Project
Hit the ground running with a pre-configured Node.js + ButterCMS setup.
Installation
- npm
- yarn
- pnpm
npm install buttercms
yarn add buttercms
pnpm add buttercms
BUTTERCMS_API_TOKEN=your_api_token
Initialize the client
- ES Modules
- CommonJS
- TypeScript
// lib/buttercms.js
import Butter from 'buttercms';
const butter = Butter(process.env.BUTTERCMS_API_TOKEN);
export default butter;
// lib/buttercms.js
const Butter = require('buttercms');
const butter = Butter(process.env.BUTTERCMS_API_TOKEN);
module.exports = butter;
// lib/buttercms.ts
import Butter from 'buttercms';
const butter = Butter(process.env.BUTTERCMS_API_TOKEN!);
export default butter;
For complete SDK documentation including all available methods and configuration options, see the JavaScript SDK Reference.
Pages
- ES Modules
- CommonJS
- TypeScript
// services/pages.js
import butter from '../lib/buttercms.js';
export async function getPage(pageType, slug, params = {}) {
try {
const response = await butter.page.retrieve(pageType, slug, params);
if (!response.data?.data) {
throw new Error('Failed to load page from ButterCMS');
}
return response.data.data;
} catch (error) {
console.error('Page not found:', error);
return null;
}
}
export async function getPages(pageType, params = {}) {
const response = await butter.page.list(pageType, params);
if (!response.data?.data) {
throw new Error('Failed to load pages from ButterCMS');
}
return response.data.data;
}
// Usage
const page = await getPage('landing_page', 'home');
console.log(page.fields.headline);
console.log(page.fields.body);
// services/pages.js
const butter = require('../lib/buttercms');
async function getPage(pageType, slug, params = {}) {
try {
const response = await butter.page.retrieve(pageType, slug, params);
if (!response.data?.data) {
throw new Error('Failed to load page from ButterCMS');
}
return response.data.data;
} catch (error) {
console.error('Page not found:', error);
return null;
}
}
async function getPages(pageType, params = {}) {
const response = await butter.page.list(pageType, params);
if (!response.data?.data) {
throw new Error('Failed to load pages from ButterCMS');
}
return response.data.data;
}
module.exports = { getPage, getPages };
// services/pages.ts
import butter from '../lib/buttercms';
interface PageFields {
headline: string;
subheadline: string;
hero_image: string;
body: string;
seo?: {
title: string;
description: string;
og_title?: string;
og_description?: string;
og_image?: string;
};
}
interface Page<T = PageFields> {
slug: string;
fields: T;
}
export async function getPage<T extends object = PageFields>(
pageType: string,
slug: string,
params: Record<string, string> = {}
): Promise<Page<T> | null> {
try {
const response = await butter.page.retrieve<T>(pageType, slug, params);
if (!response.data?.data) {
throw new Error('Failed to load page from ButterCMS');
}
return response.data.data;
} catch {
return null;
}
}
export async function getPages<T extends object = PageFields>(
pageType: string,
params: Record<string, string> = {}
): Promise<Page<T>[]> {
const response = await butter.page.list<T>(pageType, params);
if (!response.data?.data) {
throw new Error('Failed to load pages from ButterCMS');
}
return response.data.data;
}
Collections
- ES Modules
- CommonJS
- TypeScript
// services/collections.js
import butter from '../lib/buttercms.js';
export async function getCollection(keys, params = {}) {
const response = await butter.content.retrieve(keys, params);
if (!response.data?.data) {
throw new Error('Failed to load collection from ButterCMS');
}
return response.data.data;
}
// Usage
const content = await getCollection(['brands', 'navigation']);
console.log(content.brands);
console.log(content.navigation);
// services/collections.js
const butter = require('../lib/buttercms');
async function getCollection(keys, params = {}) {
const response = await butter.content.retrieve(keys, params);
if (!response.data?.data) {
throw new Error('Failed to load collection from ButterCMS');
}
return response.data.data;
}
module.exports = { getCollection };
// services/collections.ts
import butter from '../lib/buttercms';
interface Brand {
name: string;
logo: string;
description: string;
}
interface CollectionResponse {
brands?: Brand[];
navigation?: Array<{ label: string; url: string }>;
[key: string]: unknown;
}
export async function getCollection(
keys: string[],
params: Record<string, string> = {}
): Promise<CollectionResponse> {
const response = await butter.content.retrieve(keys, params);
if (!response.data?.data) {
throw new Error('Failed to load collection from ButterCMS');
}
return response.data.data;
}
Dynamic components
Component Renderer
// services/components.js
const componentRenderers = {
hero: (fields) => ({
type: 'hero',
html: `
<section class="hero">
<h1>${fields.headline}</h1>
<p>${fields.subheadline}</p>
${fields.button_label ? `<a href="${fields.button_url}" class="btn">${fields.button_label}</a>` : ''}
${fields.image ? `<img src="${fields.image}" alt="${fields.headline}" />` : ''}
</section>
`
}),
features: (fields) => ({
type: 'features',
html: `
<section class="features">
<h2>${fields.headline}</h2>
<div class="features-grid">
${fields.items.map(item => `
<div class="feature">
${item.icon ? `<img src="${item.icon}" alt="${item.title}" />` : ''}
<h3>${item.title}</h3>
<p>${item.description}</p>
</div>
`).join('')}
</div>
</section>
`
}),
cta: (fields) => ({
type: 'cta',
html: `
<section class="cta">
<h2>${fields.headline}</h2>
<p>${fields.subheadline}</p>
<a href="${fields.button_url}" class="btn">${fields.button_label}</a>
</section>
`
})
};
export function renderComponents(components) {
return components.map(component => {
const renderer = componentRenderers[component.type];
if (renderer) {
return renderer(component.fields);
}
console.warn(`Unknown component type: ${component.type}`);
return { type: component.type, html: '' };
});
}
export function renderComponentsToHTML(components) {
return renderComponents(components).map(c => c.html).join('');
}
Example Component
// Example hero component renderer
function renderHero(fields) {
return `
<section class="hero">
<div class="hero-content">
<h1>${fields.headline}</h1>
<p class="subheadline">${fields.subheadline}</p>
${fields.button_label ? `
<a href="${fields.button_url}" class="hero-cta">${fields.button_label}</a>
` : ''}
</div>
${fields.image ? `
<div class="hero-image">
<img src="${fields.image}" alt="${fields.headline}" />
</div>
` : ''}
</section>
`;
}
Using in Pages
This example fetches a
component_page whose body is a Page Builder field (an
array of components). A WYSIWYG page type like landing_page would return a
body string instead, which the component renderer can’t iterate over.- ES Modules
- CommonJS
- TypeScript
// services/component-pages.js
import butter from '../lib/buttercms.js';
import { renderComponentsToHTML } from './components.js';
export async function getComponentPage(slug) {
try {
const response = await butter.page.retrieve('component_page', slug);
if (!response.data?.data) {
throw new Error('Failed to load page from ButterCMS');
}
const page = response.data.data;
// Render body components to HTML
const bodyComponents = page.fields.body || [];
const renderedHTML = renderComponentsToHTML(bodyComponents);
return {
page,
renderedHTML
};
} catch (error) {
console.error('Page not found:', error);
return null;
}
}
// Usage
const { page, renderedHTML } = await getComponentPage('home');
// Use renderedHTML in your template
// services/component-pages.js
const butter = require('../lib/buttercms');
const { renderComponentsToHTML } = require('./components');
async function getComponentPage(slug) {
try {
const response = await butter.page.retrieve('component_page', slug);
if (!response.data?.data) {
throw new Error('Failed to load page from ButterCMS');
}
const page = response.data.data;
const bodyComponents = page.fields.body || [];
const renderedHTML = renderComponentsToHTML(bodyComponents);
return { page, renderedHTML };
} catch (error) {
console.error('Page not found:', error);
return null;
}
}
module.exports = { getComponentPage };
// services/component-pages.ts
import butter from '../lib/buttercms';
import { renderComponentsToHTML } from './components';
interface ComponentField {
type: string;
fields: Record<string, unknown>;
}
interface ComponentPageFields {
body: ComponentField[];
seo?: {
title: string;
description: string;
};
}
export async function getComponentPage(slug: string): Promise<{
page: { slug: string; fields: ComponentPageFields };
renderedHTML: string;
} | null> {
try {
const response = await butter.page.retrieve<ComponentPageFields>('component_page', slug);
if (!response.data?.data) {
throw new Error('Failed to load page from ButterCMS');
}
const page = response.data.data;
const bodyComponents = page.fields.body || [];
const renderedHTML = renderComponentsToHTML(bodyComponents);
return { page, renderedHTML };
} catch {
return null;
}
}
Blog
- Blog List
- Blog Post
- ES Modules
- CommonJS
- TypeScript
// services/blog.js
import butter from '../lib/buttercms.js';
export async function getPosts(params = {}) {
const response = await butter.post.list({
page: 1,
page_size: 10,
...params
});
if (!response.data?.data || !response.data?.meta) {
throw new Error('Failed to load posts from ButterCMS');
}
return {
posts: response.data.data,
meta: response.data.meta
};
}
export async function getPostsByCategory(categorySlug, params = {}) {
return getPosts({
category_slug: categorySlug,
...params
});
}
export async function searchPosts(query, params = {}) {
const response = await butter.post.search(query, params);
if (!response.data?.data) {
throw new Error('Failed to load search results from ButterCMS');
}
return response.data.data;
}
// Usage
const { posts, meta } = await getPosts({ page: 1 });
posts.forEach(post => {
console.log(post.title);
console.log(`By ${post.author.first_name} ${post.author.last_name}`);
});
if (meta.next_page) {
const nextPage = await getPosts({ page: meta.next_page });
}
// services/blog.js
const butter = require('../lib/buttercms');
async function getPosts(params = {}) {
const response = await butter.post.list({
page: 1,
page_size: 10,
...params
});
if (!response.data?.data || !response.data?.meta) {
throw new Error('Failed to load posts from ButterCMS');
}
return {
posts: response.data.data,
meta: response.data.meta
};
}
async function getPostsByCategory(categorySlug, params = {}) {
return getPosts({
category_slug: categorySlug,
...params
});
}
async function searchPosts(query, params = {}) {
const response = await butter.post.search(query, params);
if (!response.data?.data) {
throw new Error('Failed to load search results from ButterCMS');
}
return response.data.data;
}
module.exports = { getPosts, getPostsByCategory, searchPosts };
// services/blog.ts
import butter from '../lib/buttercms';
interface Post {
slug: string;
title: string;
body?: string;
summary: string;
published: string | null;
featured_image: string | null;
seo_title?: string;
meta_description?: string;
author: {
first_name: string;
last_name: string;
email?: string;
bio?: string;
};
categories: Array<{ name: string; slug: string }>;
tags: Array<{ name: string; slug: string }>;
}
interface PostsMeta {
next_page: number | null;
previous_page: number | null;
count: number;
}
export async function getPosts(
params: Record<string, unknown> = {}
): Promise<{ posts: Post[]; meta: PostsMeta }> {
const response = await butter.post.list({
page: 1,
page_size: 10,
...params
});
if (!response.data?.data || !response.data?.meta) {
throw new Error('Failed to load posts from ButterCMS');
}
return {
posts: response.data.data,
meta: response.data.meta
};
}
- ES Modules
- CommonJS
- TypeScript
// services/blog.js
import butter from '../lib/buttercms.js';
export async function getPost(slug) {
try {
const response = await butter.post.retrieve(slug);
if (!response.data?.data) {
throw new Error('Failed to load post from ButterCMS');
}
return response.data.data;
} catch (error) {
console.error('Post not found:', error);
return null;
}
}
// Usage
const post = await getPost('my-first-post');
if (post) {
console.log(post.title);
console.log(post.body);
console.log(`By ${post.author.first_name} ${post.author.last_name}`);
}
// services/blog.js
const butter = require('../lib/buttercms');
async function getPost(slug) {
try {
const response = await butter.post.retrieve(slug);
if (!response.data?.data) {
throw new Error('Failed to load post from ButterCMS');
}
return response.data.data;
} catch (error) {
console.error('Post not found:', error);
return null;
}
}
module.exports = { getPost };
// services/blog.ts
import butter from '../lib/buttercms';
export async function getPost(slug: string): Promise<Post | null> {
try {
const response = await butter.post.retrieve(slug);
if (!response.data?.data) {
throw new Error('Failed to load post from ButterCMS');
}
return response.data.data;
} catch {
return null;
}
}
Preview Mode
// services/preview.js
import butter from '../lib/buttercms.js';
export async function getPageWithPreview(pageType, slug, preview = false) {
const params = {};
if (preview) {
params.preview = 1;
}
try {
const response = await butter.page.retrieve(pageType, slug, params);
if (!response.data?.data) {
throw new Error('Failed to load page from ButterCMS');
}
return response.data.data;
} catch (error) {
return null;
}
}
// Usage in HTTP handler
export async function handlePageRequest(req, res) {
const { slug } = req.params;
const preview = req.query.preview === 'true';
const page = await getPageWithPreview('landing_page', slug, preview);
if (!page) {
return res.status(404).json({ error: 'Page not found' });
}
res.json({ page, isPreview: preview });
}
Caching
// services/cache.js
import NodeCache from 'node-cache';
const cache = new NodeCache({ stdTTL: 3600 }); // 1 hour TTL
export function getCached(key) {
return cache.get(key);
}
export function setCache(key, value, ttl) {
cache.set(key, value, ttl);
}
export function invalidateCache(pattern) {
const keys = cache.keys();
keys.forEach(key => {
if (key.includes(pattern)) {
cache.del(key);
}
});
}
// Usage with ButterCMS
import butter from '../lib/buttercms.js';
export async function getPageCached(pageType, slug, preview = false) {
// Skip cache for preview mode
if (preview) {
const response = await butter.page.retrieve(pageType, slug, { preview: 1 });
if (!response.data?.data) {
throw new Error('Failed to load page from ButterCMS');
}
return response.data.data;
}
const cacheKey = `page:${pageType}:${slug}`;
let page = getCached(cacheKey);
if (!page) {
const response = await butter.page.retrieve(pageType, slug);
if (!response.data?.data) {
throw new Error('Failed to load page from ButterCMS');
}
page = response.data.data;
setCache(cacheKey, page);
}
return page;
}
export async function getPostsCached(params = {}) {
const cacheKey = `posts:${JSON.stringify(params)}`;
let result = getCached(cacheKey);
if (!result) {
const response = await butter.post.list(params);
if (!response.data?.data || !response.data?.meta) {
throw new Error('Failed to load posts from ButterCMS');
}
result = {
posts: response.data.data,
meta: response.data.meta
};
setCache(cacheKey, result);
}
return result;
}
Webhook cache invalidation
// webhooks/butter.js
import { invalidateCache } from '../services/cache.js';
export function handleWebhook(req, res) {
const { webhook } = req.body;
const event = webhook?.event || '';
if (event.includes('page')) {
invalidateCache('page:');
}
if (event.includes('post')) {
invalidateCache('posts:');
}
if (event.includes('content')) {
invalidateCache('collection:');
}
res.status(200).json({ status: 'ok' });
}
SEO
// services/seo.js
export function extractSEO(page) {
const seo = page.fields.seo || {};
const headline = page.fields.headline || '';
return {
title: seo.title || headline,
description: seo.description || '',
ogTitle: seo.og_title || seo.title || headline,
ogDescription: seo.og_description || seo.description || '',
ogImage: seo.og_image || null
};
}
export function generateMetaTags(seo) {
const tags = [
`<title>${seo.title}</title>`,
`<meta name="description" content="${seo.description}">`,
`<meta property="og:title" content="${seo.ogTitle}">`,
`<meta property="og:description" content="${seo.ogDescription}">`
];
if (seo.ogImage) {
tags.push(`<meta property="og:image" content="${seo.ogImage}">`);
}
tags.push(
`<meta name="twitter:card" content="summary_large_image">`,
`<meta name="twitter:title" content="${seo.title}">`,
`<meta name="twitter:description" content="${seo.description}">`
);
return tags.join('\n');
}
// Usage
const page = await getPage('landing_page', 'home');
const seo = extractSEO(page);
const metaTags = generateMetaTags(seo);
HTTP server example
// server.js
import http from 'node:http';
import butter from './lib/buttercms.js';
import { getPageCached, getPostsCached, invalidateCache } from './services/cache.js';
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
res.setHeader('Content-Type', 'application/json');
try {
// Pages
if (url.pathname.match(/^\/api\/page\/(.+)\/(.+)$/)) {
const [, pageType, slug] = url.pathname.match(/^\/api\/page\/(.+)\/(.+)$/);
const preview = url.searchParams.get('preview') === 'true';
const page = await getPageCached(pageType, slug, preview);
return res.end(JSON.stringify(page));
}
// Blog posts list
if (url.pathname === '/api/posts') {
const page = parseInt(url.searchParams.get('page') || '1');
const result = await getPostsCached({ page, page_size: 10 });
return res.end(JSON.stringify(result));
}
// Single blog post
if (url.pathname.match(/^\/api\/posts\/(.+)$/)) {
const [, slug] = url.pathname.match(/^\/api\/posts\/(.+)$/);
const response = await butter.post.retrieve(slug);
return res.end(JSON.stringify(response.data.data));
}
// Collections
if (url.pathname === '/api/content') {
const keys = url.searchParams.get('keys')?.split(',') || [];
const response = await butter.content.retrieve(keys);
return res.end(JSON.stringify(response.data.data));
}
// Webhook
if (url.pathname === '/webhooks/butter' && req.method === 'POST') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
const { webhook } = JSON.parse(body);
if (webhook?.event?.includes('page')) invalidateCache('page:');
if (webhook?.event?.includes('post')) invalidateCache('posts:');
res.end(JSON.stringify({ status: 'ok' }));
});
return;
}
res.statusCode = 404;
res.end(JSON.stringify({ error: 'Not found' }));
} catch (error) {
res.statusCode = error.response?.status || 500;
res.end(JSON.stringify({ error: error.message }));
}
});
server.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
Resources
Node.js Starter
Pre-configured starter project
JavaScript SDK
Complete SDK reference
Express.js Guide
Express.js integration
Webhooks
Set up content webhooks