PHP Logo

PHP blog engine

ButterCMS is an API-based PHP blog engine that integrates with PHP. Use ButterCMS to rapidly build CMS-powered blogs, dynamic pages, and more.

Table of Contents

Introduction

Learn how to quickly build a custom CMS-powered blog with great SEO using PHP. This guide uses the Laravel web framework but Butter also works with any other kind of PHP application like CodeIgniter, Symfony, or raw PHP. Full working example code is available on Github.

If you need help after reading this, contact us via email, livechat, or book a time with to pair with one of our developers.

Display posts

To display posts we create a simple /blog route in our app and fetch blog posts from the Butter API. See our API reference for additional options such as filtering by category or author. The response also includes some metadata we'll use for pagination.

routes/web.php:

Route::get('/blog', 'BlogController@listAllPosts');
Route::get('/blog/p/{page}', 'BlogController@listAllPosts');

app/Http/Controllers/BlogController.php:

namespace App\Http\Controllers;

use Illuminate\Routing\Controller as BaseController;
use ButterCMS\ButterCMS;

class BlogController extends BaseController {

  private static $apiToken = 'b60a008584313ed21803780bc9208557b3b49fbb';
  private $client;

  public function __construct() {
    $this->client = new ButterCMS(BlogController::$apiToken);
  }

  public function listAllPosts(int $page = 1) {
    $response = $this->client->fetchPosts([ 
      'page' => $page,
      'page_size' => 10
    ]);
    return view('posts', [
      'posts' => $response->getPosts(),
      'nextPage' => $response->getMeta()['next_page'],
      'previousPage' => $response->getMeta()['previous_page']
    ]);
  }
}

Next we'll create a simple view at resources/views/posts.blade.php that displays our posts and pagination links:

@extends('master')

@section('pageTitle', 'Blog')

@section('content')
  <h2>Blog</h2>

  @foreach ($posts as $post)
    <a href="/blog/{{urlencode($post->getSlug())}}">{{$post->getTitle()}}</a> 
    by 
    <a href="/author/{{urlencode($post->getAuthor()->getSlug())}}">
      {{$post->getAuthor()->getFirstName()}} {{$post->getAuthor()->getLastName()}}
    </a>
    <br>
  @endforeach

  @if (!is_null($previousPage))
    <a href="/blog/p/{{$previousPage}}">Prev</a>
  @endif
  @if (!is_null($nextPage))
    <a href="/blog/p/{{$nextPage}}">Next</a>
  @endif
@stop

We'll also create an additional route and controller method for displaying individual posts:

Route::get('/blog/{slug}', 'BlogController@showPost')
public function showPost(string $slug) {
  $response = $this->client->fetchPost($slug);
  $post = $response->getPost();
  return view('post', [
    'post' => $post,
    'published' => date('j/n/Y', strtotime($post->getPublished())),
  ]);
}

The view for displaying a full post includes information such as author, publish date, and categories. See a full list of available post properties in our API reference. We use the @section Laravel helper for setting the HTML title and meta description in the <head> tag of the page.

@extends('master')

@section('pageTitle', $post->getSeoTitle())
@section('metaDescription', $post->getMetaDescription())

@section('content')

  <!-- Post title -->
  <h2>{{$post->getTitle()}}</h2>

  <!-- Publish date -->
  Published {{$published}} 

  @if (count($post->getCategories()) > 0)  
    in
  @endif

  <!-- Post categories -->
  @foreach ($post->getCategories() as $category)
    <a href="/category/{{urlencode($category->getSlug())}}">{{$category->getName()}}</a>
  @endforeach

  <br />

  <!-- Post author -->
  <a href="/author/{{urlencode($post->getAuthor()->getSlug())}}">
    {{$post->getAuthor()->getFirstName()}} {{$post->getAuthor()->getLastName()}}
  </a>

  <br />

  <div>
    {!! $post->getBody() !!}
  </div>
@stop

Categories, Tags, and Authors

Use Butter's APIs for categories, tags, and authors to feature and filter content on your blog:

Route::get('/authors', 'BlogController@listAllAuthors');
Route::get('/author/{slug}', 'BlogController@showAuthor');
Route::get('/author/{slug}/p/{page}', 'BlogController@showAuthor');

Route::get('/categories', 'BlogController@listAllCategories');
Route::get('/category/{slug}', 'BlogController@showCategory');
Route::get('/category/{slug}/p/{page}', 'BlogController@showCategory');

Route::get('/tags', 'BlogController@listAllTags');
Route::get('/tag/{slug}', 'BlogController@showTag');
Route::get('/tag/{slug}/p/{page}', 'BlogController@showTag');
<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Response as Response;
use ButterCMS\ButterCMS;

class BlogController extends BaseController {

  private static $apiToken = 'b60a008584313ed21803780bc9208557b3b49fbb';
  private $client;

  public function __construct() {
    $this->client = new ButterCMS(BlogController::$apiToken);
  }

  public function listAllAuthors() {
    $response = $this->client->fetchAuthors();
    return view('authors', [
      'authors' => $response,
    ]);
  }

  public function showAuthor(string $slug, int $page = 1) {
    $authorResponse = $this->client->fetchAuthor($slug);
    $postsResponse = $this->client->fetchPosts([ 
      'author_slug' => $slug,
      'page' => $page,
      'page_size' => 10
    ]);
    return view('author', [
      'posts' => $postsResponse->getPosts(),
      'author' => $authorResponse,
      'nextPage' => $postsResponse->getMeta()['next_page'],
      'previousPage' => $postsResponse->getMeta()['previous_page']
    ]);
  }

  public function listAllCategories() {
    $response = $this->client->fetchCategories();
    return view('categories', [
      'categories' => $response,
    ]);
  }

  public function showCategory(string $slug, int $page = 1) {
    $categoryResponse = $this->client->fetchCategory($slug);
    $postsResponse = $this->client->fetchPosts([ 
      'category_slug' => $slug,
      'page' => $page,
      'page_size' => 10
    ]);
    return view('category', [
      'posts' => $postsResponse->getPosts(),
      'category' => $categoryResponse,
      'nextPage' => $postsResponse->getMeta()['next_page'],
      'previousPage' => $postsResponse->getMeta()['previous_page']
    ]);
  }

  public function listAllTags() {
    $response = $this->client->fetchTags();
    return view('tags', [
      'tags' => $response,
    ]);
  }

  public function showTag(string $slug, int $page = 1) {
    $tagResponse = $this->client->fetchTag($slug);
    $postsResponse = $this->client->fetchPosts([ 
      'tag_slug' => $slug,
      'page' => $page,
      'page_size' => 10
    ]);
    return view('tag', [
      'posts' => $postsResponse->getPosts(),
      'tag' => $tagResponse,
      'nextPage' => $postsResponse->getMeta()['next_page'],
      'previousPage' => $postsResponse->getMeta()['previous_page']
    ]);
  }

}

See our API reference for more information about these objects:

RSS, Atom, and Sitemap

Butter generates RSS, Atom, and sitemap XML markup. To use these on your blog, return the generated XML from the Butter API with the proper content type headers.

Route::get('/feeds/rss', 'BlogController@showRSS');
Route::get('/feeds/atom', 'BlogController@showAtom');
Route::get('/sitemap', 'BlogController@showSitemap');
public function showRSS() {
  return Response::make(
    $this->client->fetchFeed('rss')->asXML(),
    200, 
    ['Content-Type' => 'text/xml']
  );
}

public function showAtom() {
  return Response::make(
    $this->client->fetchFeed('atom')->asXML(),
    200, 
    ['Content-Type' => 'text/xml']
  );
}

public function showSitemap() {
  return Response::make(
    $this->client->fetchFeed('sitemap')->asXML(),
    200, 
    ['Content-Type' => 'text/xml']
  );
}

Comments

Butter doesn't provide an API for comments due to the excellent existing options that integrate easily. Two popular services we recommend are:

Both products are free, include moderation capabilities, and give your audience a familiar commenting experience. They can also provide some additional distribution for your content since users in their networks can see when people comment on your posts. For a minimalist alternative to Disqus, check out RemarkBox or for an open-source option, Isso.

CSS

Butter integrates into your front-end so you have complete control over the design of your blog. Use the following CSS as boilerplate for post content styling.

.post-container {
  h1, h2, h3, h4, h5 {
    font-weight: 600;
    margin-bottom: 1em;
    margin-top: 1.5em;
  }

  ul, ol {
    margin-bottom: 1.25em;

    li {
      margin-bottom: 0.25em;
    }
  }

  p {
    font-family: Georgia, Cambria, "Times New Roman", Times, serif;
    font-size: 1.25em;
    line-height: 1.58;
    margin-bottom: 1.25em;
    font-weight: 400;
    letter-spacing: -.003em;
  }

  /* Responsive default image width */
  img {
    max-width: 100%;
    height: auto;
  }

  /* Responsive floating */
  @media only screen and (min-width: 720px)  {
    .butter-float-left {
      float: left;
      margin: 0px 10px 10px 0px;
    }

    .butter-float-right {
      float: right;
      margin: 0px 0px 10px 10px;
    }
  }

  /* Image caption */
  figcaption {
    font-style: italic;
    text-align: center;
    color: #ccc;
  }

  p code {
    padding: 2px 4px;
    font-size: 90%;
    color: #c7254e;
    background-color: #f9f2f4;
    border-radius: 4px;
    font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
  }

  pre {
    display: block;
    padding: 1em;
    margin: 0 0 2em;
    font-size: 1em;
    line-height: 1.4;
    word-break: break-all;
    word-wrap: break-word;
    color: #333333;
    background-color: #f5f5f5;
    font-family: Menlo, Monaco,Consolas, "Courier New", monospace;
  }
}

Migration

To import content from another platform like WordPress or Medium, send us an email.

About PHP

PHP is a widely-used open source general-purpose scripting language that is especially suited for web development and can be embedded into HTML.

Blog Engine for these technologies and more

Ruby, Rails, Node.js, Python, ASP.NET, C#, Flask, Django, Go, PHP, React, Phoenix, Elixir

Get started now

Sign up with Google Sign up with Github
or