crontent
Integration·Astro·beginner

Scheduled blog posts for Astro

Scheduled blog posts for Astro: wire Crontent webhooks into a server endpoint and content collections or a headless CMS.

How-to: Integrate Crontent with Astro. Official docs: Astro. Shared API contract: Publishing API and SDK.

Expose a server endpoint, verify the Crontent webhook, fetch the post, append a draft to an Astro content collection (or CMS), then rebuild or render on demand after review.

Field mapping

Crontent fieldAstro usage
titleCollection title, page <h1>
seo_titleLayout <title>, Open Graph (fallback: title)
slugslug in collection schema
excerptdescription frontmatter
body_mdxMD body in src/content/posts/
body_htmlUse if you render HTML with set:html
tagstags array in schema
hero_image_urlimage field, <Image> src
canonical_url<link rel="canonical"> in layout
sources[]Custom sources field or footer component

Webhook endpoint

Enable SSR with an adapter (Vercel, Netlify) and add an API route.

ts
// src/pages/api/crontent-webhook.ts
import type { APIRoute } from "astro";
import { Crontent } from "@crontent/sdk";
import { writeFile, mkdir } from "node:fs/promises";
import { join } from "node:path";

const crontent = new Crontent({ apiKey: import.meta.env.CRONTENT_API_KEY });

export const POST: APIRoute = async ({ request }) => {
  const rawBody = await request.text();
  const valid = await crontent.webhooks.verify({
    rawBody,
    signature: request.headers.get("x-crontent-signature"),
    timestamp: request.headers.get("x-crontent-timestamp"),
    secret: import.meta.env.CRONTENT_WEBHOOK_SECRET,
  });
  if (!valid) return new Response("unauthorized", { status: 401 });

  const { post_id } = JSON.parse(rawBody);
  const post = await crontent.posts.get(post_id);

  const dir = join(process.cwd(), "src/content/posts");
  await mkdir(dir, { recursive: true });
  const yaml = [
    "---",
    `title: ${JSON.stringify(post.title)}`,
    `description: ${JSON.stringify(post.excerpt ?? "")}`,
    `pubDate: ${JSON.stringify(post.ready_at ?? post.created_at)}`,
    `draft: true`,
    `tags: ${JSON.stringify(post.tags)}`,
    `heroImage: ${JSON.stringify(post.hero_image_url ?? "")}`,
    "---",
    "",
    post.body_mdx,
  ].join("\n");
  await writeFile(join(dir, `${post.slug}.md`), yaml);

  return new Response("ok");
};

Define the collection in src/content/config.ts with a draft boolean. Filter drafts out of production listings until you flip the flag.

Create drafts in a CMS

For Sanity or similar, replace the filesystem write with an unpublished document create. Astro fetches at build time via the CMS SDK.

Backfill

Run a one-off script calling crontent.posts.list({ projectId, since, limit, cursor }) and write missing slugs. Astro rebuilds pick up new collection files automatically.

Environment variables

text
CRONTENT_API_KEY=...
CRONTENT_WEBHOOK_SECRET=...

Further reading