AI content for WordPress
AI content for WordPress: land Crontent posts as WordPress drafts over the REST API using Application Passwords.
How-to: Integrate Crontent with WordPress. Official docs: WordPress. Shared API contract: Publishing API and SDK.
Verify the Crontent webhook, fetch the post, then create a WordPress draft (status: draft) via the REST API using Application Passwords.
What you need
- Crontent
CRONTENT_API_KEYandCRONTENT_WEBHOOK_SECRET - WordPress 5.6+ with REST API enabled
- Application Password for a user with
edit_postscapability (Users → Profile → Application Passwords) - Webhook receiver with
@crontent/sdk
Register the webhook:
await crontent.webhooks.register({
projectId: process.env.CRONTENT_PROJECT_ID!,
url: "https://your-app.com/api/crontent/webhook",
events: ["post.ready"],
});Map Crontent fields to WordPress
| Crontent field | WordPress field | Notes |
|---|---|---|
title | title | Post title, rendered as H1 by theme |
seo_title | Yoast/RankMath meta or title | Plugin meta field; fall back to title |
slug | slug | Post slug |
excerpt | excerpt | Manual excerpt |
body_mdx / body_html | content | HTML content block |
tags | tags | Tag names (WP creates if missing) |
hero_image_url | Featured media | Upload or sideload separately |
canonical_url | SEO plugin meta | Yoast _yoast_wpseo_canonical etc. |
sources[] | Custom field or footer HTML | Append cited sources |
ready_at | meta.crontent_ready_at | Custom post meta |
Crontent runs research and produces a draft from your brief; you control angle. WordPress drafts are visible only to editors until published.
Receive and verify webhooks
Use X-Crontent-Delivery for idempotency. Crontent retries at 1m, 5m, 30m, 2h, 12h on failure.
import { Crontent } from "@crontent/sdk";
const crontent = new Crontent({ apiKey: process.env.CRONTENT_API_KEY! });
export async function POST(req: Request) {
const rawBody = await req.text();
const deliveryId = req.headers.get("x-crontent-delivery")!;
if (await seen(deliveryId)) return new Response("OK");
const valid = await crontent.webhooks.verify({
rawBody,
signature: req.headers.get("x-crontent-signature"),
timestamp: req.headers.get("x-crontent-timestamp"),
secret: process.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);
await createWordPressDraft(post);
await markSeen(deliveryId);
return new Response("OK");
}Create a draft in WordPress
POST to /wp-json/wp/v2/posts with Basic auth (Application Password). Set status: "draft".
async function createWordPressDraft(post: Post) {
const sourcesHtml = post.sources
.map((s) => `<li><a href="${s.url}">${s.title ?? s.url}</a> (${s.publisher ?? ""})</li>`)
.join("");
const auth = Buffer.from(
`${process.env.WP_USER}:${process.env.WP_APP_PASSWORD}`
).toString("base64");
const res = await fetch(`${process.env.WP_URL}/wp-json/wp/v2/posts`, {
method: "POST",
headers: {
Authorization: `Basic ${auth}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: post.title,
slug: post.slug,
excerpt: post.excerpt,
content: `${post.body_html ?? post.body_mdx}<h2>Sources</h2><ul>${sourcesHtml}</ul>`,
status: "draft",
tags: post.tags,
meta: {
crontent_post_id: post.id,
crontent_ready_at: post.ready_at,
},
}),
});
if (!res.ok) throw new Error(await res.text());
}Register custom meta keys in functions.php or a small plugin if you need them exposed via REST.
Backfill and polling
Poll crontent.posts.list({ projectId, since, limit, cursor }) and skip posts whose crontent_post_id meta already exists. Query /wp-json/wp/v2/posts?meta_key=crontent_post_id&meta_value={id}.