Content API Ghost : Guide Complet avec Exemples JavaScript

La Content API de Ghost est l'outil principal pour récupérer le contenu de votre site de manière programmatique. Elle est conçue pour les setups headless, où Ghost gère le backend (contenu, membres, newsletters) et un frontend séparé (comme Next.js, Gatsby ou une app mobile) affiche les données. En 2025, avec la version v5 (et des mises à jour vers v6 pour la compatibilité), elle reste simple, rapide et focalisée sur la lecture seule. Pas de modifications possibles ici – pour ça, utilisez l'Admin API.

Ce guide s'adresse aux débutants en développement, aux petites entreprises ou startups qui veulent découpler leur site pour plus de flexibilité. On va couvrir l'essentiel : authentification, ressources disponibles, paramètres pour filtrer et paginer, et des exemples concrets en JavaScript. Tout est basé sur la doc officielle Ghost, sans fioritures.

Pourquoi utiliser la Content API ?

Dans un monde où les sites doivent être rapides et personnalisables, Ghost en headless CMS brille. Vous publiez du contenu via l'admin Ghost, et la Content API le sert en JSON pur. Avantages pour une SMB ou startup :

  • Performance : Pas de rendu serveur lourd, idéal pour des apps React ou Vue.
  • Évolutivité : Intégrez avec n'importe quel framework sans toucher au thème Ghost.
  • Sécurité : Lecture publique seulement, pas d'accès admin exposé.
  • Cas d'usage : Blog découplé, app mobile pour vos newsletters, ou site e-commerce basique avec tiers payants.

Limites : Seulement les contenus publics (posts publiés, pages visibles). Pour du contenu protégé (membres), ajoutez une couche d'authentification frontend.

Authentification : Obtenez votre clé publique

Pour accéder à l'API, créez une intégration dans l'admin Ghost :

  1. Allez dans Intégrations > Ajouter une intégration personnalisée.
  2. Nommez-la (ex. : "Mon frontend headless").
  3. Copiez la clé Content API (un string hex comme 22444f78447824223cefc48062).

L'URL de base est https://votre-site.com/ghost/api/content/. Ajoutez ?key=VOTRE_CLE à chaque requête GET.

Pour les sites avec membres, utilisez un browse token (JWT) pour des accès limités, mais pour débuter, la clé publique suffit. Versionnez avec ?version=v5.0 (ou v6.0 pour les futures compatibilités).

Exemple basique en curl :

curl "https://demo.ghost.io/ghost/api/content/posts/?key=22444f78447824223cefc48062"

Ça retourne du JSON avec une liste de posts. En JS, on utilise fetch ou le SDK officiel.

Les ressources disponibles : Ce que vous pouvez récupérer

La Content API expose plusieurs endpoints en lecture seule. Tous en GET, avec pagination par défaut (15 items).

Posts

Les articles principaux. Endpoint : /content/posts/.

  • Browse : Liste tous les posts publiés (triés par published_at DESC, le plus récent en premier).
  • Read : Un post par ID (/posts/{id}/) ou slug (/posts/slug/{slug}/).

Paramètres spécifiques : Filtrez par statut (published), incluez auteurs/tags.

Réponse : Array posts avec id, slug, title, html, feature_image, excerpt, url, created_at, etc. Sans include, pas d'auteurs ou tags.

Exemple réponse (simplifié) :

{
  "posts": [
    {
      "id": "5c7ece47da174000c0c5c6d7",
      "slug": "welcome-short",
      "title": "Welcome",
      "html": "<p>Bienvenue sur Ghost.</p>",
      "feature_image": "https://example.com/image.jpg",
      "url": "https://votre-site.com/welcome-short/",
      "excerpt": "Un extrait court.",
      "published_at": "2025-01-01T10:00:00.000Z"
    }
  ]
}

Pages

Comme les posts, mais pour du contenu statique (ex. : À propos). Endpoint : /content/pages/.

  • Tri par défaut : title ASC (alphabétique).
  • Même structure que posts, mais clé pages dans la réponse.

Utilisez pour des landing pages custom dans votre frontend.

Tags

Catégories pour organiser le contenu. Endpoint : /content/tags/.

  • Browse : Liste triée par name ASC.
  • Read : Par ID ou slug.
  • Incluez count.posts pour le nombre d'articles par tag.

Réponse : Array tags avec id, name, slug, description, visibility (public/internal), url.

Exemple : Filtrez les posts d'un tag avec filter=tag:mon-tag sur l'endpoint posts.

Authors

Les contributeurs. Endpoint : /content/authors/.

  • Browse : Tri par name ASC. Seulement les auteurs avec au moins un post.
  • Read : Par ID ou slug.
  • Incluez count.posts pour le nombre de publications.

Réponse : Array authors avec id, name, slug, bio, profile_image, url, website, twitter, etc.

Idéal pour des pages auteur dynamiques.

Tiers

Pour les sites avec memberships. Endpoint : /content/tiers/.

  • Liste triée par prix mensuel croissant.
  • Incluez monthly_price, yearly_price, benefits pour les détails payants.

Réponse : Array tiers avec id, name, slug, description, active, type (free/paid), currency, welcome_page_url, timestamps.

Utilisez pour afficher les abonnements sur votre portail custom.

Settings

Configuration du site. Endpoint : /content/settings/.

  • Retourne un objet unique avec title, description, logo, icon, navigation (array de liens), meta_title, og_image, etc. Pas de paramètres.

Parfait pour fetcher le menu ou le logo une seule fois au chargement de l'app.

Paramètres essentiels : Filtrage, pagination et plus

Tous les endpoints "browse" acceptent ces query params. Ils sont URL-encodés automatiquement en JS.

Filtrage (filter)

Utilise NQL (Ghost's query language). Syntaxe : propriété:opérateur valeur. Opérateurs : =, !=, >, <, in, ~ (contient), + (AND), , (OR), - (NOT), parenthèses pour grouper.

  • Propriétés : Champs comme title, slug, ou inclus comme authors.slug.
  • Valeurs : Strings (sans quotes sauf pour dates/cols), nombres, null, true/false, dates relatives (now-30d).

Exemples pour posts :

  • filter=tag:tech : Posts avec tag "tech".
  • filter=author:john+published_at>2025-01-01 : Posts de John après janvier 2025.
  • filter=title:~ghost : Titres contenant "ghost".
  • filter=(tag:news+tag:-premium) : News mais pas premium.

Pour tags : filter=visibility:public.
Pour authors : filter=name:john.

Pas de partial matches (pas de LIKE), et encodé URL (ex. : %20 pour espaces).

Pagination

Par défaut : 15 items/page. Utilisez limit (1-100) et page (1-based).

  • Réponse inclut meta.pagination : {page: 1, limit: 15, pages: 5, total: 75, next: 2, prev: null}.

Cursor-based pour de gros volumes (avancé, pas détaillé ici).

Exemple : ?limit=5&page=2.

Autres paramètres

  • Fields : Limitez les champs retournés, ex. ?fields=title,slug,url pour optimiser (mais évitez avec include).
  • Include : Ajoutez relations. Ex. ?include=authors,tags pour posts (ajoute arrays authors, tags, et primary_author/tag). Pour tiers : ?include=monthly_price,benefits.
  • Order : Tri custom, ex. ?order=published_at asc (SQL-like).
  • Formats (posts/pages) : ?formats=html,plaintext pour du texte brut en plus du HTML.

Le SDK JavaScript : Simplifiez vos requêtes

Ghost fournit un client JS officiel (@tryghost/content-api) pour éviter les fetch manuels. Installez via npm : npm install @tryghost/content-api.

Initialisation :

import GhostContentAPI from '@tryghost/content-api';

const api = new GhostContentAPI({
  url: 'https://votre-site.com',  // Sans slash final
  key: 'votre-cle-hex',
  version: 'v5.0'  // Ou v6.0
});

Méthodes : browse({options}) pour listes (Promise avec meta), read({id ou slug}, options) pour single.

Exemples pour fetcher des posts

// Liste de 5 posts avec auteurs et tags
api.posts.browse({ limit: 5, include: 'tags,authors' })
  .then(posts => {
    posts.forEach(post => {
      console.log(`${post.title} par ${post.primary_author.name}`);
    });
  })
  .catch(err => console.error('Erreur:', err));

// Post par slug, avec plaintext
api.posts.read({ slug: 'mon-article' }, { formats: ['html', 'plaintext'] })
  .then(post => console.log(post.plaintext))
  .catch(err => console.error(err));

Filtrage et pagination en SDK

// Posts d'un tag, page 2
api.posts.browse({
  filter: 'tag:tech',
  limit: 10,
  page: 2,
  include: 'authors'
}).then(posts => {
  console.log(`Page ${posts.meta.pagination.page} de ${posts.meta.pagination.pages}`);
});

Pour authors et tags

// Auteurs avec count posts
api.authors.browse({ include: 'count.posts' })
  .then(authors => authors.forEach(author => console.log(`${author.name}: ${author.count.posts} posts`)));

// Tag par slug
api.tags.read({ slug: 'news' }, { include: 'count.posts' });

Settings

api.settings.browse().then(settings => {
  console.log(`Titre du site: ${settings.title}`);
  console.log('Navigation:', settings.navigation);
});

Pour tiers :

api.tiers.browse({ include: 'monthly_price,benefits' })
  .then(tiers => tiers.forEach(tier => console.log(`${tier.name}: ${tier.monthly_price} €/mois`)));

Le SDK gère l'encodage et les erreurs automatiquement.

Gestion des erreurs

Réponses en JSON avec errors array : {message: "Détail", errorType: "Type"}.

  • 400 Bad Request : Filtre mal formé (ex. non encodé).
  • 401 Unauthorized : Clé invalide.
  • 404 Not Found : Ressource inexistante (ex. post draft).
  • 500 Internal : Erreur serveur.

En JS :

.catch(err => {
  if (err.status === 400) console.log('Vérifiez votre filtre');
  else console.error(err.message);
});

Filtre sans match ? Array vide, pas d'erreur.

Exemples pratiques pour un frontend headless

1. Liste de blog avec Next.js

Dans pages/index.js :

import GhostContentAPI from '@tryghost/content-api';

const api = new GhostContentAPI({ /* config */ });

export async function getStaticProps() {
  const posts = await api.posts.browse({
    limit: 10,
    include: 'tags',
    filter: 'featured:false'  // Exclut les featured si besoin
  });
  return { props: { posts } };
}

export default function Home({ posts }) {
  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2><a href={post.url}>{post.title}</a></h2>
          <p>{post.excerpt}</p>
          <ul>{post.tags.map(tag => <li key={tag.id}>{tag.name}</li>)}</ul>
        </article>
      ))}
    </div>
  );
}

2. Page auteur dynamique

Pour /author/[slug].js :

export async function getStaticPaths() {
  const authors = await api.authors.browse();
  return {
    paths: authors.map(author => ({ params: { slug: author.slug } })),
    fallback: false
  };
}

export async function getStaticProps({ params }) {
  const author = await api.authors.read({ slug: params.slug }, { include: 'count.posts' });
  const posts = await api.posts.browse({ filter: `authors:${author.slug}`, limit: 5 });
  return { props: { author, posts } };
}

3. App mobile avec React Native (ex. pour newsletters)

Utilisez le SDK dans un hook :

import { useState, useEffect } from 'react';

function PostsList() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    api.posts.browse({ limit: 20, include: 'authors' })
      .then(setPosts)
      .catch(console.error);
  }, []);

  return posts.map(post => <Text key={post.id}>{post.title}</Text>);
}

Pour tiers dans une app Stripe-integrated : Fetch tiers et affichez les prix/bénéfices.

4. Pagination infinie avec Gatsby

Dans un query GraphQL (Gatsby source-ghost) ou fetch manuel avec page incrementé onScroll.

Bonnes pratiques pour 2026

  • Cachez : Utilisez Redis ou Vercel Edge pour éviter les appels répétés.
  • Optimisez : fields pour payloads légers, include seulement si besoin.
  • Versionnez : Toujours spécifiez v5.0 pour éviter les breaks.
  • Tests : Utilisez demo.ghost.io pour prototyper.
  • Scalabilité : Pour startups, combinez avec webhooks pour rebuilds automatiques (ex. sur publication).

En résumé, la Content API rend Ghost ultra-flexible pour des frontends modernes. Commencez par installer le SDK, fetcher des posts basiques, et itérez. Si vous monétisez, intégrez tiers tôt. Pour des tutos plus avancés, checkez la doc officielle.