AI News Hub Logo

AI News Hub

Automating Storyblok with PHP: A Practical Guide to the Management API Client

DEV Community
Roberto B.

Learn how to use the Storyblok PHP Management API Client to automate stories, assets, workflows, references, CSV imports, and content operations in Storyblok. Storyblok gives developers and content teams a visual CMS, structured content, and APIs for delivery and management. Most projects start with the Content Delivery API, which is used to read published or draft content and render it in a website, app, or another digital channel. The Management API solves a different problem. It allows you to create, update, organize, publish, and manage content programmatically. In this article, we will look at how the Storyblok PHP Management API Client can help automate common operations in a Storyblok space, including stories, assets, folders, references, workflow stages, and bulk imports. Many content operations are repetitive. They are easy to handle manually when you only need to change a few entries, but they become slow and error-prone when the same operation has to run across many records. For example, you may need to automate: Creating many stories from a CSV file Uploading a folder of images Updating metadata for existing assets Adding references between stories Moving stories through workflow stages Publishing content after an import Applying the same content change to many entries The Management API is useful in these cases because it lets you automate editorial and operational tasks while keeping Storyblok as the system where content is managed. Instead of repeating the same steps in the UI, you can write a script, review it, test it, and run it again when needed. The storyblok/php-management-api-client package is a PHP client for the Storyblok Management API. It wraps Management API endpoints in PHP classes and typed data objects, which makes scripts easier to read and safer to maintain than raw HTTP requests. You can install it with Composer: composer require storyblok/php-management-api-client A basic client starts with a Personal Access Token: load(); $storyblokPersonalAccessToken = $_ENV['SECRET_KEY']; $client = new ManagementApiClient( personalAccessToken: $storyblokPersonalAccessToken, shouldRetry: true, ); The shouldRetry option enables retry handling for 429 Too Many Requests responses, which is useful for imports and batch operations where many requests are sent in sequence. The client provides dedicated classes for common resources. For example: StoryApi for stories AssetApi for assets AssetFolderApi for asset folders ComponentApi for components WorkflowApi for workflows WorkflowStageApi for workflow stages WorkflowStageChangeApi for moving stories between workflow stages TagApi and InternalTagApi for tags UserApi for current user information For endpoints that do not have a dedicated class yet, you can use the generic ManagementApi class. This gives you direct access to get, post, put, and delete methods: use Storyblok\ManagementApi\Endpoints\ManagementApi; $managementApi = new ManagementApi($client); $response = $managementApi->put("spaces/{$spaceId}/assets/{$assetId}", [ "asset" => [ "meta_data" => [ "title" => "Updated title", ], ], ]); This mixed approach is practical: use typed APIs when they exist, and use ManagementApi when you need lower-level control or access to an endpoint that is not wrapped yet. One common automation task is creating stories from another source. The client uses the Story and StoryComponent classes to build the payload in the same structure expected by Storyblok. use Storyblok\ManagementApi\Data\Story; use Storyblok\ManagementApi\Data\StoryComponent; use Storyblok\ManagementApi\Endpoints\StoryApi; $storyApi = new StoryApi($client, $spaceId); $content = new StoryComponent("article-page"); $content->set("headline", "My New Article"); $story = new Story( name: "My New Article", slug: "my-new-article", content: $content, ); $createdStory = $storyApi->create($story)->data(); echo "Created story: " . $createdStory->id() . PHP_EOL; You can also publish immediately: $createdStory = $storyApi->create($story, publish: true)->data(); This is useful for migrations, seed scripts, and integration jobs where content should be available as soon as it is created. For larger imports, avoid loading the full file into memory. A generator can stream rows one by one, so the script can process large files without building a big in-memory array first: function csvGenerator(string $filePath, string $delimiter = ";"): Generator { $file = new SplFileObject($filePath); $file->setFlags( SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE, ); $file->setCsvControl($delimiter, escape: "\\"); foreach ($file as $row) { if (!$row || count($row) create($story); echo "Created story: {$slug}\n"; } catch (Throwable $e) { echo "Failed to create story '{$slug}': {$e->getMessage()}\n"; } } This pattern has two benefits. Memory usage stays low, and a failed row does not stop the whole import. Assets can also be automated. For example, you can upload all files from a local folder: use Storyblok\ManagementApi\Endpoints\AssetApi; $assetApi = new AssetApi($client, $spaceId); $directory = __DIR__ . "/assets/"; $files = glob($directory . "*"); foreach ($files as $pathfilename) { try { $response = $assetApi->upload($pathfilename); if ($response->isOk()) { $asset = $response->data(); echo "Uploaded asset ID: " . $asset->id() . PHP_EOL; } else { echo $response->getErrorMessage() . PHP_EOL; } } catch (Throwable $e) { echo "Error uploading {$pathfilename}: {$e->getMessage()}" . PHP_EOL; } } This can help during migrations from another CMS, content refreshes, or media library cleanup. You can also use uploaded or existing assets inside story fields: $asset = $assetApi->get($assetId)->data(); $content = new StoryComponent("article-page"); $content->set("headline", "Article with image"); $content->setAsset("image", $asset); $story = new Story( name: "Article with image", slug: "article-with-image", content: $content, ); $storyApi->create($story, publish: true); The client converts the asset object into the field format expected by Storyblok. To update a story, retrieve it first, change the fields you need, then send it back. $story = $storyApi->get($storyId)->data(); $story->set( "content.test_edit_story_updated_at", (new DateTimeImmutable())->format(DateTimeInterface::ATOM), ); $updatedStory = $storyApi->update($storyId, $story)->data(); echo "Updated story: " . $updatedStory->id() . PHP_EOL; The set() method supports dot notation, which makes nested updates simple: $story->set("content.headline", "Updated headline"); This approach is useful when you want to preserve the existing story payload and change only selected fields. Storyblok reference fields often use UUIDs, not numeric story IDs. This matters when you update a multi-option or multi-reference field. use Storyblok\ManagementApi\QueryParameters\StoriesParams; $stories = $storyApi ->page( new StoriesParams( bySlugs: ["categories/technology", "categories/development"], ), ) ->data(); $categoryUuid = $stories->get("0.uuid"); $story = $storyApi->get($articleId)->data(); $story->set("content.categories", [$categoryUuid]); $updatedStory = $storyApi->update($articleId, $story)->data(); The important detail is the array: $story->set("content.categories", [$categoryUuid]); Even if you set only one value, Storyblok expects an array for multi-reference fields. Workflows are often part of editorial governance. For automation, you may need to move a story to a specific stage or temporarily update a stage setting. To move a story to a stage, first find the workflow and stage, then create a stage change: use Storyblok\ManagementApi\Data\WorkflowStageChange; use Storyblok\ManagementApi\Endpoints\WorkflowApi; use Storyblok\ManagementApi\Endpoints\WorkflowStageApi; use Storyblok\ManagementApi\Endpoints\WorkflowStageChangeApi; $workflowApi = new WorkflowApi($client, $spaceId); $stageApi = new WorkflowStageApi($client, $spaceId); $changeApi = new WorkflowStageChangeApi($client, $spaceId); $workflows = $workflowApi->list()->data(); $workflowId = null; foreach ($workflows as $workflow) { if ($workflow->name() === "Default") { $workflowId = $workflow->id(); break; } } $stages = $stageApi->list($workflowId)->data(); $stageId = null; foreach ($stages as $stage) { if ($stage->name() === "Drafting") { $stageId = $stage->id(); break; } } $change = new WorkflowStageChange(); $change->setStoryAndStage( storyId: $storyId, workflowStageId: (int) $stageId, ); $changeApi->create($change); This is useful when imports need to place content in a review queue instead of publishing directly. Some workflows lock story editing at a specific stage. In that case, a normal story update may fail. One option is to temporarily unlock the workflow stage, update the story, and lock the stage again. use Storyblok\ManagementApi\Endpoints\StoryApi; use Storyblok\ManagementApi\Endpoints\WorkflowStageApi; $storyApi = new StoryApi($client, $spaceId); $workflowStageApi = new WorkflowStageApi($client, $spaceId); $story = $storyApi->get($storyId)->data(); $workflowStageId = $story->workflowStageId(); $workflowStage = $workflowStageApi->get($workflowStageId)->data(); $workflowStageWasLocked = $workflowStage->getBoolean("story_editing_locked"); try { if ($workflowStageWasLocked) { $workflowStage->set("story_editing_locked", false); $workflowStageApi->update($workflowStageId, $workflowStage); } $story->set("content.headline", "Updated headline"); $storyApi->update($storyId, $story); } finally { if ($workflowStageWasLocked) { $workflowStage = $workflowStageApi->get($workflowStageId)->data(); $workflowStage->set("story_editing_locked", true); $workflowStageApi->update($workflowStageId, $workflowStage); } } The finally block is important because it restores the workflow stage even if the story update fails. Use this pattern carefully. A workflow lock usually exists for a reason, so this kind of script should be limited to controlled operations, such as migrations or administrative fixes. Automation often starts with finding the right content. The client supports query parameters and filters, so you can search stories by a content field: use Storyblok\ManagementApi\QueryParameters\Filters\Filter; use Storyblok\ManagementApi\QueryParameters\Filters\QueryFilters; $stories = $storyApi ->page( queryFilters: new QueryFilters()->add( new Filter("headline", "like", "Development"), ), ) ->data(); You can use this to build targeted scripts: Update all stories with a specific field value Find stories using an old component Assign references based on slugs Prepare content for review Generate reports for editors The PHP Management API Client helps teams automate operational work around content. For developers, it provides: PHP classes instead of raw HTTP calls Typed data objects for stories, assets, workflows, and other resources Cleaner scripts for migrations and integrations Retry handling for rate limits A generic fallback for endpoints that need lower-level access For content and operations teams, it helps with: Faster migrations Fewer manual updates More consistent content structures Easier bulk changes Better control over workflow transitions The impact is not only technical. Automation reduces repetitive work and makes large content operations easier to review, test, and repeat. The client is useful in many real projects. For example: Importing content from CSV, XML, or another CMS Uploading and tagging media assets Creating folder structures for new markets or brands Adding references between articles and categories Updating SEO fields across many stories Moving imported stories to a workflow stage Publishing selected stories after validation Creating components or updating component schemas Running cleanup scripts for assets or tags These scripts can run locally, in CI, or as part of an internal tool. The same client can support one-off migration work and recurring operational jobs. The Storyblok PHP Management API Client gives PHP teams a practical way to automate content management tasks. It is useful when work moves beyond reading content and into managing the space itself: creating stories, uploading assets, updating fields, handling references, and working with workflows. By using typed APIs where possible and the generic ManagementApi when needed, teams can build scripts that are clear, repeatable, and easier to maintain. For projects with many entries, frequent imports, or structured editorial workflows, this can save time and reduce manual errors while keeping Storyblok as the central place for content operations.