---
title: Integrate Symfony with Storyblok
description: This guide walks you through integrating Storyblok as a headless CMS, fetching content, and building components to render it effectively in your Symfony project.
url: https://www.storyblok.com/docs/guides/symfony
---

# Integrate Symfony with Storyblok

Use Storyblok to manage the content of your website built with Symfony.

> [!NOTE]
> This guide has been tested with the following package versions:
> 
> -   `symfony/framework-bundle@8.*`
> -   `storyblok/symfony-bundle@1.14.*`
> -   `storyblok/php-content-api-client@1.15.*`
> -   PHP 8.4

## Setup

Create a new Symfony project by following the official [installation](https://symfony.com/doc/current/setup.html) manual.

If you already have a Storyblok account, log into [app.storyblok.com](https://app.storyblok.com/#/login) or [log in with GitHub](https://github.com/login?client_id=Iv23liC8pLXD6VcT2EbS&return_to=%2Flogin%2Foauth%2Fauthorize%3Fclient_id%3DIv23liC8pLXD6VcT2EbS%26redirect_uri%3Dhttps%253A%252F%252Fapp.storyblok.com%252F#/login) to continue.

Create a [new blank space](https://app.storyblok.com/#/me/spaces/new?tab=select-plan) to follow the tutorial from scratch.

[No Storyblok account yet?](https://app.storyblok.com/#/signup)Create one and start a free Storyblok space

## Installation

In the terminal, `cd` into your Symfony project and install the Storyblok packages.

```bash
composer require storyblok/php-content-api-client storyblok/symfony-bundle
```

> [!TIP]
> This guide uses a [Symfony Flex recipe](https://symfony.com/doc/current/setup.html#installing-packages), which automatically adds configuration files to your project.

In the root of your project, create a `.env` file with the access token of your space.

.env

```bash
###> storyblok/symfony-bundle ###
STORYBLOK_API_BASE_URI=https://api.storyblok.com
STORYBLOK_API_TOKEN=your_storyblok_api_token
STORYBLOK_VERSION=published # the bundle's default
###< storyblok/symfony-bundle ###
```

> [!NOTE]
> Learn how to get an [access token](https://www.storyblok.com/docs/concepts/access-tokens#space-access-token) for your Storyblok project.

## Fetch a single story

Create a `Page` content type to fetch and render all stories of the `page` content type.

src/ContentType/Page.php

```php
<?php

declare(strict_types=1);

namespace App\ContentType;

use Storyblok\Bundle\ContentType\ContentType;

final readonly class Page extends ContentType
{
  public string $uuid;
  public string $name;
  public string $slug;
  public array $body;
  public \DateTimeImmutable $publishedAt;

  public function __construct(array $values)
  {
    $this->uuid = $values['uuid'];
    $this->name = $values['name'];
    $this->slug = $values['full_slug'];
    $this->body = $values['content']['body'] ?? [];
    $this->publishedAt = new \DateTimeImmutable($values['published_at']);
  }

  public function publishedAt(): \DateTimeImmutable
  {
    return $this->publishedAt;
  }
}
```

Next, create a controller for this content type.

src/Controller/PageController.php

```php
<?php

declare(strict_types=1);

namespace App\Controller;

use App\ContentType\Page;
use Storyblok\Bundle\ContentType\Attribute\AsContentTypeController;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

#[AsContentTypeController(contentType: Page::class)]
final class PageController extends AbstractController
{
  public function __invoke(Request $request, Page $page): Response
  {
    return $this->render('page.html.twig', [
      'page' => $page,
    ]);
  }
}
```

The `#[AsContentTypeController]` attribute automatically registers the controller to handle requests for the specified content type.

Finally, add Twig templates for rendering. Start with the `base` layout:

templates/base.html.twig

```twig
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
<title>{% block title %}Welcome to Storyblok!{% endblock %}</title>

    {% block stylesheets %}
    {% endblock %}
    {% block javascripts %}
    {% endblock %}
  </head>
  <body>
    <div class="container">
      {% block body %}{% endblock %}
    </div>
  </body>
</html>
```

Then, create a template to render the `page` content type:

templates/page.html.twig

```twig
{% extends 'base.html.twig' %}

{% block body %}
  {% if page.body is defined and page.body is not empty %}
    {% for block in page.body %}
      {% if block is not null %}
        {{ block|render_block }}
      {% endif %}
    {% endfor %}
  {% endif %}
{% endblock %}
```

The `render_block` Twig filter automatically renders blocks using their registered templates.

## Create blocks and templates

Stories usually contain a `body` or similar field that consists of an array with several custom type blocks (for example, Feature, Teaser, Grid).

Create them as follows:

src/Block/Feature.php

```php
<?php

declare(strict_types=1);

namespace App\Block;

use Storyblok\Bundle\Block\Attribute\AsBlock;

#[AsBlock]
final readonly class Feature
{
  public string $name;

  public function __construct(array $values)
  {
    $this->name = $values['name'] ?? '';
  }
}
```

src/Block/Teaser.php

```php
<?php

declare(strict_types=1);

namespace App\Block;

use Storyblok\Bundle\Block\Attribute\AsBlock;

#[AsBlock]
final readonly class Teaser
{
  public string $headline;

  public function __construct(array $values)
  {
    $this->headline = $values['headline'] ?? '';
  }
}
```

src/Block/Grid.php

```php
<?php

declare(strict_types=1);

namespace App\Block;

use Storyblok\Bundle\Block\Attribute\AsBlock;

#[AsBlock]
final readonly class Grid
{
  public array $columns;

  public function __construct(array $values)
  {
    $this->columns = $values['columns'] ?? [];
  }
}
```

The `#[AsBlock]` attribute automatically registers blocks for rendering. The bundle defaults to block templates stored in `templates/blocks/{name}.html.twig`.

Create them as follows:

templates/blocks/feature.html.twig

```twig
<div class="feature">
  <span>{{ block.name }}</span>
</div>
```

templates/blocks/teaser.html.twig

```twig
<div class="teaser">
  <h2>{{ block.headline }}</h2>
</div>
```

templates/blocks/grid.html.twig

```twig
<div class="grid">
  {% for column in block.columns %}
    <div>
      {{ column|render_block }}
    </div>
  {% endfor %}
</div>
```

Similar to the `Page` template, the `Grid` template iterates over the `columns` block field and renders nested blocks.

In Storyblok, open **Content**, select the Home story, and publish it.

> [!TIP]
> The bundle defaults to the `published` version, and expects a valid `published_at` value.

Run the Symfony development server and open `http://localhost:8000/home` in the browser.

```plaintext
symfony server:start
```

## Pagination

-   [Next: Visual Preview in Symfony](/docs/guides/symfony/visual-preview)
