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.Gatsby Starter Project
Hit the ground running with a pre-configured Gatsby + ButterCMS setup.
Installation
- npm
- yarn
- pnpm
npm install buttercms gatsby-source-buttercms
yarn add buttercms gatsby-source-buttercms
pnpm add buttercms gatsby-source-buttercms
.env:
BUTTERCMS_API_TOKEN=your_api_token
Configuration
Add the source plugin to your Gatsby config:- JavaScript (gatsby-config.js)
- TypeScript (gatsby-config.ts)
// gatsby-config.js
require('dotenv').config();
module.exports = {
plugins: [
{
resolve: 'gatsby-source-buttercms',
options: {
authToken: process.env.BUTTERCMS_API_TOKEN,
contentFields: {
keys: ['navigation', 'footer', 'brands'],
},
pageTypes: ['landing-page'],
locales: ['en'],
},
},
],
};
// gatsby-config.ts
import type { GatsbyConfig } from 'gatsby';
import dotenv from 'dotenv';
dotenv.config();
const config: GatsbyConfig = {
plugins: [
{
resolve: 'gatsby-source-buttercms',
options: {
authToken: process.env.BUTTERCMS_API_TOKEN,
contentFields: {
keys: ['navigation', 'footer', 'brands'],
},
pageTypes: ['landing-page'],
locales: ['en'],
},
},
],
};
export default config;
For complete SDK documentation including all available methods and configuration options, see the JavaScript SDK Reference.
Pages
- JavaScript
- TypeScript
Create pages dynamically in
gatsby-node.js:// gatsby-node.js
exports.createPages = async ({ actions, graphql }) => {
const { createPage } = actions;
const { data } = await graphql(`
{
allButterPage(filter: { page_type: { eq: "landing-page" } }) {
nodes {
slug
}
}
}
`);
data.allButterPage.nodes.forEach((page) => {
createPage({
path: page.slug === 'home' ? '/' : `/${page.slug}`,
component: require.resolve('./src/templates/landing-page.js'),
context: { slug: page.slug },
});
});
};
// src/templates/landing-page.js
import React from 'react';
import { graphql } from 'gatsby';
export default function LandingPage({ data }) {
const page = data.butterPage;
return (
<main>
<h1>{page.headline}</h1>
<p>{page.subheadline}</p>
{page.hero_image && (
<img src={page.hero_image} alt={page.headline} />
)}
<div dangerouslySetInnerHTML={{ __html: page.body }} />
</main>
);
}
export const query = graphql`
query($slug: String!) {
butterPage(slug: { eq: $slug }, page_type: { eq: "landing-page" }) {
slug
headline
subheadline
hero_image
body
}
}
`;
Create pages dynamically in
gatsby-node.ts:// gatsby-node.ts
import type { GatsbyNode } from 'gatsby';
import path from 'path';
export const createPages: GatsbyNode['createPages'] = async ({ actions, graphql }) => {
const { createPage } = actions;
const result = await graphql<{
allButterPage: { nodes: Array<{ slug: string }> };
}>(`
{
allButterPage(filter: { page_type: { eq: "landing-page" } }) {
nodes {
slug
}
}
}
`);
result.data?.allButterPage.nodes.forEach((page) => {
createPage({
path: page.slug === 'home' ? '/' : `/${page.slug}`,
component: path.resolve('./src/templates/landing-page.tsx'),
context: { slug: page.slug },
});
});
};
// src/templates/landing-page.tsx
import React from 'react';
import { graphql, PageProps } from 'gatsby';
interface PageData {
butterPage: {
slug: string;
headline: string;
subheadline: string;
hero_image: string;
body: string;
};
}
export default function LandingPage({ data }: PageProps<PageData>) {
const page = data.butterPage;
return (
<main>
<h1>{page.headline}</h1>
<p>{page.subheadline}</p>
{page.hero_image && (
<img src={page.hero_image} alt={page.headline} />
)}
<div dangerouslySetInnerHTML={{ __html: page.body }} />
</main>
);
}
export const query = graphql`
query($slug: String!) {
butterPage(slug: { eq: $slug }, page_type: { eq: "landing-page" }) {
slug
headline
subheadline
hero_image
body
}
}
`;
Collections
- JavaScript
- TypeScript
// src/pages/brands.js
import React from 'react';
import { graphql } from 'gatsby';
export default function BrandsPage({ data }) {
const brands = data.butterCollection.value;
return (
<main>
<h1>Our Brands</h1>
<ul>
{brands.map((brand, index) => (
<li key={index}>
<img src={brand.logo} alt={brand.name} />
<h2>{brand.name}</h2>
<div dangerouslySetInnerHTML={{ __html: brand.description }} />
</li>
))}
</ul>
</main>
);
}
export const query = graphql`
{
butterCollection(key: { eq: "brands" }) {
value {
name
logo
description
}
}
}
`;
// src/pages/brands.tsx
import React from 'react';
import { graphql, PageProps } from 'gatsby';
interface Brand {
name: string;
logo: string;
description: string;
}
interface PageData {
butterCollection: {
value: Brand[];
};
}
export default function BrandsPage({ data }: PageProps<PageData>) {
const brands = data.butterCollection.value;
return (
<main>
<h1>Our Brands</h1>
<ul>
{brands.map((brand, index) => (
<li key={index}>
<img src={brand.logo} alt={brand.name} />
<h2>{brand.name}</h2>
<div dangerouslySetInnerHTML={{ __html: brand.description }} />
</li>
))}
</ul>
</main>
);
}
export const query = graphql`
{
butterCollection(key: { eq: "brands" }) {
value {
name
logo
description
}
}
}
`;
Dynamic components
Component Renderer
// src/components/ComponentRenderer.js
import React from 'react';
import Hero from './Hero';
import Features from './Features';
import Testimonials from './Testimonials';
import CTA from './CTA';
const componentMap = {
hero: Hero,
features: Features,
testimonials: Testimonials,
cta: CTA,
};
export default function ComponentRenderer({ components }) {
return (
<>
{components.map((component, index) => {
const Component = componentMap[component.type];
if (!Component) return null;
return <Component key={index} {...component.fields} />;
})}
</>
);
}
Example Component
// src/components/Hero.js
import React from 'react';
export default function Hero({ headline, subheadline, image, button_label, button_url }) {
return (
<section className="hero">
<h1>{headline}</h1>
<p>{subheadline}</p>
{button_label && <a href={button_url} className="btn">{button_label}</a>}
{image && <img src={image} alt={headline} />}
</section>
);
}
Using in templates
// src/templates/component-page.js
import React from 'react';
import { graphql } from 'gatsby';
import ComponentRenderer from '../components/ComponentRenderer';
export default function ComponentPage({ data }) {
const page = data.butterPage;
return (
<main>
<ComponentRenderer components={page.body} />
</main>
);
}
export const query = graphql`
query($slug: String!) {
butterPage(slug: { eq: $slug }, page_type: { eq: "landing-page" }) {
slug
body {
type
fields {
headline
subheadline
image
button_label
button_url
}
}
}
}
`;
Blog
- Blog Post List
- Single Blog Post
- JavaScript
- TypeScript
Add to
gatsby-node.js:// gatsby-node.js (add to createPages)
createPage({
path: '/blog',
component: require.resolve('./src/templates/blog-list.js'),
context: {},
});
// src/templates/blog-list.js
import React from 'react';
import { graphql, Link } from 'gatsby';
export default function BlogList({ data }) {
const posts = data.allButterPost.nodes;
return (
<main>
<h1>Blog</h1>
<ul>
{posts.map((post) => (
<li key={post.slug}>
<h2><Link to={`/blog/${post.slug}`}>{post.title}</Link></h2>
<p dangerouslySetInnerHTML={{ __html: post.summary }} />
<span>By {post.author.first_name} {post.author.last_name}</span>
</li>
))}
</ul>
</main>
);
}
export const query = graphql`
{
allButterPost(sort: { published: DESC }) {
nodes {
slug
title
summary
published
author {
first_name
last_name
}
}
}
}
`;
// src/templates/blog-list.tsx
import React from 'react';
import { graphql, Link, PageProps } from 'gatsby';
interface Post {
slug: string;
title: string;
summary: string;
published: string;
author: {
first_name: string;
last_name: string;
};
}
interface PageData {
allButterPost: {
nodes: Post[];
};
}
export default function BlogList({ data }: PageProps<PageData>) {
const posts = data.allButterPost.nodes;
return (
<main>
<h1>Blog</h1>
<ul>
{posts.map((post) => (
<li key={post.slug}>
<h2><Link to={`/blog/${post.slug}`}>{post.title}</Link></h2>
<p dangerouslySetInnerHTML={{ __html: post.summary }} />
<span>By {post.author.first_name} {post.author.last_name}</span>
</li>
))}
</ul>
</main>
);
}
export const query = graphql`
{
allButterPost(sort: { published: DESC }) {
nodes {
slug
title
summary
published
author {
first_name
last_name
}
}
}
}
`;
- JavaScript
- TypeScript
Add to
gatsby-node.js:// gatsby-node.js (add to createPages)
const { data: blogData } = await graphql(`
{
allButterPost {
nodes {
slug
}
}
}
`);
blogData.allButterPost.nodes.forEach((post) => {
createPage({
path: `/blog/${post.slug}`,
component: require.resolve('./src/templates/blog-post.js'),
context: { slug: post.slug },
});
});
// src/templates/blog-post.js
import React from 'react';
import { graphql, Link } from 'gatsby';
export default function BlogPost({ data }) {
const post = data.butterPost;
return (
<article>
<h1>{post.title}</h1>
<p>
By {post.author.first_name} {post.author.last_name} on{' '}
{new Date(post.published).toLocaleDateString()}
</p>
{post.featured_image && (
<img src={post.featured_image} alt={post.title} />
)}
<div dangerouslySetInnerHTML={{ __html: post.body }} />
<Link to="/blog">Back to Posts</Link>
</article>
);
}
export const query = graphql`
query($slug: String!) {
butterPost(slug: { eq: $slug }) {
slug
title
body
published
featured_image
author {
first_name
last_name
}
}
}
`;
// src/templates/blog-post.tsx
import React from 'react';
import { graphql, Link, PageProps } from 'gatsby';
interface PageData {
butterPost: {
slug: string;
title: string;
body: string;
published: string;
featured_image: string;
author: {
first_name: string;
last_name: string;
};
};
}
export default function BlogPost({ data }: PageProps<PageData>) {
const post = data.butterPost;
return (
<article>
<h1>{post.title}</h1>
<p>
By {post.author.first_name} {post.author.last_name} on{' '}
{new Date(post.published).toLocaleDateString()}
</p>
{post.featured_image && (
<img src={post.featured_image} alt={post.title} />
)}
<div dangerouslySetInnerHTML={{ __html: post.body }} />
<Link to="/blog">Back to Posts</Link>
</article>
);
}
export const query = graphql`
query($slug: String!) {
butterPost(slug: { eq: $slug }) {
slug
title
body
published
featured_image
author {
first_name
last_name
}
}
}
`;
Preview Mode
Enable content editors to preview draft content before publishing:// gatsby-config.js
module.exports = {
plugins: [
{
resolve: 'gatsby-source-buttercms',
options: {
authToken: process.env.BUTTERCMS_API_TOKEN,
preview: process.env.GATSBY_PREVIEW === 'true',
// ...other options
},
},
],
};
GATSBY_PREVIEW=true in the preview environment.
SEO
// src/templates/landing-page.js
import React from 'react';
import { graphql } from 'gatsby';
export default function LandingPage({ data }) {
const page = data.butterPage;
const seo = page.seo || {};
return (
<>
<title>{seo.title || page.headline}</title>
<meta name="description" content={seo.description} />
<meta property="og:title" content={seo.og_title || seo.title || page.headline} />
<meta property="og:description" content={seo.og_description || seo.description} />
{seo.og_image && <meta property="og:image" content={seo.og_image} />}
<main>
<h1>{page.headline}</h1>
<div dangerouslySetInnerHTML={{ __html: page.body }} />
</main>
</>
);
}
export const query = graphql`
query($slug: String!) {
butterPage(slug: { eq: $slug }, page_type: { eq: "landing-page" }) {
slug
headline
body
seo {
title
description
og_title
og_description
og_image
}
}
}
`;
Resources
Gatsby Starter
Pre-configured starter project
JavaScript SDK
Complete SDK reference
GitHub Repository
View source code
Content API
REST API documentation