# Publish Webflow

> Use when the user wants to publish a blog post draft to Webflow CMS via the Webflow API v2. Reads a markdown draft, maps frontmatter to CMS fields, creates or updates the CMS item, and optionally publishes it live.

- **Type:** Skill
- **Install:** `agentstack add skill-busyeugene-content-marketing-skills-publish-webflow`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [busyeugene](https://agentstack.voostack.com/s/busyeugene)
- **Installs:** 0
- **Category:** [Content & Media](https://agentstack.voostack.com/c/content-and-media)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [busyeugene](https://github.com/busyeugene)
- **Source:** https://github.com/busyeugene/content-marketing-skills/tree/main/publish-webflow
- **Website:** https://busyless.space/

## Install

```sh
agentstack add skill-busyeugene-content-marketing-skills-publish-webflow
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Publish → Webflow

Publish a local markdown draft to Webflow CMS using the [Webflow Data API v2](https://developers.webflow.com/data/reference/rest-introduction).

## 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 via `GET https://api.webflow.com/v2/sites`.
- `WEBFLOW_COLLECTION_ID` — the collection ID for your blog. Get it via `GET /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

1. **Draft path** — markdown file under `posts/`.
2. **Mode** — `draft` (create as unpublished) or `live` (create and publish). Default `draft`.
3. **Update existing?** — if an item with the same slug exists, `skip`, `update`, or `create-new`. Default `update`.
4. **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_image` is a local path, upload via the Webflow 2-step asset flow:
    1. `POST /v2/sites/{site_id}/assets` with `{ "fileName": "...", "fileHash": "" }`. Response contains `uploadUrl` and `uploadDetails` (S3 presigned fields).
    2. POST the file as multipart/form-data to `uploadUrl`, including every field from `uploadDetails` before the `file` field. Use the returned asset `hostedUrl` in `fieldData`.
  - 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/live` create-live endpoint above in step 4 instead of the staged create.
- **Existing staged item**: PATCH the item with `isDraft: false` via `PATCH /v2/collections/{collection_id}/items/{item_id}/live` (the `/live` bulk-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 `skip` mode, stop and report.

## Verification

1. Item exists in the target collection (`GET /items/{id}` returns 200).
2. `fieldData` contains all mapped fields with expected values.
3. If mode = live, the public URL returns 200.
4. `publish-log.md` has a new row.
5. 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](https://github.com/busyeugene)
- **Source:** [busyeugene/content-marketing-skills](https://github.com/busyeugene/content-marketing-skills)
- **License:** MIT
- **Homepage:** https://busyless.space/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-busyeugene-content-marketing-skills-publish-webflow
- Seller: https://agentstack.voostack.com/s/busyeugene
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
