Skip to content

Hi-Folks/storyblok-laravel-helpers

Repository files navigation

Storyblok Laravel Helpers

Storyblok integration for Laravel. It provides bridge, visual editor, image service, richtext rendering, and component dispatcher.

This is an opinionated Laravel package where i collected some helpers that typically I use in Laravel application that integrates Storyblok Content Delivery API.

This Laravel package leverages the storyblok/php-content-api-client, a type-safe PHP SDK for Storyblok.

Installation

composer require hi-folks/storyblok-laravel-helpers

The package auto-registers its service provider and facade via Laravel's package discovery.

Quick start

Run the install command to scaffold your project:

php artisan storyblok:install

This will:

  • Publish config/storyblok.php
  • Create the Blade components directory (resources/views/components/storyblok/)
  • Create the Storyblok definitions directory (storyblok/)
  • Publish the StoryController and story.blade.php stubs
  • Append Storyblok environment variables to .env and .env.example

Then set your access token in .env:

STORYBLOK_ACCESS_TOKEN=your-access-token

Getting started with a fresh Laravel project

Here is how to go from a new Laravel app to rendering your first Storyblok page.

1. Create a Laravel project and install the package

laravel new my-app
cd my-app
composer require hi-folks/storyblok-laravel-helpers

2. Run the install command

php artisan storyblok:install

3. Set your Storyblok access token

Grab the Preview access token from your Storyblok space settings and add it to .env:

STORYBLOK_ACCESS_TOKEN=your-preview-token

4. Create a Blade component for your blok

Suppose you have a page component in Storyblok with a body field. Create the matching Blade component:

{{-- resources/views/components/storyblok/page.blade.php --}}
@props(['blok'])

<main @storyblokEditable($blok)>
    @foreach ($blok->get('body', []) as $nestedBlok)
        <x-storyblok::component :blok="$nestedBlok" />
    @endforeach
</main>

The storyblok:install command already created the resources/views/components/storyblok/ directory for you.

Note: The $blok variable passed to your Blade components is a Blok instance (HiFolks\StoryblokLaravelHelpers\Blok). Use $blok->get('field') instead of $blok['field'] to access fields — it supports dot notation for nested access (e.g. $blok->get('content.nested.value', 'default')).

5. Register the story route

Open routes/web.php and add:

use App\Http\Controllers\StoryController;

Route::get('/{slug?}', [StoryController::class, 'show'])
    ->where('slug', '.*')
    ->name('story.show');

The StoryController was published by the install command at app/Http/Controllers/StoryController.php. It fetches a story by slug via the Content Delivery API and passes it to the story.blade.php view, which uses the <x-storyblok::component> dispatcher to render bloks.

6. Run and visit

php artisan serve

Open http://localhost:8000 — Laravel will fetch the home story from Storyblok and render it through your Blade components.

From here you can create more Blade components (one per Storyblok blok) inside resources/views/components/storyblok/.

Configuration

Publish the config file manually if needed:

php artisan vendor:publish --tag=storyblok-config

Available options in config/storyblok.php:

Key Description Default
access_token Storyblok Content Delivery API token env()
version Content version (published or draft) published
hostname API base URL (see Regions below) EU endpoint
mapi_token Management API token (for CLI commands) env()
space_id Storyblok space ID (for CLI commands) env()
component_namespace Subdirectory for Storyblok Blade components storyblok
preview_path POST endpoint for bridge real-time preview /api/preview
preview_view Blade view rendered for preview requests story

Package structure

Area Files Description
Config config/storyblok.php Access token, content version, Management API credentials, component namespace, preview settings
Service Provider src/StoryblokLaravelHelpersServiceProvider.php Registers config, views, routes, Blade directives, Artisan commands, and publishable assets
Blok src/Blok.php Wraps blok arrays with get() (dot-notation), has(), and toArray()
Story src/Story.php Extends Blok with convenience methods for story data (content(), name(), slug(), fullSlug())
Facade src/Facades/Storyblok.php Facade for StoryblokManager (content delivery)
Manager src/StoryblokManager.php Factory for StoriesApi instances with region and config support
Controller src/Http/Controllers/PreviewController.php Handles real-time preview requests from the Storyblok Bridge
Middleware src/Http/Middleware/SetStoryblokDraftVersion.php Auto-switches content version to draft when inside the Visual Editor
Services src/Services/StoryblokEditable.php Generates data-blok-c and data-blok-uid attributes for the Visual Editor
src/Services/StoryblokImageUrl.php Builds Storyblok Image Service URLs with transforms, srcset generation
src/Services/StoryblokRichtext.php Renders Storyblok richtext fields to HTML via Tiptap
src/Services/StoryblokRichtextMarkdown.php Renders Storyblok richtext fields to Markdown
src/Services/Markdown/MarkdownRenderer.php Recursive Tiptap JSON-to-Markdown renderer
src/Services/RichtextImageExtension.php Custom Tiptap node for richtext images with <figure>, copyright, and alt
Commands storyblok:install Scaffolds the project (config, directories, stubs, env variables)
storyblok:import-component Imports a component definition from a JSON file via the Management API
Blade views resources/views/components/bridge.blade.php Storyblok Bridge script with Idiomorph for real-time preview
resources/views/components/component.blade.php Component dispatcher — resolves blok names to user Blade components
resources/views/components/image.blade.php Responsive image component with srcset and Image Service transforms
resources/views/components/richtext.blade.php Renders a richtext field
Stubs stubs/StoryController.php.stub Example controller for fetching and displaying a story
stubs/story.blade.php.stub Example Blade view for rendering a story

Usage

Story class

The Story class extends Blok and wraps a raw story array from the Storyblok API. It provides convenience methods for common story fields:

use HiFolks\StoryblokLaravelHelpers\Story;

$story = Story::make($response->story);

$story->name();       // e.g. "My Page"
$story->slug();       // e.g. "my-page"
$story->fullSlug();   // e.g. "en/my-page"
$story->content();    // returns a Blok instance wrapping the content array

In your story.blade.php view:

<x-storyblok-layout :title="$story->name()">
    <x-storyblok::component :blok="$story->content()->toArray()" />
</x-storyblok-layout>

Since Story extends Blok, you can also use get(), has(), and toArray() for any story field:

$story->get('published_at');
$story->get('content.component');
$story->has('tag_list');

Component dispatcher (<x-storyblok::component>)

The component dispatcher maps a Storyblok blok to a Blade component in your application. Pass any blok array and it resolves the matching Blade view under resources/views/components/storyblok/:

<x-storyblok::component :blok="$story->content()->toArray()" />

For example, a blok with "component": "hero" renders resources/views/components/storyblok/hero.blade.php. Underscores in component names are converted to hyphens (image_text_sectionimage-text-section).

If no matching Blade component is found and app.debug is true, a placeholder with the missing component name is shown — it is also editable in the Visual Editor so you can still click on it to see which blok it refers to.

Use it recursively inside your own Blade components to render nested bloks:

{{-- resources/views/components/storyblok/page.blade.php --}}
@props(['blok'])

<main @storyblokEditable($blok)>
    @foreach ($blok->get('body', []) as $nestedBlok)
        <x-storyblok::component :blok="$nestedBlok" />
    @endforeach
</main>

Image component (<x-storyblok::image>)

Renders a responsive <img> tag with srcset and Storyblok Image Service transforms. Pass the Storyblok image asset array and the component automatically picks up filename, alt, title, and focus:

{{-- Basic usage --}}
<x-storyblok::image :image="$blok->get('image')" />

{{-- Custom widths for srcset breakpoints --}}
<x-storyblok::image :image="$blok->get('image')" :widths="[400, 800, 1200]" />

{{-- With aspect ratio (width/height) --}}
<x-storyblok::image :image="$blok->get('image')" :ratio="16/9" />

{{-- With smart cropping --}}
<x-storyblok::image :image="$blok->get('image')" :smart="true" />

{{-- Priority image (above the fold) --}}
<x-storyblok::image :image="$blok->get('image')" loading="eager" fetchpriority="high" />

Available props:

Prop Type Default Description
image array required Storyblok image asset (filename, alt, title, focus)
sizes string 100vw The sizes attribute for responsive images
widths array [400, 600, 800, 1200, 1600, 2000] Widths used to generate the srcset
ratio float|null null Aspect ratio (width/height), null = auto height
class string '' CSS class(es) for the <img> tag
loading string lazy Loading strategy (lazy or eager)
fetchpriority string|null null Fetch priority (high, low, auto)
quality int 80 JPEG quality (0–100)
smart bool false Enable smart cropping (face detection)

The src attribute uses the third width in the widths array (default 800) as the fallback image.

Richtext component (<x-storyblok::richtext>)

Renders a Storyblok richtext field to HTML. Nested bloks inside the richtext are resolved through the component dispatcher, so your Blade components are reused automatically:

<x-storyblok::richtext :content="$blok->get('body')" />

Images inside richtext are rendered as <figure> elements with <figcaption> (from alt) and <footer><small> (from copyright).

Richtext to Markdown

Use StoryblokRichtextMarkdown::render() to convert a Storyblok richtext field to Markdown instead of HTML. It accepts the same Tiptap JSON structure:

use HiFolks\StoryblokLaravelHelpers\Services\StoryblokRichtextMarkdown;

$markdown = StoryblokRichtextMarkdown::render($blok->get('body'));

Supported elements:

Element Markdown output
Paragraph text\n\n
Heading (level N) # text\n\n (# repeated per level)
Bold **text**
Italic *text*
Strikethrough ~~text~~
Inline code `text`
Link [text](url)
Bullet list - item\n
Ordered list 1. item\n
Blockquote > text\n\n
Code block ```lang\ncode\n```
Horizontal rule ---\n\n
Image ![alt](src)

Bridge component (<x-storyblok::bridge>)

Adds the Storyblok Bridge script for real-time visual editing. The script is only rendered when the request comes from the Visual Editor (detected via _storyblok query parameter). Place it before </body> in your layout:

<x-storyblok::bridge />

The bridge uses Idiomorph to morph the <main> element without a full page reload when content changes in the editor.

Editable attributes (Visual Editor)

Use the @storyblokEditable Blade directive to make components clickable/editable in the Visual Editor:

<div @storyblokEditable($blok)>
    {{-- component content --}}
</div>

This outputs data-blok-c and data-blok-uid attributes parsed from the _editable field that Storyblok injects when using draft content.

Image URL directive (@storyblokImageUrl)

Use the @storyblokImageUrl directive to build Storyblok Image Service URLs directly in Blade — useful for CSS background images or any place where you need the URL without the <img> tag. Pass the whole image array and the directive automatically picks up filename and focus:

{{-- Background image --}}
<div style="background-image: url('@storyblokImageUrl($blok->get('image'), width: 1200)')">

{{-- With height and quality --}}
<div style="background-image: url('@storyblokImageUrl($blok->get('image'), width: 800, height: 600, quality: 90)')">

{{-- With smart cropping --}}
<div style="background-image: url('@storyblokImageUrl($blok->get('image'), width: 800, height: 400, smart: true)')">

Draft mode middleware

Apply the middleware to your story routes so the Visual Editor always sees draft content:

use HiFolks\StoryblokLaravelHelpers\Http\Middleware\SetStoryblokDraftVersion;

Route::get('/{slug?}', [StoryController::class, 'show'])
    ->where('slug', '.*')
    ->middleware(SetStoryblokDraftVersion::class);

Facade

The Storyblok facade provides a convenient way to build StoriesApi instances and fetch stories:

use HiFolks\StoryblokLaravelHelpers\Facades\Storyblok;

// Build a StoriesApi with config defaults
$stories = Storyblok::makeStoriesApi();
$response = $stories->bySlug('home');

// Convenience methods (use config defaults)
$response = Storyblok::storyBySlug('home');
$response = Storyblok::storyById(new \Storyblok\Api\Domain\Value\Id(123));

// Override token or version
$stories = Storyblok::makeStoriesApi(token: 'other-token', version: 'draft');

// Use a specific region (see Regions below)
$stories = Storyblok::region('us')->makeStoriesApi();

// Region + overrides
$stories = Storyblok::region('us')->makeStoriesApi(token: 'us-token');

// Fully custom hostname (e.g. self-hosted)
$stories = Storyblok::makeStoriesApi(hostname: 'https://custom.endpoint.com/v2/cdn/');

Regions

Storyblok spaces can live in different regions. You can set the region globally via config or per-request via the facade.

Global (config/env):

Set STORYBLOK_HOSTNAME in your .env:

STORYBLOK_HOSTNAME=https://api-us.storyblok.com/v2/cdn/

Per-request (facade):

$stories = Storyblok::region('us')->makeStoriesApi();

Available region codes:

Code Hostname
eu https://api.storyblok.com/v2/cdn/ (default)
us https://api-us.storyblok.com/v2/cdn/
ca https://api-ca.storyblok.com/v2/cdn/
ap https://api-ap.storyblok.com/v2/cdn/
cn https://app.storyblokchina.cn/v2/cdn/

Priority order: explicit hostname parameter > region() > config storyblok.hostname > EU default.

Testing

composer test

Or directly:

vendor/bin/phpunit

License

MIT

About

Storyblok integration for Laravel. It provides bridge, visual editor, image service, richtext rendering, and component dispatcher.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors