Skip to content

API reference

Every export of @ghostwritr/react-router. The fetchers, the webhook verifier, GhostwritrError, and the types are re-exported from the shared @ghostwritr/feed core, so the contract is identical across the Next / Astro / React Router SDKs. articleMeta is React-Router-specific.

function fetchArticles(options: FeedFetchOptions): Promise<Article[]>

Every published Article for the site, newest first. Resolves the feed manifest, then walks each immutable build page (20 per page) and de-duplicates by id. Returns [] when the feed has zero pages; throws GhostwritrError NOT_FOUND when the site has no feed yet. Call it inside a route loader.

function fetchArticle(
options: FeedFetchOptions & { slug: string },
): Promise<Article | null>

One published article by slug, read directly from the by-slug document (no page walk), or null when the slug isn’t in the current feed. A blank slug throws GhostwritrError CONFIG. Throw a 404 Response yourself on null — see Writing loaders.

interface FeedFetchOptions {
siteId: string; // the unguessable keyless read key (required)
staticBaseUrl?: string; // feed origin override; default DEFAULT_STATIC_BASE_URL
fetch?: typeof fetch; // your own fetch (caching/retries/test stub)
}
function articleMeta(article: Article, opts?: ArticleMetaOptions): MetaDescriptor[]

Builds the React Router MetaDescriptor[] for an article route’s meta export: <title> (from seoTitle), description, Open Graph + Twitter cards, <link rel="canonical">, and schema.org Article JSON-LD. Full breakdown in SEO meta.

interface ArticleMetaOptions {
url?: string; // canonical + og:url for THIS page; wins over article.canonicalUrl
siteName?: string; // og:site_name + JSON-LD publisher
twitterSite?: string; // twitter:site @handle
}

verifyWebhook(secret, rawBody, headers, opts?)

Section titled “verifyWebhook(secret, rawBody, headers, opts?)”
function verifyWebhook(
secret: string,
rawBody: string,
headers: Headers | Record<string, string | null | undefined>,
opts?: { toleranceSeconds?: number; now?: number },
): Promise<boolean>

Verify a signed, replay-safe feed.updated webhook. Reads Webhook-Id / Webhook-Timestamp / Webhook-Signature from the headers object itself, rejects a timestamp outside toleranceSeconds (default 300), and constant-time compares the v1 HMAC of `${id}.${timestamp}.${rawBody}`. Returns false for any missing, stale, or mismatched input. Web Crypto — runs in Node 20+ and on edge. See Feed-freshness webhook.

WEBHOOK_ID_HEADER · WEBHOOK_TIMESTAMP_HEADER · WEBHOOK_SIGNATURE_HEADER

Section titled “WEBHOOK_ID_HEADER · WEBHOOK_TIMESTAMP_HEADER · WEBHOOK_SIGNATURE_HEADER”
const WEBHOOK_ID_HEADER = "Webhook-Id"
const WEBHOOK_TIMESTAMP_HEADER = "Webhook-Timestamp"
const WEBHOOK_SIGNATURE_HEADER = "Webhook-Signature"

The three headers carried by every delivery. Webhook-Id is the per-delivery idempotency key (dedupe retries on it); Webhook-Timestamp is unix seconds; Webhook-Signature carries v1=<hex>.

const DEFAULT_STATIC_BASE_URL = "https://feeds.ghostwritr.io"

The default static-feed origin. Override per call with FeedFetchOptions.staticBaseUrl.

class GhostwritrError extends Error {
readonly status: number; // HTTP status, or 0 for client-side errors
readonly code: GhostwritrErrorCode | null;
readonly details?: unknown;
}

The single error type the fetchers throw. code is one of CONFIG, NETWORK_ERROR, NOT_FOUND, RATE_LIMITED, SERVER_ERROR, INVALID_RESPONSE (and the auth codes UNAUTHORIZED / FORBIDDEN). See Error handling.

From @ghostwritr/feed, available as type-only imports:

  • Article — a published article. See The article shape.
  • ArticleImage — the cover image (url, alt, width, height, srcset), or null.
  • Author — the byline; kind is "real" or "persona" and drives the JSON-LD type.
  • ArticleOrder"published-desc" (default, newest first) or "feed".
  • FeedFetchOptions — the fetcher options above.
  • FeedUpdatedPayload — the webhook body (event, siteId, buildId).
  • GhostwritrErrorCode — the union of error codes.
  • ArticleMetaOptions — the articleMeta options above (this package’s own type).