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

Php Management Api Client Laravel Package

storyblok/php-management-api-client

A PHP SDK for Storyblok's Management API that lets you manage spaces, stories, components, and assets programmatically from Laravel or any PHP app, simplifying authenticated requests to Storyblok's headless CMS endpoints.

Deep Wiki
Context7

Getting Started

Install and authenticate. Two different Storyblok tokens exist and mixing them up is the single most common first-day failure:

  • Content Delivery / Preview token (public, per-space) → used by storyblok/php-client for rendering content. Useless here.
  • Management API personal access token (per-user, account settings → Personal access tokens) → what this package needs. It grants write access to every space that user can reach, so treat it as a secret.
composer require storyblok/php-management-api-client
STORYBLOK_MANAGEMENT_TOKEN=...
STORYBLOK_SPACE_ID=123456

The space ID is the numeric ID from Settings → General in the space, not the space slug (my-space). Almost every endpoint is scoped to it.

Look here first. The package is small (13 stars, thin wrapper). On install, spend five minutes reading src/ — specifically the client constructor and one resource class — before writing code. That tells you: does it hold the space ID internally or require it per call, does it return raw arrays or a response object, and does it expose the underlying Guzzle instance. The method names in the examples below are indicative; confirm them there.

First use case — verify auth and shape with a read-only script:

$client = new Client(config('services.storyblok.management_token'));

$space   = $client->spaces()->get($spaceId);      // proves the token works
$stories = $client->stories()->all($spaceId);    // proves scoping + pagination shape

dump($space, array_slice($stories, 0, 2));

Once that prints, you have everything you need to judge how much wrapping it deserves.

Implementation Patterns

Bind it once, never new it inline. In a Laravel app, register a singleton in a service provider and inject it. Configure timeouts and retries there, not at call sites:

$this->app->singleton(Client::class, fn () => tap(
    new Client(config('services.storyblok.management_token')),
    fn ($c) => $c->setSpaceId(config('services.storyblok.space_id')),
));

If the package allows access to the Guzzle client, add a middleware that logs method + path + status + duration. You will need this the first time a bulk operation half-fails.

Use it for admin and build-time work, not page rendering. The natural fits are: content migration from a legacy CMS, bulk publish/unpublish across a release, generating stories from a product feed, seeding a staging space from production, and schema-as-code in CI. If you find yourself calling it inside a web request that renders a page, you want the CDN client instead.

Wrap bulk reads in a generator. Pagination is page-based with a hard per_page cap (typically 100). Don't hand-roll the loop at every call site:

function allStories(Client $client, int $spaceId): Generator
{
    for ($page = 1; ; $page++) {
        $batch = $client->stories()->all($spaceId, ['page' => $page, 'per_page' => 100]);
        if (empty($batch)) {
            return;
        }
        yield from $batch;
        if (count($batch) < 100) {
            return; // last page
        }
    }
}

Make importers idempotent and keyed on full_slug, not slug. Fetch the existing story set once into a ['full_slug' => id] map, then create-or-update per record. This turns a re-runnable migration into a two-line change and saves you from duplicate stories every time the job is retried. Compare full_slug because slug collides across folders.

Push the work onto the queue with a rate limiter. Bulk write jobs should be chunked (Bus::batch over 100-item chunks), with a Redis::throttle('storyblok-mgmt')->allow(5)->every(1) guard or a job middleware. A few thousand creates fired in a loop will get you throttled and produce a partially-migrated space.

Component schemas are the sharp edge of CI usage. Components are JSON blobs; storing one file per component in the repo and diffing on deploy is genuinely valuable, but see the gotchas below.

Testing. If it's Guzzle underneath, inject a MockHandler and assert on the request path rather than mocking the client class — you get free coverage of your own wrapper logic. If not, put the client behind your own StoryblokAdmin interface and fake that.

Gotchas and Tips

  • Token blast radius. A management token can modify every space the user owns. Never load it in a front-end build, a shared CI variable visible to PRs from forks, or a .env you push to staging. Rotate it in Personal access tokens and expect old tokens to die instantly.
  • Space ID placement. If the client doesn't store it, forgetting to pass it gives a 404 rather than a 401, which reads like "wrong endpoint" and wastes an hour.
  • Response shape is inconsistent across endpoints. Some return ['story' => [...]], some return a bare list. Dump array_keys() once per resource method rather than assuming.
  • Publish is separate from save. publish: 1 on a create is not the same as publishing an existing draft; there's a distinct publish action. Also remember edits go to the draft version — code that "updates" a story without publishing leaves the live site unchanged.
  • full_slug is derived. Moving a parent folder silently rewrites children's full_slugs. Anything you key on full_slug needs re-syncing after structural changes.
  • Component field removal is destructive. Deleting a field from a component schema orphans data in every story using it. Add, deprecate, wait, then remove — never in one deploy.
  • Field ordering (pos) matters for the editor UI, and duplicate pos values produce a confusing editor. If you generate schemas in code, assign pos explicitly.
  • Languages/alternates. A story with lang set is an alternate; creating the same slug in another language is a separate story with translated_slugs links. Bulk logic that ignores lang will overwrite translations.
  • Debugging failures. Log the raw response body on 4xx. 422 bodies usually name the offending field; 403 is almost always token scope, not endpoint permissions; 429 means you should slow down rather than retry immediately.
  • Extension points. If the wrapper exposes the HTTP client, use it for endpoints the package hasn't caught up with yet — the Management API ships new resources faster than a 13-star package does.
  • Vendor risk is real. This is a thin, low-traffic wrapper; it may lag API changes or be unmaintained for stretches. Pin the version in composer.json, and keep all calls behind a single service class so swapping to raw Guzzle/Saloon later is a one-file change rather than a grep across the codebase.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle