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.

Django Starter Project

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

Installation

pip install buttercms-python
Add your API token to your environment or settings.py:
# settings.py
import os

BUTTERCMS_API_TOKEN = os.environ.get('BUTTERCMS_API_TOKEN', 'your_api_token')

Initialize the client

Create a reusable client instance:
# myapp/buttercms_client.py
from butter_cms import ButterCMS
from django.conf import settings

client = ButterCMS(settings.BUTTERCMS_API_TOKEN)
For complete SDK documentation including all available methods and configuration options, see the Python SDK Reference.

Pages

# myapp/views.py
from django.shortcuts import render
from django.http import Http404
from .buttercms_client import client

def landing_page(request, slug='home'):
    try:
        response = client.pages.get('landing-page', slug)
        page = response['data']
    except:
        raise Http404("Page not found")

    return render(request, 'landing.html', {'page': page})
# myapp/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.landing_page, name='home'),
    path('page/<slug:slug>/', views.landing_page, name='landing_page'),
]
<!-- templates/landing.html -->
{% extends 'base.html' %}

{% block content %}
<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 }}" />
    {% endif %}
    {{ page.fields.body|safe }}
</main>
{% endblock %}

Collections

# myapp/views.py
from django.shortcuts import render
from .buttercms_client import client

def brands(request):
    response = client.content_fields.get(['brands'])
    brands = response['data']['brands']
    return render(request, 'brands.html', {'brands': brands})
# myapp/urls.py
urlpatterns = [
    path('brands/', views.brands, name='brands'),
]
<!-- templates/brands.html -->
{% extends 'base.html' %}

{% block content %}
<main>
    <h1>Our Brands</h1>
    <ul>
        {% for brand in brands %}
        <li>
            <img src="{{ brand.logo }}" alt="{{ brand.name }}" />
            <h2>{{ brand.name }}</h2>
            {{ brand.description|safe }}
        </li>
        {% endfor %}
    </ul>
</main>
{% endblock %}

Dynamic components

Component renderer

# myapp/components.py
from django.template.loader import render_to_string

COMPONENT_TEMPLATES = {
    'hero': 'components/hero.html',
    'features': 'components/features.html',
    'testimonials': 'components/testimonials.html',
    'cta': 'components/cta.html',
}

def render_components(components):
    """Render a list of ButterCMS components to HTML."""
    html_parts = []
    for component in components:
        template = COMPONENT_TEMPLATES.get(component['type'])
        if template:
            html = render_to_string(template, {'fields': component['fields']})
            html_parts.append(html)
    return ''.join(html_parts)

Example component template

<!-- templates/components/hero.html -->
<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>
    {% endif %}
    {% if fields.image %}
    <img src="{{ fields.image }}" alt="{{ fields.headline }}" />
    {% endif %}
</section>

Using in views

# myapp/views.py
from django.shortcuts import render
from django.http import Http404
from .buttercms_client import client
from .components import render_components

def component_page(request, slug):
    try:
        response = client.pages.get('landing-page', slug)
        page = response['data']
    except:
        raise Http404("Page not found")

    # Render components to HTML
    components_html = render_components(page['fields'].get('body', []))

    return render(request, 'component_page.html', {
        'page': page,
        'components_html': components_html,
    })
<!-- templates/component_page.html -->
{% extends 'base.html' %}

{% block content %}
<main>
    {{ components_html|safe }}
</main>
{% endblock %}

Blog

# myapp/views.py
from django.shortcuts import render
from .buttercms_client import client

def blog_list(request):
    page = request.GET.get('page', 1)
    response = client.posts.all({'page': page, 'page_size': 10})
    return render(request, 'blog/list.html', {
        'posts': response['data'],
        'meta': response['meta'],
    })
# myapp/urls.py
urlpatterns = [
    path('blog/', views.blog_list, name='blog_list'),
]
<!-- templates/blog/list.html -->
{% extends 'base.html' %}

{% block content %}
<main>
    <h1>Blog</h1>
    <ul>
        {% for post in posts %}
        <li>
            <h2><a href="{% url 'blog_post' post.slug %}">{{ post.title }}</a></h2>
            {{ post.summary|safe }}
            <span>By {{ post.author.first_name }} {{ post.author.last_name }}</span>
        </li>
        {% endfor %}
    </ul>
    {% if meta.next_page %}
    <a href="?page={{ meta.next_page }}">Next Page</a>
    {% endif %}
</main>
{% endblock %}

Caching

Use Django’s cache framework for better performance:
# myapp/views.py
from django.core.cache import cache
from django.shortcuts import render
from .buttercms_client import client

def cached_landing_page(request, slug='home'):
    cache_key = f'butter_page_landing-page_{slug}'
    page = cache.get(cache_key)

    if not page:
        try:
            response = client.pages.get('landing-page', slug)
            page = response['data']
            cache.set(cache_key, page, 3600)  # Cache for 1 hour
        except:
            raise Http404("Page not found")

    return render(request, 'landing.html', {'page': page})

Webhook cache invalidation

# myapp/views.py
from django.core.cache import cache
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
import json

@csrf_exempt
@require_POST
def buttercms_webhook(request):
    payload = json.loads(request.body)
    webhook_type = payload.get('webhook_type', '')

    if 'published' in webhook_type or 'updated' in webhook_type or 'deleted' in webhook_type:
        # Clear all ButterCMS cache keys
        cache.delete_many(cache.keys('butter_*'))

    return JsonResponse({'status': 'ok'})

SEO

# myapp/context_processors.py
def seo_context(request):
    """Add SEO fields from ButterCMS page to context."""
    return {}

# myapp/views.py
def landing_page_with_seo(request, slug='home'):
    try:
        response = client.pages.get('landing-page', slug)
        page = response['data']
    except:
        raise Http404("Page not found")

    seo = page['fields'].get('seo', {})

    return render(request, 'landing.html', {
        'page': page,
        'seo_title': seo.get('title', page['fields'].get('headline', '')),
        'seo_description': seo.get('description', ''),
        'og_title': seo.get('og_title', seo.get('title', '')),
        'og_description': seo.get('og_description', seo.get('description', '')),
        'og_image': seo.get('og_image', ''),
    })
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ seo_title|default:"My Site" }}</title>
    <meta name="description" content="{{ seo_description }}">

    <!-- Open Graph -->
    <meta property="og:title" content="{{ og_title|default:seo_title }}">
    <meta property="og:description" content="{{ og_description|default:seo_description }}">
    {% if og_image %}
    <meta property="og:image" content="{{ og_image }}">
    {% endif %}
</head>
<body>
    {% block content %}{% endblock %}
</body>
</html>

Resources

Django Starter

Pre-configured starter project

Python SDK

Complete SDK reference

GitHub Repository

View source code

Content API

REST API documentation