riclep/laravel-storyblok
Laravel Storyblok brings the Storyblok headless CMS into Laravel with an Eloquent-style model layer, relationships, link and image resolvers, caching, and Blade rendering for content blocks, so teams can build flexible, editor-friendly websites quickly.
composer require riclep/laravel-storyblok and publish the config (php artisan vendor:publish --tag=storyblok-config — confirm the tag in the package's README for 3.x)..env: STORYBLOK_API_KEY, plus a preview token if you want draft content. Region matters — a EU key against a US space returns empty results, not an error.config/storyblok.php first. It is the whole package surface: API endpoint/region, version (draft vs published), cache settings, link resolution behaviour, the block class namespace/directory, and image service options.use RicLeP\LaravelStoryblok\Facades\Storyblok;
$story = Storyblok::getStory('home');
return view('page', compact('story'));
<x-storyblok-content :content="$story->content" />
Route::get('/{slug?}', ...)->where('slug', '.*')) so all pages are Storyblok-driven, then create one block class per Storyblok component (extend the package's StoryblokBlock, set $component to match the component name in Storyblok exactly, return a view from render()).The fastest first win: static page shell in Blade + two block classes (page, teaser). You'll hit every important concept — response shape, blok nesting, and link fields — in under an hour.
Blok-per-component classes. The core day-to-day pattern. One class per Storyblok component, one Blade view per class, nested bloks rendered recursively by the content component. Keep the classes thin — they should map fields to view data, not contain business logic; put anything reusable in a view model or service.
Catch-all routing with slug resolution. Storyblok's "root" folder and folder-stories are the usual source of 404s. Resolve /{slug} to getStory($slug ?: 'home') and handle null explicitly with abort(404) rather than letting a null story reach the view.
Preview/draft mode. Toggle version=draft behind a middleware (session flag or signed preview URL) so editors get draft content while public traffic stays on published. Pass the preview token only on the draft path — never leak it to the published path or into cached responses.
Caching + webhook invalidation. Cache the API response keyed on slug + version + locale (and, if you can, the story's _storyblok publish timestamp). Wire a Storyblok webhook to an app endpoint that forgets the relevant keys on publish. This is the single biggest performance lever and the thing that makes Storyblok feel fast to editors.
Link resolution in Blade. Link fields (type: story | url | email | asset) need normalising into hrefs. Use the package's link helper/resolver at render time so internal story links resolve through your own routes and pick up slug changes automatically. Keep one place in the app where link → href happens; scattered conditionals on $link->type are the classic rot point.
Richtext. Render through the package's richtext renderer instead of {!! $story->content->body !!}. Rich text can embed nested bloks inline, and hand-rolling the HTML loses them (and is an XSS surface).
Testing. Http::fake() on api.storyblok.com plus a small fixture set of story JSON lets you test blocks and routes without hitting the API. Store one fixture per component shape.
{"type":"story","uuid":"..."}), producing empty/null hrefs, or not resolving to the right URL, that's the bug this release fixes. No public API change, no config keys changed — it's a safe patch bump inside ^3.0. If you'd worked around it in Blade, remove the workaround after upgrading or you'll double-transform.resolve_links is enabled at the API level, don't also run the local resolver over the same fields. Pick one strategy; mixing them yields mangled URLs and nested story objects.version.dd() the raw content and diff the component string against your class's $component — casing and hyphens matter.app/, and swap the cache store per environment. These are the three places to reach for before considering a fork.How can I help you explore Laravel packages today?