Created
May 17, 2026 07:18
-
-
Save roberto-butti/019a0ae8a0a3edab9e829e9e37e56546 to your computer and use it in GitHub Desktop.
Storyblok PHP Management API example: create a typed component schema, upload an asset, create a story, and move it to a workflow stage with a due date.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| /** | |
| * Tutorial script: creates an `article-example` content type, uploads an image, | |
| * creates a story using that component, and moves the story to the configured | |
| * workflow stage with a due date when the stage exists. | |
| * | |
| * Requirements: | |
| * - `vlucas/phpdotenv` installed, because this script loads configuration from `.env`. | |
| * - `SECRET_KEY`: Storyblok Management API personal access token. | |
| * - `STORYBLOK_SPACE_ID`: Storyblok space ID where the component, asset, and story are created. | |
| * - `STORYBLOK_EXAMPLE_IMAGE_PATH`: optional path to the image to upload. | |
| * - `STORYBLOK_WORKFLOW_STAGE_NAME`: optional workflow stage name, defaults to `Reviewing`. | |
| * - `STORYBLOK_WORKFLOW_DUE_DATE`: optional due date, defaults to seven days from today. | |
| */ | |
| use Dotenv\Dotenv; | |
| use Storyblok\ManagementApi\Data\Component; | |
| use Storyblok\ManagementApi\Data\Fields\RichtextField; | |
| use Storyblok\ManagementApi\Data\Fields\Schema\FieldAsset; | |
| use Storyblok\ManagementApi\Data\Fields\Schema\FieldOption; | |
| use Storyblok\ManagementApi\Data\Fields\Schema\FieldRichtext; | |
| use Storyblok\ManagementApi\Data\Fields\Schema\FieldText; | |
| use Storyblok\ManagementApi\Data\Story; | |
| use Storyblok\ManagementApi\Data\StoryComponent; | |
| use Storyblok\ManagementApi\Data\WorkflowStageChange; | |
| use Storyblok\ManagementApi\Data\WorkflowStageData; | |
| use Storyblok\ManagementApi\Endpoints\AssetApi; | |
| use Storyblok\ManagementApi\Endpoints\ComponentApi; | |
| use Storyblok\ManagementApi\Endpoints\StoryApi; | |
| use Storyblok\ManagementApi\Endpoints\WorkflowStageChangeApi; | |
| use Storyblok\ManagementApi\Endpoints\WorkflowStageApi; | |
| use Storyblok\ManagementApi\ManagementApiClient; | |
| require __DIR__ . "/../vendor/autoload.php"; | |
| // Load local environment variables from the test project .env file. | |
| $dotenv = Dotenv::createImmutable(__DIR__ . "/../"); | |
| $dotenv->load(); | |
| function envString(string $key, ?string $default = null): string | |
| { | |
| if (array_key_exists($key, $_ENV) && $_ENV[$key] !== "") { | |
| $value = $_ENV[$key]; | |
| return $value; | |
| } | |
| if ($default !== null) { | |
| return $default; | |
| } | |
| fwrite(STDERR, "Missing required environment variable: {$key}" . PHP_EOL); | |
| exit(1); | |
| } | |
| function findComponentByName( | |
| ComponentApi $componentApi, | |
| string $name, | |
| ): ?Component { | |
| // The script should be idempotent for the tutorial: if the content type | |
| // already exists, we stop before creating assets or stories. | |
| foreach ($componentApi->all()->data() as $component) { | |
| if ($component instanceof Component && $component->name() === $name) { | |
| return $component; | |
| } | |
| } | |
| return null; | |
| } | |
| function findWorkflowStageByName( | |
| WorkflowStageApi $workflowStageApi, | |
| string $name, | |
| ): ?WorkflowStageData { | |
| // Workflow stage names are configured in the Storyblok UI. We search by | |
| // name and then require an exact match before applying the stage change. | |
| $stages = $workflowStageApi->list(search: $name)->data(); | |
| if (!$stages instanceof Traversable) { | |
| return null; | |
| } | |
| foreach ($stages as $stage) { | |
| if ($stage instanceof WorkflowStageData && $stage->name() === $name) { | |
| return $stage; | |
| } | |
| } | |
| return null; | |
| } | |
| // Configuration used by the tutorial. Required values come from .env; optional | |
| // values have defaults so the script can be run without editing the code. | |
| $token = envString("SECRET_KEY"); | |
| $spaceId = envString("STORYBLOK_SPACE_ID"); | |
| $imagePath = envString( | |
| "STORYBLOK_EXAMPLE_IMAGE_PATH", | |
| __DIR__ . "/../assets/creative-workspace-with-abstract-art.jpeg", | |
| ); | |
| $storyName = envString("STORYBLOK_STORY_NAME", "Article Example Story"); | |
| $storySlug = envString( | |
| "STORYBLOK_STORY_SLUG", | |
| "article-example-story-" . date("YmdHis"), | |
| ); | |
| $workflowStageName = envString("STORYBLOK_WORKFLOW_STAGE_NAME", "Reviewing"); | |
| $workflowDueDate = envString( | |
| "STORYBLOK_WORKFLOW_DUE_DATE", | |
| new DateTimeImmutable("+7 days")->format("Y-m-d"), | |
| ); | |
| // Fail early if the image cannot be found. Asset upload is the first operation | |
| // after the component is created, so this keeps errors easy to understand. | |
| if (!is_file($imagePath)) { | |
| fwrite(STDERR, "Image file not found: {$imagePath}" . PHP_EOL); | |
| exit(1); | |
| } | |
| // Create the Management API client and the endpoint-specific APIs used below. | |
| $client = new ManagementApiClient( | |
| personalAccessToken: $token, | |
| shouldRetry: true, | |
| ); | |
| $componentApi = new ComponentApi($client, $spaceId); | |
| $assetApi = new AssetApi($client, $spaceId); | |
| $storyApi = new StoryApi($client, $spaceId); | |
| $workflowStageApi = new WorkflowStageApi($client, $spaceId); | |
| $componentName = "article-example"; | |
| // 1. Stop if the content type already exists. | |
| if (findComponentByName($componentApi, $componentName) instanceof Component) { | |
| echo "Component '{$componentName}' already exists. Nothing created." . | |
| PHP_EOL; | |
| exit(0); | |
| } | |
| // 2. Create the content type schema with the typed field helpers. | |
| // Component::contentType() sets the Storyblok flags for a content type | |
| // component, while appendFields() assigns the field positions in order. | |
| echo "Creating component '{$componentName}'..." . PHP_EOL; | |
| $component = Component::contentType($componentName) | |
| ->setDisplayName("Article Example") | |
| ->setPreviewField("headline") | |
| ->appendFields([ | |
| FieldText::make("headline")->setDisplayName("Headline")->setRequired(), | |
| FieldAsset::make("image")->setDisplayName("Image"), | |
| FieldRichtext::make("text")->setDisplayName("Text"), | |
| FieldOption::make("category") | |
| ->setDisplayName("Category") | |
| ->addOption("Developers", "developers") | |
| ->addOption("Marketers", "marketers") | |
| ->addOption("Editors", "editors"), | |
| ]); | |
| $createdComponent = $componentApi->create($component)->data(); | |
| echo "Created component: " . $createdComponent->name() . PHP_EOL; | |
| // 3. Upload an image that will be assigned to the story asset field. | |
| echo "Uploading image..." . PHP_EOL; | |
| $asset = $assetApi->upload($imagePath)->data(); | |
| echo "Uploaded asset ID: " . $asset->id() . PHP_EOL; | |
| // 4. Create the story content. StoryComponent::makeComponent() is used when | |
| // building new content programmatically. The typed richtext helper creates the | |
| // correct Storyblok richtext JSON shape. | |
| echo "Creating story '{$storyName}'..." . PHP_EOL; | |
| $content = StoryComponent::makeComponent($componentName) | |
| ->set("headline", "Automating Storyblok with PHP") | |
| ->setAsset("image", $asset) | |
| ->setRichtext( | |
| "text", | |
| RichtextField::paragraph( | |
| "This story was created from the PHP Management API client.", | |
| ), | |
| ) | |
| ->set("category", "developers"); | |
| $story = new Story(name: $storyName, slug: $storySlug, content: $content); | |
| $createdStory = $storyApi->create($story)->data(); | |
| echo "Created story ID: " . $createdStory->id() . PHP_EOL; | |
| // 5. Move the story to the configured workflow stage when it exists. | |
| // If your space does not have a "Reviewing" stage, the script leaves the story | |
| // created and exits with a clear message. | |
| $reviewingStage = findWorkflowStageByName( | |
| $workflowStageApi, | |
| $workflowStageName, | |
| ); | |
| if (!$reviewingStage instanceof WorkflowStageData) { | |
| echo "Workflow stage '{$workflowStageName}' not found. Story was created without workflow stage change." . | |
| PHP_EOL; | |
| exit(0); | |
| } | |
| // The due date is included in the workflow stage change payload. | |
| echo "Moving story to workflow stage '{$workflowStageName}' with due date {$workflowDueDate}..." . | |
| PHP_EOL; | |
| $workflowStageChange = WorkflowStageChange::makeFromParams( | |
| storyId: (int) $createdStory->id(), | |
| workflowStageId: (int) $reviewingStage->id(), | |
| dueDate: $workflowDueDate, | |
| ); | |
| $workflowStageChangeApi = new WorkflowStageChangeApi($client, $spaceId); | |
| $workflowStageChangeApi->create($workflowStageChange); | |
| echo "Workflow stage change created." . PHP_EOL; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment