O’Reilly Report: Decoupled Applications and Composable Web Architectures - Download Now

Empower your teams & get a 582% ROI: See Storyblok's CMS in action

Skip to main content

Add a CMS to Ruby on Rails in 5 minutes

In this short article, we will show you how you can use the API-based CMS Storyblok in combination with the Framework “Ruby on Rails”. At the end of this article, you will have a Ruby on Rails application which renders components filled with data from Storyblok.

You can also clone the code of this tutorial at github.com/storyblok/rails-boilerplate.

1. Create a new Ruby on Rails project

You can add Storyblok to existing projects or new ones. I will show how to add Storyblok to a new project. Execute the following commands so you get a new project ready to start with:

      rails new my-project
cd ./my-project

2. Add Storyblok's Ruby SDK

Let’s install the headless CMS client and liquid as template language by adding following lines to our Gemfile.

      gem 'liquid', github: 'Shopify/liquid', branch: 'master'
gem 'storyblok'

After adding the gems execute bundle install and start the rails app.

      bundle install
bin/rails server -p 3000

Create a Storyblok space

After you have started your project you need to get the Storyblok preview token and tell Storyblok where your app is running. Create a new Storyblok space and click on the "Home" content item. Note down your preview token and insert localhost:3000 as your host.

Storyblok Bridge

3. Generate the pages controller

Storyblok is a page centric CMS so we create a controller that handles all requests with a wildcard route.

      bin/rails g controller pages

Add the index method

Add following code to pages_controller.rb and exchange YOUR_TOKEN with the preview token from Storyblok's settings page.

      class PagesController < ApplicationController
  def index
    response.headers['X-FRAME-OPTIONS'] = 'ALLOWALL'

    client = Storyblok::Client.new(
      logger: logger,
      cache_version: Time.now.to_i,
      token: 'YOUR_TOKEN',
      version: 'draft'

    assigns = {
      story: client.story(params[:path])['data']['story']

    Liquid::Template.file_system = Liquid::LocalFileSystem.new('app/views/components')

    template = Liquid::Template.parse(File.read('app/views/layouts/page.liquid'))
    render html: template.render!(assigns.stringify_keys, {}).html_safe

Extend the routes file

Add a wildcard route to route all requests to the pages controller.

      Rails.application.routes.draw do
  match "*path", to: "pages#index", via: :all

4. Add the page template

Add the main page template page.liquid in layouts which includes the Storyblok Javascript bridge. Optionally you can add a conditional check for the parameter _storyblok in the url to include the Storyblok's Javascript bridge only when the user is in the editing mode.

      <!DOCTYPE html>
  <title>{{ story.name }}</title>

  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
  <link href="https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i" rel="stylesheet">
  <link rel="stylesheet" href="https://a.storyblok.com/t/43698/assets/css/above.css?1523887806">
  {% include 'header' with blok: global.content %}

  {% for blok in story.content.body %}
    {% include blok.component with blok: blok %}
  {% endfor %}

  <script type="text/javascript" src="//app.storyblok.com/f/storyblok-latest.js?t= Dv2ok3DqODzzb8QUuN2XCgtt"></script>
  <script type="text/javascript">

    storyblok.on('change', function(event) {
      if (!event.slugChanged) { location.reload(true) }

    storyblok.on('published', function(event) {
      if (!event.slugChanged) { location.reload(true) }


5. Create the first editable components

The demo content that get's created when adding a new space in Storyblok has already some components preconfigured. To make this components clickable in the editor the only thing that you need to do is to add {{ blok._editable }} before a DOM element.

Let's create our first component templates _teaser.liquid, _grid.liquid and _feature.liquid in the components folder.

Create _teaser.liquid

      {{ blok._editable }}
<section class="fdb-block bg-dark fdb-viewport"
  style="background-image: url('//a.storyblok.com/f/43698/3000x2000/7696f16f6b/bg_c_1.svg')">
  <div class="container align-items-center justify-content-center d-flex">
    <div class="row justify-content-center">
      <div class="text-center col-12 col-sm-10 col-md-8">
        <h1>{{ blok.headline }}</h1>
        <p class="mt-5">
          <a class="btn " href="#">Call to action</a>

Create _grid.liquid

In this component we iterate over columns to include other components dynamically.

      {{ blok._editable }}
<div class="container">
  <div class="row text-center justify-content-center pt-5 pb-5">
    {% for item in blok.columns %}
      {% include item.component with blok: item %}
    {% endfor %}

Create _feature.liquid

      {{ blok._editable }}
<div class="col-12 col-sm-6 col-lg-3 pt-4 pt-lg-0">
  <h3><strong>{{ blok.name }}</strong></h3>

  <p>{{ blok.text }}</p>

At the end you should be able to see a teaser and three feature blocks that are clickable in Storyblok.

Ruby on Rails CMS Screenshot

6. Create global components

To create global content like a header navigation you can define a new content type. Create a new "Story" name it "Global" and type setting in the content type field.

Global Ruby on Rails component

Add nav_links with type "Blocks" to the schema of the settings content type and then create nav_item blocks with name and link in the schema definition.


Extend the pages_controller.rb

After you have published the global content item add the api call to your controller:

assigns = {
  story: client.story(params[:path])['data']['story'],
  global: client.story('global')['data']['story']

Last step is to add the components to your Ruby on Rails project:

Add _header.liquid

      {{ blok._editable }}
  <div class="container">
    <nav class="navbar navbar-expand-md">
      <a class="navbar-brand" href="/">
      <img src="//a.storyblok.com/f/43698/437x202/371b07400b/img_logo_new.png" height="30" alt="Logo">
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-2651eb25-213b-4216-8d83-354812817602" aria-controls="navbar-2651eb25-213b-4216-8d83-354812817602" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
      <div class="collapse navbar-collapse" id="navbar-2651eb25-213b-4216-8d83-354812817602">
        {% if blok.nav_links != blank %}
          <ul class="navbar-nav mr-auto">
            {% for item in blok.nav_links %}
              {% include item.component with blok: item %}
            {% endfor %}
        {% endif %}

Add _nav_item.liquid

      {{ blok._editable }}
<li class="nav-item{% if blok.link.cached_url contains request.path %} active{% endif %}">
  <a class="nav-link" href="{{ blok.cached_url }}">
    {{ blok.name }}


It's incredibly easy to build a flexible block based website with Ruby and Rails and Storyblok. In case you want to extend an existing project with a blog be sure to checkout the how to create blog content structure article.

Github repository of this Tutorialgithub.com/storyblok/rails-boilerplate
Ruby on RailsRuby on Rails
Storyblok AppStoryblok


Alexander Feiglstorfer

Alexander Feiglstorfer

Passionate developer and always in search of the most effective way to resolve a problem. After working 13 years for agencies and SaaS companies using almost every CMS out there he founded Storyblok to solve the problem of being forced to a technology, proprietary template languages and the plugin hell of monolithic systems.