Install
$ agentstack add skill-busyeugene-content-marketing-skills-publish-webflow ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Publish → Webflow
Publish a local markdown draft to Webflow CMS using the Webflow Data API v2.
Setup
Required:
WEBFLOW_API_TOKEN— a Data API token (Site Settings → Apps & Integrations → API Access). Scopes needed:cms:read,cms:write,sites:read, and (if publishing live)sites:write.WEBFLOW_SITE_ID— the site ID. Get it viaGET https://api.webflow.com/v2/sites.WEBFLOW_COLLECTION_ID— the collection ID for your blog. Get it viaGET /v2/sites/{site_id}/collections.
Optional:
SLACK_WEBHOOK_URL— Slack notification when a post goes live.
All requests use:
Authorization: Bearer ${WEBFLOW_API_TOKEN}
accept-version: 2.0.0
Content-Type: application/json
Inputs
- Draft path — markdown file under
posts/. - Mode —
draft(create as unpublished) orlive(create and publish). Defaultdraft. - Update existing? — if an item with the same slug exists,
skip,update, orcreate-new. Defaultupdate. - Field mapping override — optional: path to a JSON file mapping markdown frontmatter keys to Webflow field slugs. Default mapping is documented below.
Use AskUserQuestion for mode and existing-item handling.
Process
1. Validate collection schema
Call GET /v2/collections/{WEBFLOW_COLLECTION_ID} and cache the field list. For each required frontmatter field, confirm there's a matching Webflow field. If a field is missing, fail fast with a clear error naming which Webflow field to add.
Default frontmatter → Webflow field mapping:
| Frontmatter key | Webflow field slug | Type | |---|---|---| | title | name | PlainText | | slug | slug | PlainText | | meta_title | meta-title | PlainText | | meta_description | meta-description | PlainText | | author | author | Reference (fallback PlainText) | | date | published-on | Date | | hero_image | hero-image | Image | | hero_alt | hero-alt | PlainText | | keyword | primary-keyword | PlainText | | status | — | (ignored; controlled by publish mode) | | body | post-body | RichText |
If the user provides a custom mapping file, it overrides the defaults.
2. Load and transform the draft
- Read the draft file.
- Parse frontmatter.
- Convert the markdown body to Webflow-compatible HTML (rich text field accepts HTML). Preserve: headings, paragraphs, lists, code blocks, blockquotes, images, internal and external links. Strip any HTML comments.
- For the hero image:
- If
hero_imageis a local path, upload via the Webflow 2-step asset flow:
POST /v2/sites/{site_id}/assetswith{ "fileName": "...", "fileHash": "" }. Response containsuploadUrlanduploadDetails(S3 presigned fields).- POST the file as multipart/form-data to
uploadUrl, including every field fromuploadDetailsbefore thefilefield. Use the returned assethostedUrlinfieldData.
- If it's already a URL, use it directly.
- For author as a reference field: resolve the name to an author collection item ID via
GET /v2/collections/{authors_collection}/items?name={author}. If not found, create the author item (or fall back to PlainText if the field allows).
3. Check for existing item
GET /v2/collections/{WEBFLOW_COLLECTION_ID}/items?slug={slug}
- If found and mode is
skip→ stop and report. - If found and mode is
update→PATCH /v2/collections/{collection_id}/items/{item_id}with the new field values. - If found and mode is
create-new→ append a timestamp to the slug, then create. - If not found → create.
4. Create or update
Create (staged / draft):
POST /v2/collections/{WEBFLOW_COLLECTION_ID}/items
{
"isArchived": false,
"isDraft": ,
"fieldData": { ...mapped fields }
}
For bulk creates (multiple posts at once), use POST /v2/collections/{collection_id}/items/bulk with { "items": [...] }. As of Dec 2024 newly-created items default to isDraft: true unless the live endpoint is used.
Create live (mode = live, single call): use POST /v2/collections/{collection_id}/items/live with the same body and isDraft: false. This creates and publishes in one shot.
Update:
PATCH /v2/collections/{WEBFLOW_COLLECTION_ID}/items/{item_id}
{
"fieldData": { ...mapped fields }
}
Handle the response: capture id (item ID), lastUpdated, and the fieldData.slug.
5. Publish live (if mode = live)
There is no per-item "promote to live" endpoint in the Data API v2. Options:
- New item: use the
POST /v2/collections/{collection_id}/items/livecreate-live endpoint above in step 4 instead of the staged create. - Existing staged item: PATCH the item with
isDraft: falseviaPATCH /v2/collections/{collection_id}/items/{item_id}/live(the/livebulk-publish path accepts an item list) — or publish the entire site, which flushes all staged items.
Then publish the site so any staged changes are visible:
POST /v2/sites/{WEBFLOW_SITE_ID}/publish
{ "publishToWebflowSubdomain": false, "customDomains": [...] }
Or if the user only wants the staging subdomain:
{ "publishToWebflowSubdomain": true }
Ask via AskUserQuestion the first time a publish runs in a session.
6. Optional Slack notification
If SLACK_WEBHOOK_URL is set and publish was successful:
POST ${SLACK_WEBHOOK_URL}
Content-Type: application/json
{ "text": "📤 New post published: " }
7. Write a publish log
Append a line to publish-log.md at the project root:
| {YYYY-MM-DD HH:mm} | webflow | {mode} | {title} | {item_id} | {live URL or "—"} | {status} |
8. Print summary
One block: mode, item ID, live URL (if live), Slack notified (Y/N), publish-log updated.
Fallbacks
- Missing required env var: fail fast with the exact variable name.
- Collection schema mismatch: print the missing field names and the expected types; do not guess.
- Image upload fails: ask the user to upload manually via Webflow and retry; don't create the item without the hero if the field is required.
- Site publish rate limited: create/update the item anyway, skip the site publish, and note it in the log so the user can publish manually from the Webflow dashboard.
Safety
- Default mode is
draft. This skill never publishes live without explicit user confirmation in the current session. - Never delete existing items. If a collision happens in
skipmode, stop and report.
Verification
- Item exists in the target collection (
GET /items/{id}returns 200). fieldDatacontains all mapped fields with expected values.- If mode = live, the public URL returns 200.
publish-log.mdhas a new row.- Slack notification delivered (if configured).
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: busyeugene
- Source: busyeugene/content-marketing-skills
- License: MIT
- Homepage: https://busyless.space/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.