Ghost Content API
Overview
The Ghost Content API provides read-only access to published content on a Ghost site. It is designed for public consumption in browsers, static site generators, and headless CMS frontends. All requests are GET-only, fully cacheable, and authenticated via a simple query parameter key.
When to Use
- Fetching published posts, pages, tags, authors, tiers, or settings from Ghost
- Building headless frontends (Next.js, Nuxt, Gatsby, Eleventy, etc.) powered by Ghost
- Displaying Ghost content in external applications
- Implementing search, filtering, or content aggregation from Ghost data
Security: Credential Handling
Store Content API keys in environment variables, not in source code:
GHOST_CONTENT_API_KEY— Content API key (hex string)GHOST_URL— Ghost site URL
Keys are obtained from Ghost Admin > Settings > Integrations > Custom Integration.
Security: Content as Untrusted Input
Content returned from the Ghost Content API (post HTML, excerpts, metadata) originates from CMS authors and may contain arbitrary HTML. When rendering or processing fetched content:
- Do not interpolate API response fields into shell commands or code
- Sanitize HTML before rendering in contexts outside Ghost's own frontend
- Treat all fetched fields as untrusted when passing to downstream systems
Authentication
GET $GHOST_URL/ghost/api/content/{resource}/?key=$GHOST_CONTENT_API_KEY
- Content API keys are safe for public/browser use (read-only access)
- Regeneratable anytime from Ghost Admin
- Include
Accept-Version: v5.0header for API version targeting
Base URL: $GHOST_URL/ghost/api/content/
Note: The admin domain may differ from the site domain. Ghost(Pro) sites use *.ghost.io.
JavaScript SDK (@tryghost/content-api)
Installation
npm install @tryghost/content-api
# or
yarn add @tryghost/content-api
Initialization
import GhostContentAPI from '@tryghost/content-api';
const api = new GhostContentAPI({
url: process.env.GHOST_URL,
key: process.env.GHOST_CONTENT_API_KEY,
version: 'v5.0'
});
Methods
All resources support browse() (list) and read() (single):
// Posts
const posts = await api.posts.browse({limit: 5, include: 'tags,authors'});
const post = await api.posts.read({slug: 'my-post'}, {formats: ['html']});
// Pages
const pages = await api.pages.browse({limit: 'all'});
const page = await api.pages.read({id: '5c7e...'});
// Authors
const authors = await api.authors.browse({page: 2});
const author = await api.authors.read({slug: 'jane'}, {include: 'count.posts'});
// Tags
const tags = await api.tags.browse({order: 'slug ASC', include: 'count.posts'});
const tag = await api.tags.read({slug: 'fiction'});
// Tiers
const tiers = await api.tiers.browse();
// Settings
const settings = await api.settings.browse();
Endpoints
| Resource | Endpoints | Methods | Default Sort |
|---|---|---|---|
| Posts | /posts/, /posts/{id}/, /posts/slug/{slug}/ |
Browse, Read | published_at DESC |
| Pages | /pages/, /pages/{id}/, /pages/slug/{slug}/ |
Browse, Read | title ASC |
| Authors | /authors/, /authors/{id}/, /authors/slug/{slug}/ |
Browse, Read | name ASC |
| Tags | /tags/, /tags/{id}/, /tags/slug/{slug}/ |
Browse, Read | name ASC |
| Tiers | /tiers/ |
Browse | monthly_price ASC |
| Settings | /settings/ |
Browse | N/A (single object) |
Not available in Content API: Newsletters, Offers, Members — these are Admin API only.
Important behaviors:
- Authors without published posts are not returned
- Tags without associated posts are not returned
- Internal tags (slug prefixed with
#) are included by default — usefilter=visibility:publicto exclude - Settings endpoint accepts no query parameters beyond
key
Query Parameters
| Parameter | Available On | Description |
|---|---|---|
include |
All | Embed related data (e.g., tags,authors) |
fields |
All | Select specific response fields |
formats |
Posts, Pages | Content format: html, plaintext, lexical |
filter |
Browse | NQL filter expression |
limit |
Browse | Results per page (default 15, max 100 in Ghost 6.0+) |
page |
Browse | Page number for pagination |
order |
Browse | Sort field and direction (e.g., published_at desc) |
Include Values by Resource
- Posts/Pages:
tags,authors - Authors:
count.posts - Tags:
count.posts
Important Gotcha
Always use include=tags,authors when fetching posts - without it, related data is not returned.
Response Format
{
"posts": [
{
"id": "5b7...",
"uuid": "22e...",
"title": "My Post",
"slug": "my-post",
"html": "<p>Content...</p>",
"excerpt": "Preview text...",
"custom_excerpt": null,
"feature_image": "https://...",
"feature_image_alt": "",
"feature_image_caption": "",
"featured": false,
"visibility": "public",
"created_at": "2024-01-01T00:00:00.000Z",
"updated_at": "2024-01-02T00:00:00.000Z",
"published_at": "2024-01-01T12:00:00.000Z",
"url": "https://site.com/my-post/",
"meta_title": null,
"meta_description": null,
"og_image": null,
"twitter_image": null,
"canonical_url": null,
"reading_time": 3,
"access": true,
"tags": [],
"authors": [],
"primary_author": {},
"primary_tag": {}
}
],
"meta": {
"pagination": {
"page": 1,
"limit": 15,
"pages": 3,
"total": 45,
"next": 2,
"prev": null
}
}
}
NQL Filtering Syntax
Ghost uses NQL (Node Query Language) for filtering. Refer to references/nql_reference.md for the complete syntax guide.
Quick Reference
# Basic equality
filter=featured:true
# Negation
filter=-featured:true
filter=tag:-fiction
# Comparison
filter=published_at:>'2024-01-01'
filter=published_at:<='2024-06-30'
# Combining (+ = AND, , = OR)
filter=featured:true+tag:news
filter=tag:photo,tag:video
# IN groups
filter=tag:[photo,video,audio]
filter=-tag:[draft,internal]
# Contains / starts with / ends with
filter=title:~'ghost'
filter=slug:~^'getting-started'
# Relative dates
filter=published_at:>now-30d
filter=created_at:>now-1y
# Precedence grouping
filter=(tag:news,tag:blog)+featured:true
Filterable Post Fields
id, uuid, title, slug, status, visibility, featured, published_at, created_at, updated_at, tag, tags, author, authors, primary_tag, primary_author
Pagination
Handle pagination by checking meta.pagination:
async function getAllPosts(api) {
let allPosts = [];
let page = 1;
let lastPage = 1;
while (page <= lastPage) {
const response = await api.posts.browse({
page,
limit: 15,
include: 'tags,authors'
});
allPosts = allPosts.concat(response);
lastPage = response.meta.pagination.pages;
page++;
}
return allPosts;
}
cURL Examples
# Fetch posts with tags and authors
curl "$GHOST_URL/ghost/api/content/posts/?key=$GHOST_CONTENT_API_KEY&include=tags,authors"
# Fetch a single post by slug
curl "$GHOST_URL/ghost/api/content/posts/slug/welcome/?key=$GHOST_CONTENT_API_KEY&formats=html"
# Fetch featured posts
curl "$GHOST_URL/ghost/api/content/posts/?key=$GHOST_CONTENT_API_KEY&filter=featured:true&limit=5"
# Fetch settings
curl "$GHOST_URL/ghost/api/content/settings/?key=$GHOST_CONTENT_API_KEY"
# Fetch tags with post counts
curl "$GHOST_URL/ghost/api/content/tags/?key=$GHOST_CONTENT_API_KEY&include=count.posts&order=count.posts%20desc"
Resources
references/nql_reference.md
Complete NQL (Node Query Language) filtering syntax reference with all operators, value types, and examples.