21torr/storyblok
Symfony bundle providing API helpers and infrastructure to work with Storyblok. Simplifies fetching content, integrating Storyblok services, and building Storyblok-powered Symfony apps. Includes documentation for setup and usage.
Install the package:
composer require 21torr/storyblok
Configure the bundle in config/packages/storyblok.yaml:
storyblok:
adapters:
default:
space_id: 'your_space_id'
token: 'your_preview_token'
adapter_key: 'your_adapter_key'
cache: 'cache.adapter.redis'
cache_prefix: 'storyblok'
Fetch a story in a controller:
use Storyblok\Bundle\StoryblokBundle\Service\ContentApi;
public function show(ContentApi $contentApi)
{
$story = $contentApi->fetch('home', ['version' => 'published']);
return $this->render('story.html.twig', ['story' => $story]);
}
Render components in Twig:
{% for block in story.content %}
{% include 'components/' ~ block._component ~ '.html.twig' with {
'block': block,
'contentApi': contentApi
} %}
{% endfor %}
Fetch and render a homepage with dynamic components:
// src/Controller/HomeController.php
public function index(ContentApi $contentApi)
{
$story = $contentApi->fetch('home', ['version' => 'published']);
return $this->render('home/index.html.twig', ['story' => $story]);
}
Create adapters for different environments (e.g., preview, production):
storyblok:
adapters:
preview:
space_id: '%env(STORYBLOK_SPACE_ID)%'
token: '%env(STORYBLOK_PREVIEW_TOKEN)%'
adapter_key: 'preview'
production:
space_id: '%env(STORYBLOK_SPACE_ID)%'
token: '%env(STORYBLOK_TOKEN)%'
adapter_key: 'production'
Inject the adapter into services:
public function __construct(
private StoryblokAdapterInterface $storyblokAdapter
) {}
Use component discovery to auto-load components:
// src/Storyblok/Component/ComponentResolver.php
public function resolve(string $componentName): string
{
return sprintf('components/%s.html.twig', $componentName);
}
Pass context to components:
{% include 'components/' ~ block._component ~ '.html.twig' with {
'block': block,
'contentApi': contentApi,
'assetProxyUrlGenerator': assetProxyUrlGenerator
} %}
Generate proxy URLs for assets:
public function generateUrl(AssetProxyUrlGenerator $assetProxyUrlGenerator, string $assetId)
{
return $assetProxyUrlGenerator->generate($assetId);
}
Use AssetData DTO for type safety:
/** @var AssetData $asset */
$imageUrl = $asset->getUrl();
$altText = $asset->getAlt();
Set up a webhook endpoint:
// src/EventListener/StoryblokWebhookListener.php
public function __invoke(StoryblokWebhookEvent $event)
{
$this->storyblokAdapter->sync();
}
Trigger sync manually:
php bin/console storyblok:sync
RichText fields:
{{ block.richtext|storyblokRichtext(contentApi) }}
Link fields:
<a href="{{ block.link.url }}">{{ block.link.title }}</a>
Choice fields:
{{ block.choice|storyblokChoice }}
Caching:
php bin/console cache:clear
php bin/console storyblok:sync
cache_prefix to avoid conflicts in multi-environment setups.Adapter Keys:
Pagination:
sendPaginatedRequest() for large datasets:
$assets = $managementApi->sendPaginatedRequest('/assets', []);
Asset URLs:
asset_proxy:
storyblok:
asset_proxy:
signed_urls: true
token: '%env(STORYBLOK_ASSET_TOKEN)%'
Field Validation:
ChoiceField requires allowMissingData for optional selections:
fields:
my_choice:
type: choice
options:
- value: 'option1'
label: 'Option 1'
allowMissingData: true
Enable debug mode:
storyblok:
debug: true
var/log/storyblok.log.Use the debug command:
php bin/console debug:storyblok
Validate Storyblok config:
php bin/console debug:config storyblok
Custom Field Types:
AbstractField and register in storyblok.yaml:
storyblok:
field_types:
my_custom_field: App\Storyblok\Field\MyCustomField
Event Subscribers:
StoryblokDefinitionsSyncedEvent for post-sync logic:
public static function getSubscribedEvents(): array
{
return [
StoryblokDefinitionsSyncedEvent::class => 'onDefinitionsSynced',
];
}
Asset Proxy Customization:
AssetProxyUrlGenerator for custom URL logic:
services:
App\Storyblok\AssetProxy\CustomAssetProxyUrlGenerator:
decorates: 'storyblok.asset_proxy_url_generator'
arguments: ['@.inner']
Fetch folders efficiently:
$folders = $contentApi->fetchFoldersInPath('/path/to/folder');
Use fetchFolderTitleMap() for large folder structures:
$titleMap = $contentApi->fetchFolderTitleMap('/');
Lazy-load components with ComponentResolver:
$resolver->resolve($componentName); // Only loads if needed
From v3 to v5:
ManagementApi::fetchFoldersInPath() → ContentApi::fetchFoldersInPath()).storyblok.yaml.Private Assets:
signed_urls: true and a token in asset_proxy config.How can I help you explore Laravel packages today?