The article shape
Every read across every SDK returns the same Article. The type is hand-authored and stable, and it reflects a single rule: a published article is a complete article. Guaranteed fields are always present; only genuinely-optional fields are nullable.
The Article interface
Section titled “The Article interface”interface Article { id: string; title: string; // your <h1> slug: string; // your route param markdown: string; // the body, as Markdown seoTitle: string; // SEO <title>; falls back to title seoDescription: string | null; canonicalUrl: string | null; formatType: string; // schema.org type hint, e.g. "Article" inLanguage: string; // BCP-47, e.g. "en" tags: string[]; // possibly empty image: ArticleImage | null; author: Author; // always present publishedAt: string; // ISO-8601 createdAt: string; // ISO-8601 updatedAt: string; // ISO-8601}| Field | Type | Nullable | Notes |
|---|---|---|---|
id | string | no | Stable identifier; use as a React key. |
title | string | no | The on-page title (your <h1>). |
slug | string | no | URL-safe; your route param. |
markdown | string | no | The article body as Markdown. Render with <ArticleContent>. |
seoTitle | string | no | The SEO <title>. Server-side falls back to title, so it’s always set. |
seoDescription | string | null | yes | Meta description, or null — let the search engine derive one. |
canonicalUrl | string | null | yes | Absolute canonical URL, or null if the site has no URL set. |
formatType | string | no | schema.org type hint (e.g. "Article"). |
inLanguage | string | no | BCP-47 tag for <html lang> / inLanguage. |
tags | string[] | no | Short topical tags; the array may be empty. |
image | ArticleImage | null | yes | The cover image, or null when none was generated. |
author | Author | no | The byline — always present. |
publishedAt | string | no | ISO-8601 publish timestamp. |
createdAt | string | no | ISO-8601 row-created timestamp. |
updatedAt | string | no | ISO-8601 row-updated timestamp. |
ArticleImage
Section titled “ArticleImage”The cover is genuinely optional — image is null when an article has no generated cover.
interface ArticleImage { url: string; // ready-to-use responsive URL (the default/large width) alt: string; // may be "" for a decorative cover width: number | null; // set <img width/height> from these to prevent layout shift height: number | null; srcset: string | null; // a responsive srcset string, or null}| Field | Type | Nullable | Notes |
|---|---|---|---|
url | string | no | When image is present, url is always present. |
alt | string | no | Present, but may be an empty string. |
width | number | null | yes | Master width; pair with height to reserve space. |
height | number | null | yes | Master height. |
srcset | string | null | yes | Drop straight into <img srcset> when set. |
Author
Section titled “Author”The byline is always present. kind is what selects the structured-data type — see SEO & JSON-LD.
interface Author { name: string; slug: string; bio: string | null; avatarUrl: string | null; jobTitle: string | null; sameAs: string[]; // profile/social URLs → Person.sameAs kind: "real" | "persona"; // "real" → Person, "persona" → Organization}| Field | Type | Nullable | Notes |
|---|---|---|---|
name | string | no | Display name. |
slug | string | no | URL-safe author identifier. |
bio | string | null | yes | Short biography, or null. |
avatarUrl | string | null | yes | Avatar image URL, or null. |
jobTitle | string | null | yes | Role/title (e.g. "Senior Engineer"), or null. |
sameAs | string[] | no | Profile/social URLs; may be empty. |
kind | "real" | "persona" | no | Drives Person vs Organization JSON-LD. |
The nullability contract
Section titled “The nullability contract”The split is deliberate and load-bearing — you can rely on it in TypeScript without optional chaining where the contract guarantees presence.
- Guaranteed (never
null):markdown,seoTitle,publishedAt, andauthor— these have server-side fallbacks, so the feed never ships them empty. - Genuinely optional (
nullwhen absent):seoDescription,canonicalUrl, andimage.
A published article that is missing a guaranteed field is a contract violation, not a null — the SDK throws a GhostwritrError with code INVALID_RESPONSE rather than coercing it. See Error handling.