> ## Documentation Index
> Fetch the complete documentation index at: https://buttercms.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Node.js

> Build content-driven Node.js apps with ButterCMS. Covers setup, pages, collections, components, and blogs, with CommonJS and ES examples.

## 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](../../core-concepts/content-types/pages), [Collections](../../core-concepts/content-types/collections), and [Blog Posts](../../core-concepts/content-types/blog-engine).

<Tip>
  In order for the snippets to work, you'll need to [setup your dashboard content schemas inside of ButterCMS](../buttercms-setup) first.
</Tip>

### 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.

<Card title="Node.js Starter Project" icon="rocket" href="../starter-projects/nodejs">
  Hit the ground running with a pre-configured Node.js + ButterCMS setup.
</Card>

## Installation

<Tabs>
  <Tab title="npm">
    ```bash theme={null}
    npm install buttercms
    ```
  </Tab>

  <Tab title="yarn">
    ```bash theme={null}
    yarn add buttercms
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash theme={null}
    pnpm add buttercms
    ```
  </Tab>
</Tabs>

Set your API token as an environment variable:

```bash theme={null}
BUTTERCMS_API_TOKEN=your_api_token
```

## Initialize the client

<Tabs>
  <Tab title="ES Modules">
    ```javascript theme={null}
    // lib/buttercms.js
    import Butter from 'buttercms';

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

    export default butter;
    ```
  </Tab>

  <Tab title="CommonJS">
    ```javascript theme={null}
    // lib/buttercms.js
    const Butter = require('buttercms');

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

    module.exports = butter;
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    // lib/buttercms.ts
    import Butter from 'buttercms';

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

    export default butter;
    ```
  </Tab>
</Tabs>

<Info>
  For complete SDK documentation including all available methods and configuration options, see the [JavaScript SDK Reference](../sdks/javascript-sdk).
</Info>

## Pages

<Tabs>
  <Tab title="ES Modules">
    ```javascript theme={null}
    // 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);
    ```
  </Tab>

  <Tab title="CommonJS">
    ```javascript theme={null}
    // 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 };
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    // 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;
    }
    ```
  </Tab>
</Tabs>

## Collections

<Tabs>
  <Tab title="ES Modules">
    ```javascript theme={null}
    // 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);
    ```
  </Tab>

  <Tab title="CommonJS">
    ```javascript theme={null}
    // 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 };
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    // 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;
    }
    ```
  </Tab>
</Tabs>

## Dynamic components

### Component Renderer

```javascript theme={null}
// 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

```javascript theme={null}
// 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

<Info>
  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.
</Info>

<Tabs>
  <Tab title="ES Modules">
    ```javascript theme={null}
    // 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
    ```
  </Tab>

  <Tab title="CommonJS">
    ```javascript theme={null}
    // 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 };
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    // 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;
      }
    }
    ```
  </Tab>
</Tabs>

## Blog

<Tabs>
  <Tab title="Blog List">
    <Tabs>
      <Tab title="ES Modules">
        ```javascript theme={null}
        // 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 });
        }
        ```
      </Tab>

      <Tab title="CommonJS">
        ```javascript theme={null}
        // 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 };
        ```
      </Tab>

      <Tab title="TypeScript">
        ```typescript theme={null}
        // 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
          };
        }
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Blog Post">
    <Tabs>
      <Tab title="ES Modules">
        ```javascript theme={null}
        // 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}`);
        }
        ```
      </Tab>

      <Tab title="CommonJS">
        ```javascript theme={null}
        // 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 };
        ```
      </Tab>

      <Tab title="TypeScript">
        ```typescript theme={null}
        // 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;
          }
        }
        ```
      </Tab>
    </Tabs>
  </Tab>
</Tabs>

## Preview Mode

```javascript theme={null}
// 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

```javascript theme={null}
// 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

```javascript theme={null}
// 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

```javascript theme={null}
// 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

```javascript theme={null}
// 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

<CardGroup cols={2}>
  <Card title="Node.js Starter" icon="rocket" href="../starter-projects/nodejs">
    Pre-configured starter project
  </Card>

  <Card title="JavaScript SDK" icon="js" href="../sdks/javascript-sdk">
    Complete SDK reference
  </Card>

  <Card title="Express.js Guide" icon="node" href="./expressjs">
    Express.js integration
  </Card>

  <Card title="Webhooks" icon="bell" href="../../api/concepts/webhooks-overview">
    Set up content webhooks
  </Card>
</CardGroup>
