storyblok / php-content-api-client
PHP Client for Storyblok Content API
Installs: 33 937
Dependents: 2
Suggesters: 0
Security: 0
Stars: 4
Watchers: 8
Forks: 2
Open Issues: 0
Requires
- php: >=8.3
- oskarstark/enum-helper: ^1.5
- oskarstark/trimmed-non-empty-string: ^1.1
- psr/log: ^3.0
- symfony/cache: ^7.0
- symfony/cache-contracts: ^3.5
- symfony/http-client: ^6.0 || ^7.0
- thecodingmachine/safe: ^2.0 || ^3.0
- webmozart/assert: ^1.11
Requires (Dev)
- ergebnis/composer-normalize: ^2.2
- ergebnis/data-provider: ^3.2
- ergebnis/php-cs-fixer-config: ^6.28
- ergebnis/test-util: ^1.5
- phpstan/extension-installer: ^1.0
- phpstan/phpstan: ^2.0
- phpstan/phpstan-webmozart-assert: ^2.0
- phpunit/phpunit: ^11.5
- thecodingmachine/phpstan-safe-rule: ^1.4
README

Storyblok Content Delivery API Client
Co-created with SensioLabs, the creators of Symfony.
Branch | PHP | Code Coverage |
---|---|---|
master |
Symfony
Use the storyblok/storyblok-bundle to integrate this library into your Symfony application.
Usage
Installation
composer require storyblok/php-content-api-client
Setup
use Storyblok\Api\StoryblokClient; $client = new StoryblokClient( baseUri: 'https://api.storyblok.com', token: '***********', timeout: 10 // optional ); // you can now request any endpoint which needs authentication $client->request('GET', '/api/something', $options);
Spaces
In your code you should type-hint to Storyblok\Api\SpacesApiInterface
Get the current space
Returns the space associated with the current token.
use Storyblok\Api\SpacesApi; use Storyblok\Api\StoryblokClient; $client = new StoryblokClient(/* ... */); $spacesApi = new SpacesApi($client); $response = $spacesApi->me();
Stories
In your code you should type-hint to Storyblok\Api\StoriesApiInterface
Get all available stories
use Storyblok\Api\StoriesApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Request\StoriesRequest; $client = new StoryblokClient(/* ... */); $storiesApi = new StoriesApi($client); $response = $storiesApi->all(new StoriesRequest(language: 'de'));
Fetch by Version (draft
, published
)
Global
use Storyblok\Api\StoriesApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Domain\Value\Dto\Version; use Storyblok\Api\Request\StoryRequest; $client = new StoryblokClient(/* ... */); $storiesApi = new StoriesApi($client, Version::Draft); $response = $storiesApi->bySlug('/my-story/', new StoryRequest( language: 'de', ));
Method Call
use Storyblok\Api\StoriesApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Domain\Value\Dto\Version; use Storyblok\Api\Request\StoryRequest; $client = new StoryblokClient(/* ... */); $storiesApi = new StoriesApi($client, Version::Published); $response = $storiesApi->bySlug('/my-story/', new StoryRequest( language: 'de', version: Version::Draft, // This overrides the global "version" ));
Pagination
use Storyblok\Api\StoriesApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Domain\Value\Dto\Pagination; use Storyblok\Api\Request\StoriesRequest; $client = new StoryblokClient(/* ... */); $storiesApi = new StoriesApi($client); $response = $storiesApi->all(new StoriesRequest( language: 'de', pagination: new Pagination(page: 1, perPage: 30) ));
Sorting
use Storyblok\Api\StoriesApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Domain\Value\Dto\SortBy; use Storyblok\Api\Domain\Value\Dto\Direction; use Storyblok\Api\Request\StoriesRequest; $client = new StoryblokClient(/* ... */); $storiesApi = new StoriesApi($client); $response = $storiesApi->all(new StoriesRequest( language: 'de', sortBy: new SortBy(field: 'title', direction: Direction::Desc) ));
Filtering
use Storyblok\Api\StoriesApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Domain\Value\Filter\FilterCollection; use Storyblok\Api\Domain\Value\Dto\Direction; use Storyblok\Api\Domain\Value\Filter\Filters\InFilter; use Storyblok\Api\Request\StoriesRequest; $client = new StoryblokClient(/* ... */); $storiesApi = new StoriesApi($client); $response = $storiesApi->all(new StoriesRequest( language: 'de', filters: new FilterCollection([ new InFilter(field: 'single_reference_field', value: 'f2fdb571-a265-4d8a-b7c5-7050d23c2383') ]) ));
Available filters
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\AllInArrayFilter; new AllInArrayFilter(field: 'tags', value: ['foo', 'bar', 'baz']);
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\AnyInArrayFilter; new AnyInArrayFilter(field: 'tags', value: ['foo', 'bar', 'baz']);
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\GreaterThanDateFilter; new GreaterThanDateFilter(field: 'created_at', value: new \DateTimeImmutable());
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\LessThanDateFilter; new LessThanDateFilter(field: 'created_at', value: new \DateTimeImmutable());
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\GreaterThanFloatFilter; new GreaterThanFloatFilter(field: 'price', value: 39.99);
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\LessThanFloatFilter; new LessThanFloatFilter(field: 'price', value: 199.99);
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\GreaterThanIntFilter; new GreaterThanIntFilter(field: 'stock', value: 0);
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\LessThanIntFilter; new LessThanIntFilter(field: 'stock', value: 100);
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\InFilter; new InFilter(field: 'text', value: 'Hello World!'); // or new InFilter(field: 'text', value: ['Hello Symfony!', 'Hello SensioLabs!']);
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\NotInFilter; new NotInFilter(field: 'text', value: 'Hello World!'); // or new NotInFilter(field: 'text', value: ['Bye Symfony!', 'Bye SensioLabs!']);
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\IsFilter; // You can use one of the following constants: // IsFilter::EMPTY_ARRAY // IsFilter::NOT_EMPTY_ARRAY // IsFilter::EMPTY // IsFilter::NOT_EMPTY // IsFilter::TRUE // IsFilter::FALSE // IsFilter::NULL // IsFilter::NOT_NULL new IsFilter(field: 'text', value: IsFilter::EMPTY);
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\LikeFilter; new LikeFilter(field: 'description', value: '*I love Symfony*');
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\NotLikeFilter; new NotLikeFilter(field: 'description', value: '*Text*');
Example:
use Storyblok\Api\Domain\Value\Filter\Filters\OrFilter; use Storyblok\Api\Domain\Value\Filter\Filters\LikeFilter; use Storyblok\Api\Domain\Value\Filter\Filters\NotLikeFilter; new OrFilter( new LikeFilter(field: 'text', value: 'Yes!*'), new LikeFilter(field: 'text', value: 'Maybe!*'), // ... );
Get all available stories by Content Type (string
)
use Storyblok\Api\StoriesApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Request\StoriesRequest; $client = new StoryblokClient(/* ... */); $storiesApi = new StoriesApi($client); $response = $storiesApi->allByContentType('custom_content_type', new StoriesRequest( language: 'de', ));
Get multiple stories by multiple uuid's (array
)
use Storyblok\Api\StoriesApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Request\StoriesRequest; use Storyblok\Api\Domain\Value\Uuid; $client = new StoryblokClient(/* ... */); $storiesApi = new StoriesApi($client); $response = $storiesApi->allByUuids([new Uuid(/** ... */), new Uuid(/** ... */)], new StoriesRequest( language: 'de', ));
Get by uuid (Storyblok\Api\Domain\Value\Uuid
)
use Storyblok\Api\StoriesApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Domain\Value\Uuid; use Storyblok\Api\Request\StoryRequest; $uuid = new Uuid(/** ... */); $client = new StoryblokClient(/* ... */); $storiesApi = new StoriesApi($client); $response = $storiesApi->byUuid($uuid, new StoryRequest( language: 'de', ));
Get by slug (string
)
use Storyblok\Api\StoriesApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Request\StoryRequest; $client = new StoryblokClient(/* ... */); $storiesApi = new StoriesApi($client); $response = $storiesApi->bySlug('folder/slug', new StoryRequest( language: 'de', ));
Get by id (Storyblok\Api\Domain\Value\Id
)
use Storyblok\Api\StoriesApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Domain\Value\Id; use Storyblok\Api\Request\StoryRequest; $id = new Id(/** ... */); $client = new StoryblokClient(/* ... */); $storiesApi = new StoriesApi($client); $response = $storiesApi->byId($id, new StoryRequest( language: 'de', ));
Links
In your code you should type-hint to Storyblok\Api\LinksApiInterface
Get all available links
use Storyblok\Api\LinksApi; use Storyblok\Api\StoryblokClient; $client = new StoryblokClient(/* ... */); $linksApi = new LinksApi($client); $response = $linksApi->all();
Pagination
use Storyblok\Api\LinksApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Domain\Value\Dto\Pagination; use Storyblok\Api\Request\LinksRequest; $client = new StoryblokClient(/* ... */); $linksApi = new LinksApi($client); $response = $linksApi->all(new LinksRequest( pagination: new Pagination(page: 1, perPage: 1000) ));
Get by parent (Storyblok\Api\Domain\Value\Id
)
use Storyblok\Api\LinksApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Domain\Value\Id; $id = new Id(/** ... */); $client = new StoryblokClient(/* ... */); $linksApi = new LinksApi($client); $response = $linksApi->byParent($id);
Get all root links
use Storyblok\Api\LinksApi; use Storyblok\Api\StoryblokClient; $client = new StoryblokClient(/* ... */); $linksApi = new LinksApi($client); $response = $linksApi->roots($id);
Datasource
In your code you should type-hint to Storyblok\Api\DatasourceApiInterface
Get by name (string
)
use Storyblok\Api\DatasourceApi; use Storyblok\Api\StoryblokClient; $client = new StoryblokClient(/* ... */); $api = new DatasourceApi($client); $response = $api->byName('tags'); // returns Storyblok\Api\Domain\Value\Datasource
If it has more than one dimension, you can get the entries by
use Storyblok\Api\DatasourceApi; use Storyblok\Api\StoryblokClient; use Storyblok\Api\Domain\Value\Datasource\Dimension; $client = new StoryblokClient(/* ... */); $api = new DatasourceApi($client); $response = $api->byName('tags', new Dimension('de')); // returns Storyblok\Api\Domain\Value\Datasource
Tags
In your code you should type-hint to Storyblok\Api\TagsApiInterface
Get all available tags
use Storyblok\Api\TagsApi; use Storyblok\Api\StoryblokClient; $client = new StoryblokClient(/* ... */); $api = new TagsApi($client); $response = $api->all(); // returns Storyblok\Api\Response\TagsResponse
Assets
To use the assets API you have to configure the Assets client.
use Storyblok\Api\StoryblokClient; use Storyblok\Api\AssetsApi; $client = new StoryblokClient( baseUri: 'https://api.storyblok.com', token: 'assets-api-token', timeout: 10 // optional ); $assetsApi = new AssetsApi($assetsClient); $assetsApi->get('filename.png')
DX Enhancement through Abstract Collections
To improve developer experience (DX), especially when working with content types like stories, the following abstract class is provided to manage collections of specific content types. This class simplifies data handling and ensures type safety while dealing with large amounts of content from Storyblok.
Abstract ContentTypeCollection Class
The ContentTypeCollection class provides a structured way to work with Storyblok content types. It makes managing pagination, filtering, and sorting more intuitive and reusable, saving time and reducing boilerplate code.
<?php declare(strict_types=1); namespace App\ContentType; use IteratorAggregate; use Storyblok\Api\Response\StoriesResponse; /** * @template T of ContentTypeInterface * * @implements IteratorAggregate<int, T> */ abstract readonly class ContentTypeCollection implements \Countable, \IteratorAggregate { public int $total; public int $perPage; public int $curPage; public int $lastPage; public ?int $prevPage; public ?int $nextPage; /** * @var list<T> */ private array $items; final public function __construct(StoriesResponse $response) { $this->items = array_values(array_map($this->createItem(...), $response->stories)); $this->total = $response->total->value; $this->curPage = $response->pagination->page; $this->perPage = $response->pagination->perPage; $this->lastPage = (int) ceil($this->total / $this->perPage); $this->prevPage = 1 < $this->curPage ? $this->curPage - 1 : null; $this->nextPage = $this->curPage < $this->lastPage ? $this->curPage + 1 : null; } /** * @return \Traversable<int, T> */ final public function getIterator(): \Traversable { return new \ArrayIterator($this->items); } final public function count(): int { return \count($this->items); } /** * @param array<string, mixed> $values * * @return T */ abstract protected function createItem(array $values): ContentTypeInterface; }
Benefits of Using the Abstract Collection:
- Simplified Data Handling: Instead of dealing with raw arrays of stories, this abstract class helps you manage collections of content types, like blog posts or articles, in an organized manner. It abstracts away the repetitive work of pagination and mapping response data to objects.
- Enhanced Readability: Using a well-structured collection class makes the code easier to read and maintain. Instead of handling pagination and raw data structures in controllers or services, you simply instantiate the collection and let it handle the data.
- Reusability: The class is flexible and reusable across different content types. Once implemented, you can easily create new collections for other Storyblok content types with minimal extra code.
- Pagination and Metadata Management: The collection class comes with built-in properties for pagination and metadata (e.g., total items, current page, etc.), making it much easier to manage paginated data efficiently.
Example Usage with a Collection
Here is an example of how to use the ContentTypeCollection to manage blog posts in your Symfony project:
<?php declare(strict_types=1); namespace App\ContentType\BlogPost; use App\ContentType\ContentTypeCollection; use App\ContentType\ContentTypeFactory; /** * @extends ContentTypeCollection<BlogPost> */ final readonly class BlogPostCollection extends ContentTypeCollection { protected function createItem(array $values): BlogPost { return ContentTypeFactory::create($values, BlogPost::class); } }
new BlogPostCollection( $this->stories->allByContentType( BlogPost::type(), new StoriesRequest( language: $this->localeSwitcher->getLocale(), pagination: new Pagination($this->curPage, self::PER_PAGE), sortBy: new SortBy('first_published_at', Direction::Desc), filters: $filters, excludeFields: new FieldCollection([ new Field('body'), new Field('additional_contents'), ]), ), ), );
Helpers
The Storyblok\Api\Util\ValueObjectTrait
provides utility methods for mapping raw Storyblok data arrays into strong PHP
value objects, enums, and domain models. These helpers reduce boilerplate code and improve readability in DTO
constructors or factory methods.
Use this trait in your value objects or models to simplify the parsing and validation of Storyblok field values.
Available Methods
Method | Description |
---|---|
one() |
Expects exactly one item (e.g. from a blocks field). Instantiates one object from it. |
list() |
Maps a list of items to objects. Allows setting $min , $max , or exact $count constraints. |
nullOrOne() |
Same as one() , but allows the field to be optional (returns null if empty). |
enum() |
Maps a string value to a backed enum. Supports default value and whitelisting of allowed values. |
DateTimeImmutable() |
Returns a Safe\DateTimeImmutable object from a given date string. |
Uuid() |
Returns a Storyblok\Api\Domain\Value\Uuid instance from a string. |
Asset() |
Maps an asset array to a Storyblok\Api\Domain\Type\Asset object. |
nullOrAsset() |
Same as Asset() , but allows null or invalid input. |
MultiLink() |
Maps a multilink array to a Storyblok\Api\Domain\Type\MultiLink object. |
nullOrMultiLink() |
Same as MultiLink() , but returns null if url and id are missing or empty. |
RichText() |
Maps rich text content to a Storyblok\Api\Domain\Type\RichText object. |
nullOrRichText() |
Same as RichText() , but returns null if content is empty or only contains whitespace. |
boolean() |
Returns true if the key exists and its value is true , otherwise false . |
zeroOrInteger() |
Returns an integer from the field, or 0 if missing. |
zeroOrFloat() |
Returns a float from the field, or 0.0 if missing. |
string() |
Returns a trimmed non-empty string (using TrimmedNonEmptyString ). Optional max length check. |
nullOrString() |
Same as string() , but returns null if missing or invalid. |
nullOrEditable() |
Returns an Editable instance or null . |
Management API client
For the Management API PHP Client, see storyblok/php-management-api-client.
License
This project is licensed under the MIT License. Please see License File for more information.