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.

Express.js Starter Project

Hit the ground running with a pre-configured Express.js + ButterCMS setup.

Installation

npm install buttercms express ejs
Add your API token to .env:
BUTTERCMS_API_TOKEN=your_api_token

Initialize the client

Create a reusable client instance:
// lib/buttercms.js
const Butter = require('buttercms');

const butter = Butter(process.env.BUTTERCMS_API_TOKEN);

module.exports = butter;
For complete SDK documentation including all available methods and configuration options, see the JavaScript SDK Reference.

Basic setup

// app.js
require('dotenv').config();
const express = require('express');
const butter = require('./lib/buttercms');

const app = express();

app.set('view engine', 'ejs');
app.set('views', './views');

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

module.exports = app;

Pages

// routes/pages.js
const express = require('express');
const router = express.Router();
const butter = require('../lib/buttercms');

router.get('/:slug?', async (req, res) => {
  const slug = req.params.slug || 'home';
  try {
    const response = await butter.page.retrieve('landing-page', slug);
    res.render('landing', { page: response.data.data });
  } catch (err) {
    res.status(404).render('404');
  }
});

module.exports = router;
<!-- views/landing.ejs -->
<!DOCTYPE html>
<html>
<head>
  <title><%= page.fields.headline %></title>
</head>
<body>
  <main>
    <h1><%= page.fields.headline %></h1>
    <p><%= page.fields.subheadline %></p>
    <% if (page.fields.hero_image) { %>
      <img src="<%= page.fields.hero_image %>" alt="<%= page.fields.headline %>" />
    <% } %>
    <%- page.fields.body %>
  </main>
</body>
</html>

Collections

// routes/brands.js
const express = require('express');
const router = express.Router();
const butter = require('../lib/buttercms');

router.get('/', async (req, res) => {
  const response = await butter.content.retrieve(['brands']);
  res.render('brands', { brands: response.data.data.brands });
});

module.exports = router;
<!-- views/brands.ejs -->
<main>
  <h1>Our Brands</h1>
  <ul>
    <% brands.forEach(brand => { %>
      <li>
        <img src="<%= brand.logo %>" alt="<%= brand.name %>" />
        <h2><%= brand.name %></h2>
        <%- brand.description %>
      </li>
    <% }); %>
  </ul>
</main>

Dynamic components

Component Renderer

// lib/component-renderer.js
const componentTemplates = {
  hero: 'components/hero',
  features: 'components/features',
  testimonials: 'components/testimonials',
  cta: 'components/cta',
};

function getComponentTemplate(type) {
  return componentTemplates[type] || null;
}

module.exports = { getComponentTemplate };

Component template

<!-- views/components/hero.ejs -->
<section class="hero">
  <h1><%= fields.headline %></h1>
  <p><%= fields.subheadline %></p>
  <% if (fields.button_label) { %>
    <a href="<%= fields.button_url %>" class="btn"><%= fields.button_label %></a>
  <% } %>
  <% if (fields.image) { %>
    <img src="<%= fields.image %>" alt="<%= fields.headline %>" />
  <% } %>
</section>

Using in routes

// routes/component-pages.js
const express = require('express');
const router = express.Router();
const butter = require('../lib/buttercms');
const { getComponentTemplate } = require('../lib/component-renderer');

router.get('/:slug', async (req, res) => {
  try {
    const response = await butter.page.retrieve('landing-page', req.params.slug);
    const page = response.data.data;

    // Map components to their templates
    const components = page.fields.body.map(component => ({
      template: getComponentTemplate(component.type),
      fields: component.fields,
    })).filter(c => c.template);

    res.render('component-page', { page, components });
  } catch (err) {
    res.status(404).render('404');
  }
});

module.exports = router;
<!-- views/component-page.ejs -->
<!DOCTYPE html>
<html>
<head>
  <title><%= page.fields.seo?.title || 'Page' %></title>
</head>
<body>
  <main>
    <% components.forEach(component => { %>
      <%- include(component.template, { fields: component.fields }) %>
    <% }); %>
  </main>
</body>
</html>

Blog

// routes/blog.js
const express = require('express');
const router = express.Router();
const butter = require('../lib/buttercms');

router.get('/', async (req, res) => {
  const page = req.query.page || 1;
  const response = await butter.post.list({ page, page_size: 10 });
  res.render('blog/list', {
    posts: response.data.data,
    meta: response.data.meta,
  });
});

module.exports = router;
<!-- views/blog/list.ejs -->
<main>
  <h1>Blog</h1>
  <ul>
    <% posts.forEach(post => { %>
      <li>
        <h2><a href="https://buttercms.com/blog/<%= post.slug %>"><%= post.title %></a></h2>
        <%- post.summary %>
        <span>By <%= post.author.first_name %> <%= post.author.last_name %></span>
      </li>
    <% }); %>
  </ul>
  <% if (meta.next_page) { %>
    <a href="/blog?page=<%= meta.next_page %>">Next Page</a>
  <% } %>
</main>

Caching

Use a caching middleware for better performance:
// middleware/cache.js
const cache = new Map();
const CACHE_TTL = 3600000; // 1 hour

function butterCache(keyPrefix) {
  return async (req, res, next) => {
    const cacheKey = `${keyPrefix}_${req.originalUrl}`;
    const cached = cache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
      res.locals.butterCache = cached.data;
      return next();
    }

    // Store original render function
    const originalRender = res.render.bind(res);

    // Override render to cache the data
    res.render = (view, options) => {
      cache.set(cacheKey, {
        data: options,
        timestamp: Date.now(),
      });
      originalRender(view, options);
    };

    next();
  };
}

function invalidateCache(pattern) {
  for (const key of cache.keys()) {
    if (key.includes(pattern)) {
      cache.delete(key);
    }
  }
}

module.exports = { butterCache, invalidateCache };

Webhook handler

// routes/webhooks.js
const express = require('express');
const router = express.Router();
const { invalidateCache } = require('../middleware/cache');

router.post('/buttercms', express.json(), (req, res) => {
  const { webhook_type } = req.body;

  if (webhook_type?.includes('published') ||
      webhook_type?.includes('updated') ||
      webhook_type?.includes('deleted')) {
    invalidateCache('butter');
  }

  res.json({ status: 'ok' });
});

module.exports = router;

SEO

// middleware/seo.js
function extractSeoData(page) {
  const seo = page.fields.seo || {};
  return {
    title: seo.title || page.fields.headline || 'Page',
    description: seo.description || '',
    ogTitle: seo.og_title || seo.title || page.fields.headline,
    ogDescription: seo.og_description || seo.description,
    ogImage: seo.og_image || page.fields.hero_image || '',
  };
}

module.exports = { extractSeoData };
<!-- views/partials/head.ejs -->
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= seo.title %></title>
  <meta name="description" content="<%= seo.description %>">

  <!-- Open Graph -->
  <meta property="og:title" content="<%= seo.ogTitle %>">
  <meta property="og:description" content="<%= seo.ogDescription %>">
  <% if (seo.ogImage) { %>
    <meta property="og:image" content="<%= seo.ogImage %>">
  <% } %>
</head>

Complete app setup

// app.js
require('dotenv').config();
const express = require('express');
const pagesRouter = require('./routes/pages');
const blogRouter = require('./routes/blog');
const brandsRouter = require('./routes/brands');
const webhooksRouter = require('./routes/webhooks');

const app = express();

app.set('view engine', 'ejs');

app.use('/', pagesRouter);
app.use('/page', pagesRouter);
app.use('/blog', blogRouter);
app.use('/brands', brandsRouter);
app.use('/webhooks', webhooksRouter);

// 404 handler
app.use((req, res) => {
  res.status(404).render('404');
});

app.listen(process.env.PORT || 3000);

Resources

Express.js Starter

Pre-configured starter project

JavaScript SDK

Complete SDK reference

GitHub Repository

View source code

Content API

REST API documentation