Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Symfony Bundle Laravel Package

storyblok/symfony-bundle

Integrates the Storyblok headless CMS with Symfony, offering configuration, services, and helpers to fetch, manage, and render Storyblok content in Symfony applications with less boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package. storyblok/symfony-bundle boots via Symfony Flex and registers services through a StoryblokBundle + DI extension; there is no service provider, no config/storyblok.php, no facade. In a Laravel app the practical path is one of:

  1. composer require storyblok/php-client and wire a singleton yourself (the bundle is a thin, opinionated wrapper around exactly that client), or
  2. read this bundle's source as the reference implementation for caching, webhook invalidation and rich-text rendering, then port those patterns.

If you are in a Symfony app, the first steps are:

composer require storyblok/symfony-bundle
# accept the Flex recipe, then:
php bin/console config:dump-reference storyblok

Configure the minimum in config/packages/storyblok.yaml:

storyblok:
    base_uri: 'https://api.storyblok.com/v2'
    token: '%env(STORYBLOK_TOKEN)%'
    api_parameters:
        version: '%env(STORYBLOK_VERSION)%'   # draft in dev, published in prod

Where to look first: config:dump-reference storyblok (the authoritative key list for your installed version), then the service definition for the client, then the Twig extension.

First use case: inject the client, fetch a story by slug, render content. This is the 20-line vertical slice that proves token, version and cache are all correct before you build anything else.

What changed in 1.18.2 — the two commits are a Dependabot CI bump and one bugfix: strip the language prefix from full_slug when the Translatable Slugs app is not installed. Patch-level, no config keys added or removed, no deprecations. It only matters if you resolve routes from full_slug on an i18n Storyblok space.

Implementation Patterns

Version switching per environment. api_parameters.version is the single lever between draft and published. Bind it to APP_ENV rather than hardcoding, and never let draft reach production — draft responses bypass the CDN cache and expose unpublished content.

Catch-all routing from full_slug. The canonical Storyblok-CMS pattern: one wildcard route, resolve the story by slug, 404 on miss.

// Laravel equivalent of the bundle's routing story
Route::get('/{slug?}', function (StoryblokClient $client, string $slug = '') {
    $story = $client->getStoryBySlug($slug);   // returns false on 404
    abort_unless($story, 404);
    return view('storyblok.page', compact('story'));
})->where('slug', '.*');

This is exactly the code path affected by the 1.18.2 fix — see Gotchas.

Cache-then-invalidate, not cache-with-TTL-only. The bundle's real value is the pairing of a PSR-6 cache pool around the client with a webhook endpoint that busts it. Port both or neither; a TTL-only cache gives you stale content right after an editor hits publish, which is the #1 complaint you'll get. Config shape is roughly:

storyblok:
    cache:
        pool: 'cache.storyblok'   # must be a real PSR-6 pool service id
        ttl: 3600
    webhook_secret: '%env(STORYBLOK_WEBHOOK_SECRET)%'

Point Storyblok's webhook at the bundle's route for story.published, story.unpublished, story.deleted, and make sure the secret matches — an unverified webhook route is a cache-poisoning vector.

Rich text as a first-class renderer. Use the bundle's storyblok_richtext filter / rich-text service instead of {!! $story['content']['body'] !!}. It walks the doc tree, so you can override rendering per node type (links with linktype: story need slug resolution against your route table, not the raw cached_url).

Block components mapped by component name. Every blok carries a component string. Convention: one partial per component name, with a default fallback that renders nothing in prod and logs in dev. Keep the mapping in one place so adding a blok is a one-file change.

Asset and image handling. Storyblok's image service is a URL transform (/m/{width}x{height}/filters:quality(...)). The bundle's asset transformer does this for you, but it only rewrites a.storyblok.com URLs — guard against passing external URLs through it.

Visual editor hooks. storyblok_editable (data attributes for the Storyblok bridge) should be applied only when a request is a preview — gate on version === 'draft' and an authenticated editor session, otherwise you leak draft metadata to anonymous traffic.

Testing. Fixture the raw CDN JSON (cdn/stories/{slug} response body) and assert your view layer against it. Stub the PSR-18 HTTP client or bind a fake client in the container; don't hit the API in tests — the published/draft distinction makes them flaky.

Gotchas and Tips

  • The 1.18.2 slug fix is a silent route change. If your space uses Storyblok's i18n (language / fallback_lang) and you do not have the Translatable Slugs app installed, full_slug was previously left with its language prefix (de/unternehmen); 1.18.2 strips it. Routes generated from full_slug therefore change shape for non-default languages. Audit your i18n spaces, your sitemap, and add 301s for the old prefixed URLs before deploying. If you do have the app installed, behaviour is unchanged.
  • getStoryBySlug returns false, not an exception, on 404. A truthiness check is not optional. Symmetrically, network failures and 401s do throw — catch them separately so a bad token doesn't render as "page not found".
  • Leading slash inconsistencies in full_slug. Storyblok returns slugs without a leading slash, but cached_url may include one, and the language-prefix handling complicates this further. Normalize once at the boundary (ltrim($slug, '/')) and never compare slugs from two different sources directly.
  • version: draft disables CDN caching. Switching it on in production is both a performance cliff and a content-leak. Assert it in your deploy check.
  • Cache pool must be a PSR-6 pool, and must be namespaceable. If you point storyblok.cache.pool at a pool you also clear from elsewhere, you'll get surprise purges. Use a dedicated pool so cache:pool:clear <storyblok-pool> is surgical.
  • Webhook invalidation fails silently. Wrong secret → non-2xx that Storyblok retries and eventually drops; route behind auth/firewall → never reached. Log webhook hits and wire a failing alert, or you'll discover it days later via stale pages.
  • Rich text is editor-supplied HTML. Storyblok does not sanitize; neither does the rich-text renderer. Sanitize on output (HTMLPurifier, league/html-to-markdown-style allowlists) if anyone outside your org can edit content. In Blade this means being deliberate about {!! !!}.
  • Image transform assumes Storyblok's asset domain. Wrap it: if (!str_contains($url, 'a.storyblok.com')) return $url;.
  • Debugging: config:dump-reference storyblok for the schema, debug:config storyblok for effective values after env resolution, and a PSR-18 logging middleware (or Laravel's Http::withOptions + Log::debug) to see the exact CDN URL — most "wrong content" bugs are a wrong version, language or cv parameter.
  • Extension points worth knowing: decorate the client service (add retry/timeout/logging without forking), register a custom Twig extension to override blok rendering, listen to the webhook events to trigger your own rebuilds, and subclass the slug/asset services if your URL scheme differs from the default.
  • Upgrade risk for 1.18.0 → 1.18.2 is low (no BC breaks signalled), but pin the client dependency's major and run your i18n route tests against a staging Storyblok space — that's where the slug change will surface, if anywhere.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin