Published
December 26, 2025
Webflow's CMS is powerful but it has some limitations.
- Creating and Updating items. Full capabilities are available however there is some inherent friction- you must log into Webflow and use the designer, which isn't the smoothest experience when editing e.g. large rich-text content.
- Likewise, it pushes you through an item-level publishing process, where new changes are assumed to be "draft changes." This can work very well but often, you just want to make a change and have it published immediately with as few clicks as possible.
- Rich-text capabilities are limited, e.g. no table support.
Notion's content block-structure is very appealing because it maps nicely to the structures in Webflow's rich-text elements.
Our Experience
Sygnal uses Notion for a lot of our content management and recently we've begin using it with direct Webflow integration to update our CMS content.
This KB section of our website is a new initiative which is entirely Notion-driven, with 100% of the content written in Notion and synced to the CMS.
Technology
What Notion offers
- Webhooks (official): Subscribe to page/database events (e.g.,
page.content_updated,database.item.updated). Notion sends a POST to your endpoint; you then call the Notion API to fetch the new content. Notion Developers - API (official): Use the Notion API to get full data, including content blocks, and to perform content manipulations.
- Manual Automations → "Send webhook" action: Setup a button, database button, or database-automation and Notion can POST to your URL when a page/property changes. Notion
High-level flow
- In Notion, enable either:
- Webhook target = your Cloudflare Worker URL. Worker verifies request and queues work.
- Worker calls Notion API to read page properties and blocks (latest content). Notion Developers
- Convert blocks → HTML (custom renderer or library).
- Call Webflow CMS API to create/update the mapped item; publish per current API rules. developers.webflow.com+1
Notes
- Webhook payload is a signal (IDs, timestamps), not full content → you must fetch the page/blocks after receiving it. Notion Developers
- Use page/database filters in Notion to limit which edits fire your automation.
- Webflow: map fields (title, slug, rich text HTML, references), then publish the updated item. Review 2025 CMS publishing changes. developers.webflow.com+1
Minimal Worker sketch (TypeScript)
export default {
async fetch(req: Request, env: Env) {
// 1) Validate source (optionally check a shared secret header from Notion Automation)
if (req.method !== "POST") return new Response("OK");
const event = await req.json(); // contains page/database IDs
// 2) Fetch latest from Notion
const pageId = event.data?.id ?? event.payload?.page?.id; // handle both webhook/automation shapes
const page = await fetch("https://api.notion.com/v1/pages/" + pageId, {
headers: {
"Authorization": `Bearer ${env.NOTION_TOKEN}`,
"Notion-Version": "2022-06-28"
}
}).then(r=>r.json());
const blocks = await fetch(`https://api.notion.com/v1/blocks/${pageId}/children?page_size=100`, {
headers: {
"Authorization": `Bearer ${env.NOTION_TOKEN}`,
"Notion-Version": "2022-06-28"
}
}).then(r=>r.json());
// 3) Convert Notion blocks -> HTML (implement renderBlocks(blocks.results))
const html = renderBlocks(blocks.results);
// 4) Upsert Webflow CMS item
const siteId = env.WF_SITE_ID, collectionId = env.WF_COLLECTION_ID;
const itemPayload = {
isArchived: false,
isDraft: false,
fieldData: {
name: page.properties?.Name?.title?.[0]?.plain_text ?? "Untitled",
slug: slugify(page.properties?.Slug?.rich_text?.[0]?.plain_text ?? pageId),
rich_content: html
}
};
const upsert = await fetch(`https://api.webflow.com/v2/collections/${collectionId}/items`, {
method: "POST",
headers: {
"Authorization": `Bearer ${env.WEBFLOW_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify(itemPayload)
}).then(r=>r.json());
// 5) Publish (check latest API behavior)
await fetch(`https://api.webflow.com/v2/sites/${siteId}/publish`, {
method: "POST",
headers: { "Authorization": `Bearer ${env.WEBFLOW_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({ publishItems: [{ collectionId, itemId: upsert.id }] })
});
return new Response("OK");
}
}Rich Text
Block → HTML conversion
- Either write a small renderer (paragraph, headings, bulleted/numbered lists, callouts, images, code) or use an OSS converter and adapt output.
- Preserve inline annotations (bold/italic/link), map Notion image/file URLs to permanent hosts if needed.
Technical Notes
Security & reliability
- Use a shared secret header in Notion Automation; verify in Worker. Queue heavy work with
waitUntil. - Idempotency: store a
last_synced_tsper page to avoid duplicate publishes. - Rate limits: both Notion and Webflow have quotas; backoff and retry.
Alternatives
- Zapier/Make: "Notion database item updated" → "Webflow CMS: Create/Update Item." Faster to start but less control. Zapier+2Zapier+2
This setup gives you page-change triggers from Notion and a deterministic CMS update into Webflow.
