Statamic as Headless CMS via REST/GraphQL API
Statamic supports headless mode via built-in Content API (REST) and official GraphQL addon. REST API is available in the free version, GraphQL is in Pro.
REST API
Enabled in config/statamic/api.php:
return [
'enabled' => env('STATAMIC_API_ENABLED', true),
'route' => '/api/v1',
'resources' => [
'collections' => true,
'taxonomies' => true,
'assets' => true,
'globals' => true,
'forms' => false,
'users' => false,
],
'cache' => [
'enabled' => env('STATAMIC_API_CACHE', true),
'expiry' => 60,
],
];
Available endpoints:
GET /api/v1/collections
GET /api/v1/collections/{collection}/entries
GET /api/v1/collections/{collection}/entries/{id}
GET /api/v1/taxonomies/{taxonomy}/terms
GET /api/v1/globals
GET /api/v1/globals/{handle}
GET /api/v1/assets/{container}
Filtering and selection via REST
// List posts with filtering
const res = await fetch(
`${STATAMIC_URL}/api/v1/collections/blog/entries?` +
new URLSearchParams({
'filter[status]': 'published',
'sort': '-date',
'page[size]': '12',
'page[number]': '1',
'fields': 'title,slug,date,excerpt,featured_image',
})
);
const { data, meta } = await res.json();
GraphQL API (Pro)
composer require statamic/graphql
php artisan vendor:publish --tag=statamic-graphql-config
Schema configuration:
// config/statamic/graphql.php
return [
'enabled' => true,
'route' => '/graphql',
'resources' => [
'collections' => ['blog', 'pages', 'events'],
'taxonomies' => ['categories', 'tags'],
'globals' => ['site'],
'assets' => ['assets'],
],
'middleware' => ['web'],
'cache' => ['enabled' => true, 'expiry' => 3600],
];
Queries:
query BlogPosts($page: Int, $limit: Int) {
entries(
collection: "blog"
filter: { status: { eq: "published" } }
sort: [{ field: "date", order: "DESC" }]
limit: $limit
page: $page
) {
data {
id
slug
title
date
... on Entry_Blog_Post {
excerpt
featured_image {
id
url
width
height
alt
}
categories {
title
slug
url
}
}
}
total
per_page
current_page
last_page
}
}
Custom GraphQL type
// Schema extension via addon
use Statamic\Facades\GraphQL;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
GraphQL::addType(new ObjectType([
'name' => 'PostStats',
'fields' => [
'views' => ['type' => Type::int()],
'likes' => ['type' => Type::int()],
],
]));
// Add field to existing Entry type
GraphQL::addField('Entry_Blog_Post', 'stats', function () {
return [
'type' => GraphQL::type('PostStats'),
'resolve' => function ($entry) {
return [
'views' => PostStats::getViews($entry->id()),
'likes' => PostStats::getLikes($entry->id()),
];
},
];
});
Next.js + Statamic REST
// lib/statamic.ts
const BASE = process.env.STATAMIC_URL!;
export async function getBlogPosts(page = 1) {
const url = new URL(`${BASE}/api/v1/collections/blog/entries`);
url.searchParams.set('filter[status]', 'published');
url.searchParams.set('sort', '-date');
url.searchParams.set('page[size]', '12');
url.searchParams.set('page[number]', String(page));
const res = await fetch(url.toString(), {
next: { revalidate: 3600, tags: ['blog'] },
});
return res.json();
}
// Webhook for revalidation on publishing
// Statamic → CP → Utilities → API → Webhooks
// Event: entry:saved → POST https://nextjs.app/api/revalidate
Setting up REST API with basic endpoints — 4–8 hours. GraphQL with custom types — 1–2 days.







