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 Laravel Package

21torr/storyblok

API helpers and infrastructure for working with Storyblok from Symfony, simplifying content delivery and management.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the reality check: this is a Symfony bundle, not a Laravel package. It ships a Symfony bundle class, DI compiler passes, config tree, and (as of 5.3.0) a dependency on the 21TORR Hosting Bundle. There is no service provider, no Blade integration, no config/storyblok.php. If your day job is Laravel, you will not composer require this into an app and get value — you'd adopt it only if you also run a Symfony service (or are willing to stand one up as a content sidecar).

Assuming a Symfony context:

  1. Check the runtime gate before anything else. v5.3.0 requires PHP 8.5+ and Symfony 8.0+. That rules out most current production stacks. If you're below that, you're on the parallel-maintained 3.21.x line — pin accordingly and read the UPGRADE doc referenced in the 5.0.0 release notes, not the release blog posts.
  2. Install and register the bundle, then go straight to the external docs at 21torr-docs.pages.dev/docs/php/symfony/storyblok/. The README is a stub pointing there; there is no in-repo API reference. Budget time to read source and tests for anything the docs gloss over — with ~5 stars and 0 dependents, Stack Overflow will not save you.
  3. Understand the v5 mental model: adapters. The single most important change in 5.0.0 is that global bundle config and the global ContentApi/ManagementApi services were removed. Everything is now scoped to an adapter class extending AbstractStoryblokAdapter, identified by a key. Create exactly one adapter per space/environment combination (e.g. marketing_preview, marketing_live) and inject the adapter-scoped services.
  4. Pick your first use case: one adapter, ContentApi fetch of a single story by slug, and render it through your normalized component classes (see next section). Don't start with the Management API or definitions sync — get read-path rendering working first.
  5. Then wire definitions sync. The sync command pulls component definitions from Storyblok; 5.3.0 also registers this as an app deploy hook. That's what keeps your PHP component/field definitions honest against what editors actually built.

Implementation Patterns

One adapter per space/environment, aliased by key. Adapter keys must be valid "snails" (there's an app validator that fails the container build otherwise), and since 5.1.1 the adapter key — not the space ID — is the webhook token. Keep a preview adapter strictly separate from the live one so draft content can never leak into production rendering; the adapter boundary is your safety rail, not a runtime flag.

Model content as typed component classes, not arrays. This is where the package actually pays for itself. Define a PHP class per Storyblok component, declare its fields with the bundle's field types, and let ComponentNormalizer hydrate JSON into typed objects:

  • NumberField — minValue / maxValue / decimals / steps, validated at hydration
  • ChoiceField — single and multi-select, honours allowMissingData
  • RichTextField — allowLinksOpeningInNewWindow, allow_target_blank, allow_custom_attributes
  • LinkField — allow_target_blank, allow_custom_attributes, fullData in every variant
  • MarkdownField — allow_multiline
  • BooleanField — inline_label
  • AssetField — useDescriptionAsAlt

The leverage is that field config doubles as validation. Configure the range once and every render gets a properly typed value or a loud failure, instead of $block['price'] ?? 0 scattered across templates.

Definitions sync belongs in deploy, not in a request. Use the sync command in your deploy hook and subscribe to StoryblokDefinitionsSyncedEvent to clear caches / warm whatever you derive from the definitions. Treat the synced definitions as build artifacts.

Read path: prefer the fast endpoints and explicit content versions. ContentApi::fetchFoldersInPath() and fetchFolderTitleMap() use a faster endpoint and replaced the ManagementApi equivalents (deprecated in 5.2.0) — migrate if you have older code. When fetching single stories, pass cv explicitly; multiple patch releases exist purely because omitting it caused redirects and extra requests. Query params are sorted automatically now to avoid the same problem.

Asset handling through the proxy. AssetProxyUrlGenerator plus the extracted Storyblok asset URL parser service/DTO keep your access token out of templates and let you serve private/signed assets. If you're currently pasting a.storyblok.com URLs into markup, move to the proxy — it's the difference between a leaked token and not.

Routing and identity. StoryblokIdSlugMapper handles id↔slug resolution for routing. Pair it with the 5.1.2 change: all IDs from Storyblok are strings, so type your DTOs and route params as string and never cast to int.

Management API for write/edit tooling. fetchAllAssets() for asset audits, updateStory() with an array payload (the JSON-string form is deprecated), and sendRequest() is public since 5.1.0 for endpoints the bundle hasn't wrapped yet. Use sendPaginatedRequest() rather than hand-rolling pagination — 5.1.4 fixed invalid pagination handling there.

If you're translating this to Laravel: lift the pattern, not the code. A Storyblok service bound as a singleton per space, one hydrator/DTO class per component with a validation rules array mirroring the field config, a storyblok:sync-definitions artisan command in your deploy script, and an asset proxy route. That gets you ~80% of the value; the remaining 20% is the definitions diffing, which is genuinely fiddly to rebuild.

Gotchas and Tips

  • The framework mismatch is the headline. No Laravel support, no framework-agnostic core exposed. With 0 dependents and 5 stars, you are the first person to hit most edge cases — expect to read source, not docs.
  • Every pre-5.0 tutorial is wrong. Global config and global ContentApi/ManagementApi are gone. Any snippet that injects a global ContentApi or configures storyblok: at the bundle root is v3-era. Look for adapter-scoped injection.
  • Webhook token changed in 5.1.1 from space ID to adapter key. If you upgraded across that boundary and webhooks "just stopped", this is why. Nothing logs loudly about it.
  • Adapter keys must be valid snails or the app validator fails at container build. That's actually a good failure — but it means renaming an adapter key is a webhook-token change too.
  • Long-running workers: state reset has bitten this package. 5.2.6 is literally "Fix missing reset of ContentApi and StoryblokIdSlugMapper." If you're on Messenger workers or anything with a services-resetter, verify the reset actually fires for your adapter services, and regression-test after upgrading.
  • Date times are UTC. DateTimeField was fixed to parse as UTC; do the timezone conversion at your presentation edge and don't assume the field gives you local time.
  • NumberField validation is finicky. Floating-point step checks were fixed twice (5.2.2 line and 5.3.2), steps <= 0 now rejects instead of dividing by zero, default values are normalized in 5.3.1, and negative minValue only works from 5.3.4 — where the range is also validated at hydration time. Pick a version deliberately and add a test story per configured range.
  • Slugs have trailing slashes trimmed (5.2.8). If your route lookups or cache keys embed the raw slug, normalize on your side too.
  • ID types changed to strings in 5.1.2 — check every int type hint on a Storyblok-sourced ID, including folder data (FolderData only gained id/uuid in 5.3.3, so older code may not surface them at all).
  • Use the debug command and the app validator first when something isn't wired. With sparse docs, these are your fastest feedback loop. Inspect the adapter-scoped container to confirm which services exist after a config change.
  • Version pinning is non-negotiable here. The 3.21.x and 5.x lines are both maintained and both receive the same bugfixes (see the duplicate "Parse DateTimeFields as UTC" and "explicit cv" entries in both). Decide which line you're on by runtime, pin exactly, and read UPGRADE on every major bump.
  • Upstream is responsive. Frequent patch releases, small PRs by named maintainers. File issues with reproduction — you won't get community answers given the adoption level.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource