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.

Go Starter Project

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

Installation

go get github.com/buttercms/buttercms-go
Set your API token as an environment variable:
export BUTTERCMS_API_TOKEN=your_api_token

Initialize the Client

// main.go
package main

import (
    "os"
    "github.com/buttercms/buttercms-go"
)

func init() {
    ButterCMS.SetAuthToken(os.Getenv("BUTTERCMS_API_TOKEN"))
}
For complete SDK documentation including all available methods and configuration options, see the Go SDK Reference.

Pages

Create a Page Type:
  1. Go to Content TypesPage Types → Click +
  2. Add fields to your schema:
    • Short Text field named headline
    • Long Text field named subheadline
    • Media field named hero_image
    • WYSIWYG field named body
  3. Click Create Page Type and name it Landing Page (slug: landing_page)
Create a Page:
  1. Go to PagesNew Page → Select Landing Page
  2. Enter a title (e.g., “Home”) which generates slug home
  3. Fill in your content and click Publish
// handlers/pages.go
package handlers

import (
    "html/template"
    "net/http"
    "github.com/buttercms/buttercms-go"
)

var templates = template.Must(template.ParseGlob("templates/*.html"))

func LandingPageHandler(w http.ResponseWriter, r *http.Request) {
    slug := r.URL.Query().Get("slug")
    if slug == "" {
        slug = "home"
    }

    params := map[string]string{
        "locale": "en",
    }

    page, err := ButterCMS.GetPage("landing_page", slug, params)
    if err != nil {
        http.Error(w, "Page not found", http.StatusNotFound)
        return
    }

    templates.ExecuteTemplate(w, "landing.html", page)
}
// main.go
func main() {
    http.HandleFunc("/", handlers.LandingPageHandler)
    http.HandleFunc("/page/", handlers.LandingPageHandler)
    http.ListenAndServe(":8080", nil)
}
<!-- templates/landing.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{{.Data.Fields.headline}}</title>
</head>
<body>
    <main>
        <h1>{{.Data.Fields.headline}}</h1>
        <p>{{.Data.Fields.subheadline}}</p>
        {{if .Data.Fields.hero_image}}
        <img src="{{.Data.Fields.hero_image}}" alt="{{.Data.Fields.headline}}" />
        {{end}}
        {{.Data.Fields.body | safeHTML}}
    </main>
</body>
</html>

Collections

Create a Collection:
  1. Go to Content TypesCollections → Click +
  2. Add fields to your schema:
    • Short Text field named name
    • Media field named logo
    • WYSIWYG field named description
  3. Click Create Collection and name it Brands (key: brands)
Add Collection Items:
  1. After saving, the create item screen loads automatically
  2. Fill in your brand details and click Save
  3. Add more items via Collections → hover Brands → click +
// handlers/brands.go
func BrandsHandler(w http.ResponseWriter, r *http.Request) {
    content, err := ButterCMS.GetContentFields([]string{"brands"}, nil)
    if err != nil {
        http.Error(w, "Error fetching brands", http.StatusInternalServerError)
        return
    }

    templates.ExecuteTemplate(w, "brands.html", content)
}
<!-- templates/brands.html -->
<!DOCTYPE html>
<html>
<body>
    <main>
        <h1>Our Brands</h1>
        <ul>
            {{range .Data.brands}}
            <li>
                <img src="{{.logo}}" alt="{{.name}}" />
                <h2>{{.name}}</h2>
                {{.description | safeHTML}}
            </li>
            {{end}}
        </ul>
    </main>
</body>
</html>

Dynamic Components

Create Components:
  1. Go to Content TypesComponents → Click +
  2. Create a Hero component with fields:
    • headline (Short Text)
    • subheadline (Long Text)
    • image (Media)
    • button_label (Short Text)
    • button_url (Short Text)
  3. Create additional components (Features, Testimonials, CTA, etc.)
Add Component Picker to Page Type:
  1. Edit your Page Type schema
  2. Add a Component field named body
  3. Select which components can be used in this field
  4. Content editors can now drag-and-drop components when creating pages

Component Renderer

// components/renderer.go
package components

import (
    "bytes"
    "html/template"
)

var componentTemplates = template.Must(template.ParseGlob("templates/components/*.html"))

type Component struct {
    Type   string                 `json:"type"`
    Fields map[string]interface{} `json:"fields"`
}

func RenderComponents(components []Component) template.HTML {
    var buf bytes.Buffer

    for _, comp := range components {
        templateName := comp.Type + ".html"
        if err := componentTemplates.ExecuteTemplate(&buf, templateName, comp.Fields); err != nil {
            continue // Skip unknown components
        }
    }

    return template.HTML(buf.String())
}

Component Template

<!-- templates/components/hero.html -->
<section class="hero">
    <h1>{{.headline}}</h1>
    <p>{{.subheadline}}</p>
    {{if .button_label}}
    <a href="{{.button_url}}" class="btn">{{.button_label}}</a>
    {{end}}
    {{if .image}}
    <img src="{{.image}}" alt="{{.headline}}" />
    {{end}}
</section>

Using in Handlers

// handlers/component_page.go
func ComponentPageHandler(w http.ResponseWriter, r *http.Request) {
    slug := r.URL.Path[len("/landing/"):]

    page, err := ButterCMS.GetPage("component_page", slug, nil)
    if err != nil {
        http.Error(w, "Page not found", http.StatusNotFound)
        return
    }

    // Extract components from page body
    bodyComponents := page.Data.Fields["body"].([]interface{})
    var comps []components.Component
    for _, c := range bodyComponents {
        comp := c.(map[string]interface{})
        comps = append(comps, components.Component{
            Type:   comp["type"].(string),
            Fields: comp["fields"].(map[string]interface{}),
        })
    }

    data := map[string]interface{}{
        "page":       page,
        "components": components.RenderComponents(comps),
    }

    templates.ExecuteTemplate(w, "component-page.html", data)
}

Blog

ButterCMS includes a pre-built blog engine with posts, authors, categories, and tags.Create a Blog Post:
  1. Go to Blog PostsNew Post
  2. Enter a title, body content using the rich text editor
  3. Optionally add a featured image, categories, tags, and author
  4. Click Publish
The blog engine automatically provides:
  • Post slugs, summaries, and SEO fields
  • Author profiles with bio and social links
  • Categories and tags for organization
  • RSS and Atom feeds
// handlers/blog.go
func BlogListHandler(w http.ResponseWriter, r *http.Request) {
    page := r.URL.Query().Get("page")
    if page == "" {
        page = "1"
    }

    params := map[string]string{
        "page":      page,
        "page_size": "10",
    }

    posts, err := ButterCMS.GetPosts(params)
    if err != nil {
        http.Error(w, "Error fetching posts", http.StatusInternalServerError)
        return
    }

    templates.ExecuteTemplate(w, "blog-list.html", posts)
}
<!-- templates/blog-list.html -->
<!DOCTYPE html>
<html>
<body>
    <h1>Blog</h1>
    <ul>
        {{range .Data}}
        <li>
            <h2><a href="/blog/{{.Slug}}">{{.Title}}</a></h2>
            <p>{{.Summary}}</p>
            <span>By {{.Author.FirstName}} {{.Author.LastName}}</span>
        </li>
        {{end}}
    </ul>
    {{if .Meta.NextPage}}
    <a href="/blog?page={{.Meta.NextPage}}">Next Page</a>
    {{end}}
</body>
</html>

Caching

Use an in-memory cache for better performance:
// cache/cache.go
package cache

import (
    "sync"
    "time"
)

type CacheEntry struct {
    Data      interface{}
    ExpiresAt time.Time
}

type Cache struct {
    entries map[string]CacheEntry
    mu      sync.RWMutex
    ttl     time.Duration
}

func New(ttl time.Duration) *Cache {
    return &Cache{
        entries: make(map[string]CacheEntry),
        ttl:     ttl,
    }
}

func (c *Cache) Get(key string) (interface{}, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()

    entry, ok := c.entries[key]
    if !ok || time.Now().After(entry.ExpiresAt) {
        return nil, false
    }
    return entry.Data, true
}

func (c *Cache) Set(key string, data interface{}) {
    c.mu.Lock()
    defer c.mu.Unlock()

    c.entries[key] = CacheEntry{
        Data:      data,
        ExpiresAt: time.Now().Add(c.ttl),
    }
}

func (c *Cache) Invalidate(pattern string) {
    c.mu.Lock()
    defer c.mu.Unlock()

    for key := range c.entries {
        if strings.Contains(key, pattern) {
            delete(c.entries, key)
        }
    }
}

SEO

// handlers/pages.go
type SEOData struct {
    Title       string
    Description string
    OGTitle     string
    OGImage     string
}

func extractSEO(page *ButterCMS.PageResponse) SEOData {
    fields := page.Data.Fields
    seo := fields["seo"].(map[string]interface{})

    return SEOData{
        Title:       getStringOr(seo, "title", fields["headline"].(string)),
        Description: getStringOr(seo, "description", ""),
        OGTitle:     getStringOr(seo, "og_title", getStringOr(seo, "title", "")),
        OGImage:     getStringOr(seo, "og_image", ""),
    }
}

func getStringOr(m map[string]interface{}, key, fallback string) string {
    if v, ok := m[key].(string); ok {
        return v
    }
    return fallback
}

Resources

Go Starter

Pre-configured starter project

Go SDK

Complete SDK reference

GitHub Repository

View source code

Content API

REST API documentation