Skip to content

@storyblok/api-client Reference

@storyblok/api-client is Storyblok’s modern TypeScript client for the Content Delivery API (CAPI). It exposes a single createApiClient() factory that returns resource clients for stories, links, tags, datasources, datasource entries, spaces, and experiments, with built-in caching, retry logic, and rate limiting. Responses are fully typed and can be narrowed end-to-end by passing a project schema type from @storyblok/schema to .withTypes<Schema>().

  • Node.js LTS (version 22.x recommended)
  • Modern web browser (the latest versions of Chrome, Edge, Firefox, and Safari)

Add the package to a project:

Terminal window
npm install @storyblok/api-client@alpha

The following example demonstrates how to initialize the client and fetch a story from the Content Delivery API:

import { createApiClient } from "@storyblok/api-client";
const client = createApiClient({
accessToken: process.env.STORYBLOK_ACCESS_TOKEN!,
region: "eu",
});
const { data, error } = await client.stories.get("home");
if (error) throw error;
console.log(data?.story.content);

Every resource client method returns an ApiResponse object containing data, error, response, and request. Set throwOnError: true on the client (or per request) to receive data directly and have errors thrown as ClientError instances instead.

The following sections document the createApiClient() factory, each resource client, and the exported error class and types.

Accepts a configuration object and returns a client with typed methods for each Content Delivery API resource.

createApiClient(config);
import { createApiClient } from "@storyblok/api-client";
const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
region: "us",
});

createApiClient() accepts the following options:

Option Description
accessToken Required. The Storyblok access token (preview or public).
region The API region. Possible values: 'eu' (default), 'us', 'ap', 'ca', 'cn'. See Region.
baseUrl A custom base URL that overrides the region-based URL (for example, when proxying requests).
headers Additional headers to send with every request.
throwOnError If true, the client throws on HTTP errors instead of returning { error }. Defaults to false.
inlineRelations If true, the client replaces resolved relation UUIDs in story content with full story objects. Defaults to false. See Inline relations.
cache Cache configuration object. See Caching.
retry Retry configuration object. See Retry.
rateLimit Rate-limiting configuration. See Rate limiting.
timeout Request timeout in milliseconds. Defaults to 30_000.
fetch A custom fetch implementation to use instead of the global fetch. Useful for testing or platform-specific fetch wrappers.

The returned client exposes the following properties:

Property Description
stories Methods for fetching stories. See client.stories.
links Methods for fetching links. See client.links.
spaces Methods for fetching space information. See client.spaces.
datasources Methods for fetching datasources. See client.datasources.
datasourceEntries Methods for fetching datasource entries. See client.datasourceEntries.
tags Methods for fetching tags. See client.tags.
experiments Methods for fetching experiments. See client.experiments.
get A generic GET method for arbitrary Content Delivery API endpoints. See client.get().
flushCache Flushes the cache and resets the tracked content version. See client.flushCache().
interceptors Request, response, and error interceptors. See client.interceptors.
withTypes Returns a typed client that narrows story.content to a project schema. See TypeScript.

Use the region option to match the space’s server location.

const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
region: "us",
});

The following options are available:

Value Region
'eu' European Union (default).
'us' United States.
'ap' Australia/Asia Pacific.
'ca' Canada.
'cn' China.

Use the stories resource client to retrieve stories.

Fetches a single story by slug, ID, or UUID. A UUID identifier sets find_by: 'uuid' automatically.

client.stories.get(identifier);
client.stories.get(identifier, options);
// Fetch by slug
const result = await client.stories.get("blog/my-first-post", {
query: { version: "draft" },
});
// Fetch by ID
const result = await client.stories.get(123456);
// Fetch by UUID (find_by is set automatically)
const result = await client.stories.get("2bd955a2-6e1f-4d3c-9a0d-3f1b8c7e4a56");

The identifier parameter accepts the following values:

Type Description
string A story’s full_slug (for example, 'blog/my-post') or uuid. A uuid sets find_by: 'uuid' automatically.
number A story’s numeric id.

The options parameter accepts the following properties:

Property Description
query Query parameters. See the table below.
signal An AbortSignal for canceling the request.
throwOnError Override the client-level throwOnError setting for this request.
fetchOptions Custom options forwarded to the underlying fetch call. See Custom fetch options.

The query object accepts the following properties. For the complete list, see Query parameters in the Content Delivery API reference.

Property Description
find_by Set to 'uuid' to look up a story by UUID. Set automatically for UUID identifiers.
version 'published' or 'draft'.
cv Cache version number.
from_release Retrieve content from a specific release by ID.
resolve_links Resolve link fields: 'story', 'url', or 'link'.
resolve_links_level Resolve link nesting depth: 1 or 2.
resolve_relations Comma-separated list of component.field pairs to resolve.
resolve_level Set to 2 to resolve nested stories.
resolve_assets Set to 1 to include asset metadata.
fallback_lang Fallback language code.
language Language code for localized content.

Fetches multiple stories with optional filtering, sorting, and pagination.

client.stories.list();
client.stories.list(options);
const result = await client.stories.list({
query: {
starts_with: "blog/",
version: "draft",
per_page: 25,
},
});
if (result.data) {
console.log(result.data.stories);
}

The options parameter accepts the following properties:

Property Description
query Query parameters. See the table below.
signal An AbortSignal for canceling the request.
throwOnError Override the client-level throwOnError setting for this request.
fetchOptions Custom options forwarded to the underlying fetch call. See Custom fetch options.

The query object accepts all properties from client.stories.get() (except find_by) plus the following. For the complete list, see Query parameters in the Content Delivery API reference.

Property Description
starts_with Filter stories by path prefix.
search_term Full-text search term.
per_page Number of stories per page.
page Page number for pagination.
content_type Filter by component name.
by_slugs Comma-separated list of slugs.
excluding_slugs Comma-separated list of slugs to exclude.
by_uuids Comma-separated list of UUIDs.
by_uuids_ordered Comma-separated list of UUIDs (results preserve order).
excluding_ids Comma-separated list of IDs to exclude.
with_tag Filter by tag name.
sort_by Sort field and direction (for example, 'published_at:desc').
in_workflow_stages Filter by workflow stage IDs.
filter_query Filter query object.
level Story nesting level.
is_startpage Set to 1 for start pages only, 0 to exclude them.
first_published_at_gt Filter by first published date (greater than).
first_published_at_lt Filter by first published date (less than).
published_at_gt Filter by published date (greater than).
published_at_lt Filter by published date (less than).
updated_at_gt Filter by updated date (greater than).
updated_at_lt Filter by updated date (less than).
excluding_fields Comma-separated list of fields to exclude from the response.

Use the links resource client to retrieve links.

Fetches all links from the Content Delivery API. Useful for building site-wide navigation menus.

client.links.list();
client.links.list(options);
const result = await client.links.list({
query: { version: "published" },
});
if (result.data) {
console.log(result.data.links);
}

The options parameter accepts the following properties:

Property Description
query Query parameters. See the table below.
signal An AbortSignal for canceling the request.
throwOnError Override the client-level throwOnError setting for this request.
fetchOptions Custom options forwarded to the underlying fetch call. See Custom fetch options.

The query object accepts the following properties. For the complete list, see Query parameters in the Content Delivery API reference.

Property Description
version 'published' or 'draft'.
cv Cache version number.
starts_with Filter links by path prefix.
with_parent Filter by parent folder ID.
include_dates Set to 1 to include date fields.
paginated Set to 1 to enable pagination.
page Page number.
per_page Number of links per page.

Use the spaces resource client to retrieve information about the space.

Fetches public information about the current space (name, language codes, version cv). This endpoint is never cached.

client.spaces.get();
client.spaces.get(options);
const result = await client.spaces.get();
if (result.data) {
console.log(result.data.space);
}

The options parameter accepts the following properties:

Property Description
query Query parameters forwarded to the request.
signal An AbortSignal for canceling the request.
throwOnError Override the client-level throwOnError setting for this request.
fetchOptions Custom options forwarded to the underlying fetch call. See Custom fetch options.

Use the datasources resource client to retrieve datasources.

Fetches a single datasource by numeric ID. Resolve the ID via client.datasources.list() if only the slug is known.

client.datasources.get(id);
client.datasources.get(id, options);
const result = await client.datasources.get(123);
if (result.data) {
console.log(result.data.datasource);
}

The options parameter accepts the following properties:

Property Description
query Query parameters. See the table below.
signal An AbortSignal for canceling the request.
throwOnError Override the client-level throwOnError setting for this request.
fetchOptions Custom options forwarded to the underlying fetch call. See Custom fetch options.

The query object accepts the following property. For the complete list, see Query parameters in the Content Delivery API reference.

Property Description
cv Cache version number.

Fetches all datasources.

client.datasources.list();
client.datasources.list(options);
const result = await client.datasources.list({
query: { page: 1, per_page: 25 },
});
if (result.data) {
console.log(result.data.datasources);
}

The options parameter accepts the following properties:

Property Description
query Query parameters. See the table below.
signal An AbortSignal for canceling the request.
throwOnError Override the client-level throwOnError setting for this request.
fetchOptions Custom options forwarded to the underlying fetch call. See Custom fetch options.

The query object accepts the following properties. For the complete list, see Query parameters in the Content Delivery API reference.

Property Description
page Page number.
per_page Number of datasources per page.
cv Cache version number.

Use the datasourceEntries resource client to retrieve the entries of a datasource.

Fetches all datasource entries.

client.datasourceEntries.list();
client.datasourceEntries.list(options);
const result = await client.datasourceEntries.list({
query: { datasource: "categories" },
});
if (result.data) {
console.log(result.data.datasource_entries);
}

The options parameter accepts the following properties:

Property Description
query Query parameters. See the table below.
signal An AbortSignal for canceling the request.
throwOnError Override the client-level throwOnError setting for this request.
fetchOptions Custom options forwarded to the underlying fetch call. See Custom fetch options.

The query object accepts the following properties. For the complete list, see Query parameters in the Content Delivery API reference.

Property Description
datasource The datasource slug.
dimension The datasource dimension value.
page Page number.
per_page Number of entries per page.
cv Cache version number.

Use the tags resource client to retrieve tags.

Fetches all tags.

client.tags.list();
client.tags.list(options);
const result = await client.tags.list({
query: { starts_with: "blog" },
});
if (result.data) {
console.log(result.data.tags);
}

The options parameter accepts the following properties:

Property Description
query Query parameters. See the table below.
signal An AbortSignal for canceling the request.
throwOnError Override the client-level throwOnError setting for this request.
fetchOptions Custom options forwarded to the underlying fetch call. See Custom fetch options.

The query object accepts the following properties. For the complete list, see the Tags endpoints in the Content Delivery API reference.

Property Description
starts_with Filter tags by prefix.
version 'published' or 'draft'.

Use the experiments resource client to retrieve running experiments and their variant mappings.

Returns all running experiments. Each experiment lists its variants and their story_mappings, which map an original_story_id/original_slug to a variant_story_id/variant_slug.

client.experiments.list();
client.experiments.list(options);
const result = await client.experiments.list();
if (result.data) {
console.log(result.data.experiments);
}

The Content Delivery API has no per-story variant filter: list the experiments, pick a variant deterministically, then fetch that variant’s variant_slug via client.stories. For the underlying endpoints, refer to Experiments in the Content Delivery API reference.

A generic GET method for arbitrary Content Delivery API endpoints. The caller provides the expected response type as a generic parameter.

client.get(path);
client.get(path, options);
const result = await client.get<{ links: Record<string, unknown> }>("/v2/cdn/links", { query: { version: "published" } });

Flushes the cache and resets the tracked content version (cv). Intended for use when cache.flush is 'manual' — for example, after receiving a Storyblok webhook.

client.flushCache();

The interceptors property exposes request, response, and error middleware.

client.interceptors.request.use((request, options) => {
// Modify or inspect outgoing requests.
return request;
});
client.interceptors.response.use((response, request, options) => {
// Modify or inspect incoming responses.
return response;
});
client.interceptors.error.use((error, response, request, options) => {
// Handle or transform errors.
return error;
});

Each interceptor channel (request, response, error) supports the following methods:

Method Description
use(fn) Register an interceptor. Returns an ID.
eject(id) Remove an interceptor by ID.
exists(id) Check whether an interceptor is registered.
update(id, fn) Replace an interceptor.
clear() Remove all interceptors.

Example: Add a custom header to every request

Section titled “Example: Add a custom header to every request”
client.interceptors.request.use((request, options) => {
request.headers.set("X-Custom-Header", "my-value");
return request;
});

An error class representing an HTTP error response. Thrown when throwOnError is true, or available on the error property of the response when throwOnError is false.

import { ClientError } from "@storyblok/api-client";
new ClientError(message, { status, statusText, data });

ClientError exposes the error details under a single response property:

Property Type Description
message string The error message.
response.status number The HTTP status code.
response.statusText string The HTTP status text.
response.data ApiErrorBody | undefined The parsed response body (typically { error?, message? }).

By default, references fields remain UUID strings even when resolve_relations is used. Set inlineRelations: true to replace matching relation fields in story content with full story objects.

const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
inlineRelations: true,
});
const result = await client.stories.get("blog/my-post", {
query: {
version: "draft",
resolve_relations: "article.author",
},
});

When enabled:

  • Inlining runs only for fields explicitly listed in resolve_relations (exact component.field match).
  • The rels and rel_uuids arrays remain unchanged in the response payload.
  • If the API response includes rel_uuids (relation overflow), the client automatically fetches the missing related stories and includes them in the inlining pass.
  • Relation fields can contain either UUID strings or inlined story objects.
  • Cyclic relations produce cyclic object references (for example, A → B → A).

Before (inlineRelations: false):

result.data.story.content.author;
// "9ef7f0c0-5a5f-4fbb-9f5e-1e8de56b8910"

After (inlineRelations: true):

result.data.story.content.author;
// { uuid: "9ef7f0c0-5a5f-4fbb-9f5e-1e8de56b8910", name: "...", ...storyFields }

The client includes a built-in in-memory cache for published (version: 'published') GET requests. Draft requests (version: 'draft') and the spaces endpoint are never cached. The default cache is an in-memory LRU (1000 entries) with the 'cache-first' strategy and a 60-second TTL.

const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
cache: {
strategy: "cache-first",
ttlMs: 60_000,
},
});

The cache object accepts the following options:

Option Description
provider A custom cache provider. Defaults to an in-memory LRU cache (maxEntries: 1_000). See Custom cache provider.
strategy A cache strategy name or custom handler function. Defaults to 'cache-first'. See Cache strategies.
ttlMs Cache entry time-to-live in milliseconds. Defaults to 60_000.
cv Controls the cv (content version) query parameter. 'auto' (default) attaches the tracked cv to published requests for cache busting. 'manual' omits it (the client still tracks cv internally), useful for SSR with edge caching where stable URLs are required.
flush 'auto' (default) flushes the cache when the API returns a new content version. 'manual' requires calling client.flushCache() explicitly. See Cache flush control.
onRevalidationError Called when SWR background revalidation fails. Only relevant when strategy is 'swr'. Defaults to console.warn.
Strategy Behavior
'cache-first' The default. Serve from cache if available. Fall back to the network and cache the result.
'network-first' Always fetch from the network. Cache the result and fall back to cache on error.
'swr' Serve stale cache immediately and revalidate in the background. If no cached response exists, the request waits for the network result.

The strategy option also accepts a custom handler function of type CacheStrategyHandler:

import type { CacheStrategyHandler } from "@storyblok/api-client";
const customStrategy: CacheStrategyHandler = async ({ key, cachedResult, loadNetwork }) => {
if (cachedResult) {
return cachedResult;
}
return loadNetwork();
};
const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
cache: { strategy: customStrategy },
});

By default (flush: 'auto'), the cache flushes automatically whenever the API returns a new content version (cv) value.

For at-scale scenarios that require external control over cache invalidation (for example, distributed Redis across multiple instances, webhook-triggered flush, or SSG build race conditions), set flush: 'manual' and call client.flushCache() explicitly.

const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
cache: { flush: "manual" },
});
// Flush manually after receiving a Storyblok webhook
app.post("/webhook", async (req, res) => {
await client.flushCache();
res.sendStatus(200);
});

Provide a custom cache provider to integrate with Redis, Memcached, or any other caching backend. The provider must implement the CacheProvider interface:

import type { CacheProvider } from "@storyblok/api-client";
const memory = new Map<string, { value: unknown; storedAt: number; ttlMs: number }>();
const customCacheProvider: CacheProvider = {
async get(key) {
return memory.get(key);
},
async set(key, entry) {
memory.set(key, {
value: entry.value,
storedAt: entry.storedAt ?? Date.now(),
ttlMs: entry.ttlMs,
});
},
async flush() {
memory.clear();
},
};
const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
cache: {
provider: customCacheProvider,
strategy: "network-first",
ttlMs: 30_000,
},
});

The CacheProvider interface:

Method Signature Description
get (key: string) => Promise<CacheEntry | undefined> Retrieve a cache entry by key.
set (key: string, entry: CacheEntryInput) => Promise<void> Store a cache entry.
flush () => Promise<void> Remove all cache entries.

A CacheEntry contains:

Property Type Description
value unknown The cached value.
storedAt number Timestamp (milliseconds) when the entry was stored.
ttlMs number Time-to-live in milliseconds.

The client retries failed requests automatically. Pass a retry object to override individual settings — any omitted property falls back to its default.

Property Default Description
limit 3 Maximum number of retry attempts.
backoffLimit 20_000 Maximum backoff delay in milliseconds.
jitter true Add randomness to backoff delays to avoid thundering herd.
// Increase the retry limit and keep other defaults.
const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
retry: { limit: 5 },
});
// Disable retries entirely.
const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
retry: { limit: 0 },
});

The client applies preventive rate limiting by default to avoid hitting the Content Delivery API limits. Refer to the rate limit documentation for more details.

The rateLimit option accepts several forms:

Value Behavior
undefined The default. Auto-detect the concurrency tier from the request path and per_page query parameter.
number Fixed maximum number of concurrent (in-flight) requests.
RateLimitConfig Full configuration object (see below).
false Disable rate limiting entirely.

The RateLimitConfig object accepts:

Property Default Description
maxConcurrency Auto-detected Fixed maximum number of concurrent (in-flight) requests. When omitted (the default), the concurrency tier is auto-detected; setting it disables auto-detection.
adaptToServerHeaders true Dynamically adjust the concurrency limit from the X-RateLimit-Policy response header (a concurrency quota, not a request-rate limit).
// Disable rate limiting.
const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
rateLimit: false,
});
// Fixed limit.
const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
rateLimit: 10,
});

Every resource client method accepts a fetchOptions property for passing non-standard options to the underlying fetch call. This is useful for platform-specific extensions such as Next.js cache control or Cloudflare Workers settings.

The fetchOptions object is typed as Record<string, unknown>, so any key-value pair is accepted. The options are forwarded directly to the fetch call as the second argument alongside standard RequestInit properties.

The following examples show common cases:

const result = await client.stories.get("home", {
fetchOptions: {
next: { revalidate: 60 },
},
});
const result = await client.stories.list({
query: { starts_with: "blog/" },
fetchOptions: {
next: { revalidate: 120, tags: ["stories"] },
},
});
const result = await client.stories.get("home", {
fetchOptions: {
cf: { cacheTtl: 300, cacheEverything: true },
},
});

The examples above forward options through the global fetch. To call the API with a different fetch altogether, supply one via the fetch config option when creating the client. Combine the fetch config option with per-request fetchOptions for full control. For example, pass Next.js fetch at init time and control revalidation per request:

import { createApiClient } from "@storyblok/api-client";
const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
fetch, // Next.js extended fetch
});
// This request revalidates every 60 seconds.
const home = await client.stories.get("home", {
fetchOptions: { next: { revalidate: 60 } },
});
// This request uses on-demand revalidation via tags.
const blog = await client.stories.list({
query: { starts_with: "blog/" },
fetchOptions: { next: { tags: ["blog"] } },
});

By default, the client returns a response object with either data or error:

import { createApiClient } from "@storyblok/api-client";
const client = createApiClient({ accessToken: "YOUR_ACCESS_TOKEN" });
const result = await client.stories.get("home");
if (result.data) {
console.log(result.data.story);
} else {
console.error(result.error);
}

Set throwOnError: true to throw a ClientError on HTTP errors instead:

import { ClientError } from "@storyblok/api-client";
const client = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
throwOnError: true,
});
try {
const result = await client.stories.get("home");
console.log(result.data.story);
} catch (error) {
if (error instanceof ClientError) {
console.error(error.response.status, error.response.statusText, error.response.data);
}
}

@storyblok/api-client ships complete type definitions and is fully compatible with @storyblok/schema.

Use the client in conjunction with [@storyblok/schema](/docs/libraries/js/schema) to narrow story.content to the project’s component union. The client object exposes a withTypes<T>() method that returns the same instance cast to a version that narrows story content to the components declared in the passed schema. withTypes<T>() accepts either { components: Block } or { blocks: Block }, the latter matches the Schema type produced by @storyblok/schema. It carries no runtime cost and adds no schema values to the bundle.

import { createApiClient } from "@storyblok/api-client";
import type { Schema } from "./schema";
const client = createApiClient({ accessToken: "YOUR_ACCESS_TOKEN" }).withTypes<Schema>();
const { data } = await client.stories.get("home");
// data.story.content is now a discriminated union of the schema's blocks.

Was this page helpful?

What went wrong?

This site uses reCAPTCHA and Google's Privacy Policy (opens in a new window).Terms of Service (opens in a new window) apply.