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.

Laravel Starter Project

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

Installation

composer require buttercms/buttercms-php
Add your API token to .env:
BUTTERCMS_API_TOKEN=your_api_token
Add configuration to config/services.php:
// config/services.php
'buttercms' => [
    'api_token' => env('BUTTERCMS_API_TOKEN'),
],

Initialize the client

// app/Services/ButterCMS.php
<?php

namespace App\Services;

use ButterCMS\ButterCMS as ButterClient;

class ButterCMS
{
    protected ButterClient $client;

    public function __construct()
    {
        $this->client = new ButterClient(config('services.buttercms.api_token'));
    }

    public function getPage(string $pageType, string $slug, array $params = [])
    {
        return $this->client->fetchPage($pageType, $slug, $params);
    }

    public function getPages(string $pageType, array $params = [])
    {
        return $this->client->fetchPages($pageType, $params);
    }

    public function getCollection(array $keys, array $params = [])
    {
        return $this->client->fetchContentFields($keys, $params);
    }

    public function getPosts(array $params = [])
    {
        return $this->client->fetchPosts($params);
    }

    public function getPost(string $slug)
    {
        return $this->client->fetchPost($slug);
    }

    public function searchPosts(string $query, array $params = [])
    {
        return $this->client->searchPosts($query, $params);
    }

    public function getCategories(array $params = [])
    {
        return $this->client->fetchCategories($params);
    }

    public function getTags(array $params = [])
    {
        return $this->client->fetchTags($params);
    }
}
Register in AppServiceProvider:
// app/Providers/AppServiceProvider.php
public function register()
{
    $this->app->singleton(\App\Services\ButterCMS::class, function ($app) {
        return new \App\Services\ButterCMS();
    });
}
For complete SDK documentation including all available methods and configuration options, see the PHP SDK Reference.

Pages

// app/Http/Controllers/PageController.php
<?php

namespace App\Http\Controllers;

use App\Services\ButterCMS;
use Illuminate\Http\Request;

class PageController extends Controller
{
    public function __construct(
        protected ButterCMS $butter
    ) {}

    public function show(string $slug = 'home')
    {
        try {
            $response = $this->butter->getPage('landing-page', $slug, [
                'locale' => app()->getLocale(),
            ]);
            $page = $response->getData();
        } catch (\Exception $e) {
            abort(404);
        }

        return view('pages.landing', compact('page'));
    }

    public function list()
    {
        $response = $this->butter->getPages('landing-page');
        $pages = $response->getData();

        return view('pages.list', compact('pages'));
    }
}
{{-- resources/views/pages/landing.blade.php --}}
@extends('layouts.app')

@section('title', $page->fields->seo->title ?? $page->fields->headline)

@section('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 !!}
</main>
@endsection

Collections

// app/Http/Controllers/BrandController.php
<?php

namespace App\Http\Controllers;

use App\Services\ButterCMS;

class BrandController extends Controller
{
    public function __construct(
        protected ButterCMS $butter
    ) {}

    public function index()
    {
        $response = $this->butter->getCollection(['brands']);
        $brands = $response->getData()->brands;

        return view('brands.index', compact('brands'));
    }
}
{{-- resources/views/brands/index.blade.php --}}
@extends('layouts.app')

@section('content')
<main>
    <h1>Our Brands</h1>
    <ul>
        @foreach($brands as $brand)
        <li>
            <img src="{{ $brand->logo }}" alt="{{ $brand->name }}" />
            <h2>{{ $brand->name }}</h2>
            {!! $brand->description !!}
        </li>
        @endforeach
    </ul>
</main>
@endsection

Dynamic components

Component renderer

// app/View/Components/ButterComponent.php
<?php

namespace App\View\Components;

use Illuminate\View\Component;

class ButterComponent extends Component
{
    public function __construct(
        public object $component
    ) {}

    public function render()
    {
        $viewName = 'components.butter.' . $this->component->type;

        if (!view()->exists($viewName)) {
            return '';
        }

        return view($viewName, ['fields' => $this->component->fields]);
    }
}

Example component

{{-- resources/views/components/butter/hero.blade.php --}}
<section class="hero">
    <h1>{{ $fields->headline }}</h1>
    <p>{{ $fields->subheadline }}</p>

    @if($fields->button_label ?? null)
    <a href="{{ $fields->button_url }}" class="btn">{{ $fields->button_label }}</a>
    @endif

    @if($fields->image ?? null)
    <img src="{{ $fields->image }}" alt="{{ $fields->headline }}" />
    @endif
</section>
{{-- resources/views/components/butter/features.blade.php --}}
<section class="features">
    <h2>{{ $fields->headline }}</h2>
    <div class="features-grid">
        @foreach($fields->items as $item)
        <div class="feature">
            @if($item->icon ?? null)
            <img src="{{ $item->icon }}" alt="{{ $item->title }}" />
            @endif
            <h3>{{ $item->title }}</h3>
            <p>{{ $item->description }}</p>
        </div>
        @endforeach
    </div>
</section>
{{-- resources/views/components/butter/cta.blade.php --}}
<section class="cta">
    <h2>{{ $fields->headline }}</h2>
    <p>{{ $fields->subheadline }}</p>
    <a href="{{ $fields->button_url }}" class="btn">{{ $fields->button_label }}</a>
</section>

Using in pages

// app/Http/Controllers/ComponentPageController.php
<?php

namespace App\Http\Controllers;

use App\Services\ButterCMS;

class ComponentPageController extends Controller
{
    public function __construct(
        protected ButterCMS $butter
    ) {}

    public function show(string $slug)
    {
        try {
            $response = $this->butter->getPage('landing-page', $slug);
            $page = $response->getData();
        } catch (\Exception $e) {
            abort(404);
        }

        $components = $page->fields->body ?? [];

        return view('pages.component-page', compact('page', 'components'));
    }
}
{{-- resources/views/pages/component-page.blade.php --}}
@extends('layouts.app')

@section('title', $page->fields->seo->title ?? 'Page')

@section('content')
<main>
    @foreach($components as $component)
        <x-butter-component :component="$component" />
    @endforeach
</main>
@endsection

Blog

// app/Http/Controllers/BlogController.php
<?php

namespace App\Http\Controllers;

use App\Services\ButterCMS;
use Illuminate\Http\Request;

class BlogController extends Controller
{
    public function __construct(
        protected ButterCMS $butter
    ) {}

    public function index(Request $request)
    {
        $page = $request->get('page', 1);
        $response = $this->butter->getPosts([
            'page' => $page,
            'page_size' => 10,
        ]);

        $posts = $response->getData();
        $meta = $response->getMeta();

        return view('blog.index', compact('posts', 'meta'));
    }

    public function category(string $slug, Request $request)
    {
        $page = $request->get('page', 1);
        $response = $this->butter->getPosts([
            'category_slug' => $slug,
            'page' => $page,
            'page_size' => 10,
        ]);

        $posts = $response->getData();
        $meta = $response->getMeta();

        return view('blog.index', compact('posts', 'meta'));
    }
}
{{-- resources/views/blog/index.blade.php --}}
@extends('layouts.app')

@section('content')
<main>
    <h1>Blog</h1>
    <ul>
        @foreach($posts as $post)
        <li>
            <h2><a href="{{ route('blog.show', $post->slug) }}">{{ $post->title }}</a></h2>
            <p class="meta">
                By {{ $post->author->first_name }} {{ $post->author->last_name }}
                on {{ \Carbon\Carbon::parse($post->published)->format('F j, Y') }}
            </p>
            <p>{{ $post->summary }}</p>
        </li>
        @endforeach
    </ul>

    @if($meta->next_page)
    <a href="{{ route('blog.index', ['page' => $meta->next_page]) }}">Next Page</a>
    @endif
</main>
@endsection

Routes

// routes/web.php
use App\Http\Controllers\PageController;
use App\Http\Controllers\BlogController;
use App\Http\Controllers\BrandController;
use App\Http\Controllers\ComponentPageController;

// Pages
Route::get('/', [PageController::class, 'show'])->name('home');
Route::get('/page/{slug}', [PageController::class, 'show'])->name('page.show');
Route::get('/landing/{slug}', [ComponentPageController::class, 'show'])->name('landing.show');

// Collections
Route::get('/brands', [BrandController::class, 'index'])->name('brands.index');

// Blog
Route::get('/blog', [BlogController::class, 'index'])->name('blog.index');
Route::get('/blog/category/{slug}', [BlogController::class, 'category'])->name('blog.category');
Route::get('/blog/{slug}', [BlogController::class, 'show'])->name('blog.show');

Preview mode

// app/Http/Controllers/PageController.php
public function show(string $slug = 'home', Request $request)
{
    $preview = $request->get('preview') === 'true';

    try {
        $params = ['locale' => app()->getLocale()];
        if ($preview) {
            $params['preview'] = 1;
        }

        $response = $this->butter->getPage('landing-page', $slug, $params);
        $page = $response->getData();
    } catch (\Exception $e) {
        abort(404);
    }

    return view('pages.landing', compact('page', 'preview'));
}

Caching

// app/Services/ButterCMS.php
use Illuminate\Support\Facades\Cache;

public function getPage(string $pageType, string $slug, array $params = [])
{
    $cacheKey = "butter_page_{$pageType}_{$slug}_" . md5(serialize($params));

    // Skip cache in preview mode
    if (isset($params['preview']) && $params['preview']) {
        return $this->client->fetchPage($pageType, $slug, $params);
    }

    return Cache::remember($cacheKey, 3600, function () use ($pageType, $slug, $params) {
        return $this->client->fetchPage($pageType, $slug, $params);
    });
}

public function getPosts(array $params = [])
{
    $cacheKey = "butter_posts_" . md5(serialize($params));

    return Cache::remember($cacheKey, 3600, function () use ($params) {
        return $this->client->fetchPosts($params);
    });
}

public function clearCache(string $pattern = 'butter_*')
{
    // For Redis
    if (config('cache.default') === 'redis') {
        $keys = Cache::getRedis()->keys(config('cache.prefix') . $pattern);
        foreach ($keys as $key) {
            Cache::forget(str_replace(config('cache.prefix'), '', $key));
        }
    } else {
        Cache::flush();
    }
}

Webhook cache invalidation

// app/Http/Controllers/WebhookController.php
<?php

namespace App\Http\Controllers;

use App\Services\ButterCMS;
use Illuminate\Http\Request;

class WebhookController extends Controller
{
    public function __construct(
        protected ButterCMS $butter
    ) {}

    public function handle(Request $request)
    {
        $payload = $request->all();

        // Clear relevant cache based on webhook event
        if (str_contains($payload['webhook']['event'] ?? '', 'page')) {
            $this->butter->clearCache('butter_page_*');
        }

        if (str_contains($payload['webhook']['event'] ?? '', 'post')) {
            $this->butter->clearCache('butter_posts_*');
        }

        return response()->json(['status' => 'ok']);
    }
}

SEO

{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html>
<head>
    <title>@yield('title', config('app.name'))</title>
    <meta name="description" content="@yield('description', '')">

    <!-- Open Graph -->
    <meta property="og:title" content="@yield('og_title', View::yieldContent('title', config('app.name')))">
    <meta property="og:description" content="@yield('og_description', View::yieldContent('description', ''))">
    @hasSection('og_image')
    <meta property="og:image" content="@yield('og_image')">
    @endif

    <!-- Twitter -->
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:title" content="@yield('title', config('app.name'))">
    <meta name="twitter:description" content="@yield('description', '')">
</head>
<body>
    @yield('content')
</body>
</html>
{{-- resources/views/pages/landing.blade.php --}}
@extends('layouts.app')

@section('title', $page->fields->seo->title ?? $page->fields->headline)
@section('description', $page->fields->seo->description ?? '')
@section('og_title', $page->fields->seo->og_title ?? $page->fields->seo->title ?? $page->fields->headline)
@section('og_description', $page->fields->seo->og_description ?? $page->fields->seo->description ?? '')
@if($page->fields->seo->og_image ?? null)
@section('og_image', $page->fields->seo->og_image)
@endif

@section('content')
<!-- Page content -->
@endsection

Resources

Laravel Starter

Pre-configured starter project

PHP SDK

Complete SDK reference

GitHub Repository

View source code

Webhooks

Set up content webhooks