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

Storyblok Bundle Laravel Package

sensiolabs-de/storyblok-bundle

Symfony bundle for integrating the Storyblok headless CMS into your application, with configuration and services to fetch and render Storyblok content in Symfony projects.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a Symfony bundle, not a Laravel package — there is no service provider, no config/storyblok.php to publish, and no package auto-discovery. For a Laravel developer the practical value is threefold: it wraps storyblok/php-client with sane DI wiring, it shows you the reference implementation of Storyblok's published-vs-draft switching, and it ships a webhook handler abstraction worth copying.

Minimal path with a Laravel app:

  1. composer require storyblok/php-client (the underlying client this bundle wraps, installable standalone).
  2. Read the bundle's DependencyInjection/Configuration.php first — it is the single source of truth for every knob you'll want to mirror in config/storyblok.php: API key, preview token, version (draft/published), base URI, HTTP client, cache adapter, webhook secret, and webhook controller path.
  3. Bind the client as a singleton in AppServiceProvider with the token selected by environment (preview token locally/staging, public token in production).
  4. First use case: resolve a story by slug and render it.
$this->app->singleton(StoryblokClient::class, fn () => new StoryblokClient(
    apiKey: config('storyblok.token'),       // preview token outside prod
    version: config('storyblok.version'),    // draft | published
    client: new \Storyblok\Client\Http\SymfonyHttpClient(), // optional
));

Look at the bundle's webhook controller and WebhookHandlerInterface second — that is the part most teams re-implement badly, and the pattern (one handler per event/content type, dispatched by a resolver) transfers directly to Laravel.

Implementation Patterns

Token and version switching by environment. Two separate tokens exist and they are not interchangeable: the public token only ever returns published content, the preview token returns drafts but leaks unpublished CMS data if it reaches a browser. Drive version off the request (a signed preview flag in session) and the token off config, never the other way round. A request-scoped binding is safer than a singleton if you support preview mode.

Repository layer over the client. Wrap fetches in a StoryblokRepository with story(string $slug): ?array and stories(array $params), so controllers and Blade never touch the client directly. This is also where you normalise Storyblok's shape into something your domain understands.

Cache with version-aware keys. Cache on full_slug plus the version, e.g. storyblok:v1:{version}:{slug} — a shared key means a preview render silently poisons production for everyone until TTL expiry. Use Cache::remember with a generous TTL and rely on webhooks, not TTL, for freshness.

Webhook endpoint that mirrors the bundle's handler contract. Register POST /webhooks/storyblok in routes/api.php (exclude it from CSRF), verify the signature, then resolve a handler:

interface WebhookHandlerInterface {
    public function supports(string $action, string $storyType): bool;
    public function handle(array $payload): void;
}

Resolve handlers from a tagged collection (bind WebhookHandlerInterface as an array of implementations, or a HandlerRegistry map), then dispatch real work onto a queue: ClearStoryCache for published/unpublished/deleted, RebuildStaticSite for full publishes. Keep the controller under ~100ms or Storyblok will retry.

Blok rendering via Blade partials. Storyblok's body is an array of bloks with a component string. Map it in a single recursive component: @includeIf('bloks.'.$blok['component'], ['blok' => $blok]) with a bloks.fallback for unknown components — this beats a match statement that silently renders nothing when editors add a component.

Preview mode. A route toggling a session flag keyed on a signed URL parameter, a middleware that sets version=draft from that flag, and a visible banner so preview content is never mistaken for production output.

Gotchas and Tips

0.4.1 in context. This release is almost entirely Dependabot codecov/codecov-action bumps; the only functional change is "Fixes auto configuration for WebhookHandlerInterface". That auto-configuration is Symfony DI tagging — if you ported the pattern to Laravel, verify your handler registry explicitly, because the equivalent failure mode (interface registered but handlers never collected) is silent: webhooks arrive, nothing happens, no error. No breaking changes, no new features, no deprecations. Upgrading is safe but changes nothing for a Laravel consumer.

You will not get WebhookHandlerInterface auto-wiring in Laravel. The bundle relies on registerForAutoconfiguration(); Laravel has no equivalent. Either tag your handlers manually in a service provider or use a HandlerRegistry with an explicit array — do not assume the container will find them.

Verify the signature against the raw body. Laravel's $request->all() is the parsed payload; signature verification (HMAC of the body with your webhook secret, per current Storyblok docs) must use $request->getContent(). Any middleware that re-encodes the JSON first will invalidate the check.

A missing story is a 200, not a 404. The API often returns a successful response with a null/empty story. Always null-check $response->getStory() — do not assume a thrown exception on unknown slugs.

Cache-busting races. The webhook can fire a second or two before the CDN serves the new content; a handler that flushes cache and immediately re-fetches may re-cache the old story. ->delay(now()->addSeconds(5)) on the queued job is the cheap fix.

Rate limits and transient 5xx. Wrap client calls in retry(3, 200, ...) (or your own backoff) and set an explicit timeout — the default PSR-18 timeout is unbounded and a slow Storyblok region will hang your workers.

Preview tokens and browser exposure. If you use the live-preview JS bridge (_storyblok param), keep the preview token server-side and proxy through your own route; putting it in the page source is a data leak, not a convenience.

Extension points worth copying, not installing. The pluggable PSR-18 HTTP client and PSR-16 cache adapter in the bundle's config are the right seams: swap in Illuminate\Http\Client or your own cache repository rather than enabling the client's internal cache alongside Laravel's, or you'll have two cache layers with independent invalidation and no way to debug which one served stale content.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console