Integrate Symfony with Storyblok
Use Storyblok to manage the content of your website built with Symfony.
This guide has been tested with the following package versions:
symfony/framework-bundle@7.3.*
storyblok/symfony-bundle@1.5.*
storyblok/php-content-api-client@1.3.*
- PHP 8.4
Setup
Create a new Symfony project by following the Installation page from its official documentation.
If you already have a Storyblok account, go to app.storyblok.com or log in with GitHub to continue.
Create a new blank space to follow the tutorial from scratch, or start from the core blueprint.
No Storyblok account yet?
Create one and start a free Storyblok space Opens in new tabInstallation
In the terminal, cd
into your Symfony project and install the Storyblok packages.
composer require storyblok/php-content-api-client storyblok/symfony-bundle
This guide uses Symfony Flex recipe, which automatically adds configuration files to your project.
In the root of your project, create a .env
file with the access token from your space.
###> storyblok/symfony-bundle ###
STORYBLOK_API_BASE_URI=https://api.storyblok.com
STORYBLOK_API_TOKEN=your_storyblok_api_token
STORYBLOK_VERSION=draft # or published
###< storyblok/symfony-bundle ###
Learn how to get an 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.
<?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;
}
}
Create a controller for your content type using the #[AsContentTypeController]
attribute.
<?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.
Add the Twig templates used during rendering. Create a base layout at templates/base.html.twig
and a page template at templates/page.html.twig
.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
{% block stylesheets %}
{% endblock %}
{% block javascripts %}
{% endblock %}
</head>
<body>
<div class="container">
{% block body %}{% endblock %}
</div>
</body>
</html>
{% 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 might contain a body
or similar field which consists of an array with several blocks of custom types (e.g. Feature, Teaser, Grid) in it.
<?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'] ?? '';
}
}
<?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'] ?? '';
}
}
<?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 template for each block will default to blocks/{name}.html.twig
.
The bundle will look for templates in the templates/blocks/
directory by default, create them as follow.
<div class="feature">
<span>{{ block.name }}</span>
</div>
<div class="teaser">
<h2>{{ block.headline }}</h2>
</div>
<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.
Run your Symfony development server and go to the /home
route in your browser.
symfony server:start
Next Part
Visual Preview in Symfony