Automated blog posts for Webflow
Automated blog posts for Webflow: create unpublished Webflow CMS items from Crontent post.ready webhooks via the CMS API.
How-to: Integrate Crontent with Webflow. Official docs: Webflow. Shared API contract: Publishing API and SDK.
Verify the Crontent webhook, fetch the post, and POST an unpublished Webflow CMS collection item so editors publish from the Designer.
Field mapping
| Crontent field | Webflow CMS field (example) |
|---|---|
title | name |
seo_title | seo-title custom field |
slug | slug |
excerpt | post-summary RichText or PlainText |
body_mdx | Convert to HTML for RichText post-body |
body_html | post-body RichText |
tags | Multi-reference or PlainText |
hero_image_url | main-image Image field (upload or URL) |
canonical_url | canonical-url PlainText |
sources[] | Append to body or separate RichText field |
Webhook to Webflow draft
Create a CMS collection with fields matching the table above. Generate a site API token with CMS write scope.
import { Crontent } from "@crontent/sdk";
const crontent = new Crontent({ apiKey: process.env.CRONTENT_API_KEY! });
async function createWebflowDraft(post: Awaited<ReturnType<typeof crontent.posts.get>>) {
const sourcesHtml = post.sources.length
? `<h2>Sources</h2><ul>${post.sources.map((s) =>
`<li><a href="${s.url}">${s.title ?? s.url}</a></li>`
).join("")}</ul>`
: "";
const res = await fetch(
`https://api.webflow.com/v2/collections/${process.env.WEBFLOW_COLLECTION_ID}/items`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WEBFLOW_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
isArchived: false,
isDraft: true,
fieldData: {
name: post.title,
slug: post.slug,
"post-summary": post.excerpt,
"post-body": (post.body_html ?? "") + sourcesHtml,
"seo-title": post.seo_title ?? post.title,
"canonical-url": post.canonical_url,
},
}),
}
);
if (!res.ok) throw new Error(await res.text());
return res.json();
}
export async function handleWebhook(req: Request) {
const rawBody = await req.text();
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 createWebflowDraft(post);
return new Response("ok");
}Publish from Webflow after reviewing layout, images, and SEO settings.
Image handling
Webflow image fields often require an asset upload step. Upload hero_image_url via the Assets API before linking the field.
Backfill
Import via posts.list. Check existing slugs with a collection items list call to avoid duplicates.