Almost EVERYONE who tried headless systems said they saw benefits. Download the state of CMS now!

Storyblok now on AWS Marketplace: Read more

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

Section titled 1. Create a new Ruby on Rails project 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

Section titled 2. Add Storyblok's Ruby SDK 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

Section titled Create a Storyblok space 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

Section titled 3. Generate the pages controller 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

Section titled Add the index method 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 =
      logger: logger,
      token: 'YOUR_TOKEN',
      version: 'draft'

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

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

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

Section titled Extend the routes file 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

Section titled 4. Add the page template 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>{{ }}</title>

  <link rel="stylesheet" href="" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
  <link href=",100i,300,300i,400,400i,500,500i,700,700i,900,900i" rel="stylesheet">
  <link rel="stylesheet" href="">
  {% include 'header' with blok: global.content %}

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

  <script type="text/javascript" src="// 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) }


Section titled 5. Create the first editable components 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.

Section titled Create _teaser.liquid Create _teaser.liquid

      {{ blok._editable }}
<section class="fdb-block bg-dark fdb-viewport"
  style="background-image: url('//')">
  <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>

Section titled Create _grid.liquid 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 %}

Section titled Create _feature.liquid Create _feature.liquid

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

Section titled 6. Create global components 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.


Section titled Extend the pages_controller.rb 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:

Section titled Add _header.liquid Add _header.liquid

      {{ blok._editable }}
  <div class="container">
    <nav class="navbar navbar-expand-md">
      <a class="navbar-brand" href="/">
      <img src="//" 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 %}

Section titled Add _nav_item.liquid Add _nav_item.liquid

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

Section titled Conclusion Conclusion

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
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.