# Webflow CMS Bulk Import & Sync (`coolinbex/webflow-cms-bulk-sync`) Actor

Bulk import, update, and upsert Webflow CMS items from Apify datasets or JSON. Includes field mapping, deduplication, retries, dry runs, and optional publishing.

- **URL**: https://apify.com/coolinbex/webflow-cms-bulk-sync.md
- **Developed by:** [coolinbex](https://apify.com/coolinbex) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Webflow CMS Bulk Import & Sync

**Bulk import, update, and upsert Webflow CMS items from Apify datasets or JSON.** Built for recurring high-volume workflows such as job boards, directories, real-estate listings, events, content feeds, and scraped datasets.

### Why use it

Instead of manually importing CSV files or writing one-off Webflow scripts, connect an Apify dataset to a Webflow CMS collection and run the Actor whenever your source data changes.

- Create, update, or **upsert** CMS items
- Read large Apify datasets in pages
- Use Webflow bulk writes of up to 100 items per request
- Match existing records by `slug`, a custom field, or Webflow item `id`
- Map nested source fields with dot paths
- Skip unchanged items to reduce API calls
- Normalize numbers, booleans, dates, images, option values, and references
- Automatically generate missing slugs
- Retry `429` and transient `5xx` responses
- Respect Webflow's `Retry-After` header
- Isolate bad rows when one record causes a bulk request to fail
- Optional item publishing after sync
- Safe dry-run preview before any CMS changes
- Structured dataset results, run summary, and detected collection schema

### Safe default run

The default input uses a built-in **demo dataset** and forces dry-run mode. It does not require a Webflow token and never changes a Webflow site.

This makes the Actor's first Store run useful instead of failing because credentials are missing.

### Quick start with an Apify dataset

1. Create a Webflow API token with `cms:read` and `cms:write` access.
2. Copy the destination Webflow CMS collection ID.
3. Set **Source** to `Apify dataset`.
4. Enter the source dataset ID or name.
5. Configure field mapping.
6. Keep **Dry run** enabled for the first run.
7. Review the output, then disable dry run to sync.

Example input:

```json
{
  "sourceType": "apifyDataset",
  "sourceDatasetId": "YOUR_DATASET_ID",
  "webflowToken": "YOUR_WEBFLOW_TOKEN",
  "collectionId": "YOUR_COLLECTION_ID",
  "mode": "upsert",
  "matchField": "slug",
  "fieldMapping": {
    "name": "title",
    "slug": "slug",
    "description": "content.html",
    "company": "company.name",
    "salary": "salary.amount",
    "featured": "isFeatured"
  },
  "dryRun": true,
  "publish": false
}
```

### Field mapping

`fieldMapping` is a JSON object where:

```text
Webflow field slug -> source property path
```

Dot paths are supported:

```json
{
  "name": "job.title",
  "slug": "job.slug",
  "company": "company.name"
}
```

With **Auto-map same-name fields** enabled, source keys that already match Webflow field slugs are also mapped automatically.

### Supported Webflow field handling

The Actor reads the collection schema before syncing and uses it to normalize values.

| Webflow field | Accepted source value |
| --- | --- |
| Plain/Rich text, link, email, phone, color, video | string-compatible value |
| Number | number or numeric string |
| Switch / Bool | boolean, `yes/no`, `true/false`, `1/0` |
| DateTime | valid date/time converted to ISO 8601 |
| Image / File | public URL or `{ "url": ..., "alt": ... }` / `fileId` object |
| Multi-image | array of public URLs or asset objects |
| Option | option ID or option name; names are resolved from the collection schema |
| Reference | Webflow item ID |
| Multi-reference | array of Webflow item IDs |

Reference fields intentionally require item IDs in v1. The Actor does not guess which related record a name should reference.

### Sync modes

#### Upsert

Recommended for recurring data pipelines.

- Match found -> update if mapped values changed
- No match -> create
- Unchanged -> skip the API write

#### Create only

Creates new records and never updates a matched record. Existing matches can be marked failed or skipped.

#### Update only

Updates matched records and never creates missing ones.

### Matching and deduplication

`slug` is the safest default match field for most Webflow collections.

You can also use a stable custom key such as:

```text
external-id
source-id
listing-id
job-id
```

If a custom match field exists in the source, the Actor persists it to newly created items even when same-name auto-mapping is disabled. This keeps future upsert runs matchable.

Duplicate source match values are detected. Ambiguous existing Webflow matches are rejected instead of updating an arbitrary record.

### Dry run

Dry run still reads the Webflow collection schema and existing items, so its decisions reflect the real collection. It then outputs:

- `would_create`
- `would_update`
- `unchanged`
- `skipped`
- `failed`

No CMS writes or publishes are performed.

### Publishing

Writes are staged first. When **Publish synced items** is enabled, successfully created or updated item IDs are sent to Webflow's CMS item publish endpoint.

If Webflow only confirms part of a bulk publish, the Actor retries the unconfirmed IDs individually so one publishing problem does not hide the others.

### Failure isolation

A normal bulk integration can lose an entire 100-item batch because one record is invalid.

This Actor handles row-specific `400`, `409`, and `422` errors by recursively splitting the failed batch until it identifies the bad row. Valid records in the same original batch can still succeed.

Authentication, permission, collection-not-found, exhausted rate-limit, and persistent server/network failures remain fatal because retrying every row would only waste requests.

### Output

The default dataset contains one row per processed source record with:

- source index
- status
- match field/value
- Webflow item ID
- publishing result
- mapped field count
- optionally mapped `fieldData`
- warnings
- row-level error code/message
- processing timestamp

The key-value store also contains:

- `SUMMARY` — counts and run metadata
- `COLLECTION_SCHEMA` — detected Webflow fields and types

### Status values

| Status | Meaning |
| --- | --- |
| `created` | Created in Webflow |
| `updated` | Existing Webflow item changed |
| `would_create` | Dry-run create preview |
| `would_update` | Dry-run update preview |
| `unchanged` | Mapped values already matched |
| `skipped` | Deliberately skipped duplicate/existing row |
| `failed` | Row could not be validated or synced |

### Large datasets

The source Apify dataset is streamed in pages instead of being loaded completely into memory. Webflow items are indexed for matching, and source rows are processed in configurable batches up to Webflow's current 100-item bulk limit.

The included test suite verifies a simulated **10,000-item import** with 100 correctly bounded bulk requests.

### Webflow API behavior used

This Actor targets the current Webflow Data API v2 CMS endpoints:

- `GET /v2/collections/{collection_id}`
- `GET /v2/collections/{collection_id}/items`
- `POST /v2/collections/{collection_id}/items/insert`
- `PATCH /v2/collections/{collection_id}/items`
- `POST /v2/collections/{collection_id}/items/publish`

Webflow currently limits CMS bulk create/update operations to 100 items per request and returns `429 Too Many Requests` with `Retry-After` when rate limits are exceeded.

### Security

- Webflow token is an Apify secret input.
- Token contents are never written to dataset rows, summaries, or logs.
- Debug logging only records request method/path and retry attempts.
- The default run is read/write-safe because it uses demo data and dry-run mode.

### Deliberate v1 boundaries

- One Webflow collection per Actor run
- Primary CMS locale only
- Does not delete Webflow items missing from the source
- Reference and multi-reference values require Webflow item IDs
- CMS collections only; not Webflow Ecommerce products or static pages

These boundaries keep bulk sync predictable and safe.

### Local development

```bash
npm install
npm run check
npm test
npm start
```

The automated suite covers input defaults, mapping, data coercion, pagination, Webflow endpoint payloads, `429` retry behavior, batching, bad-row isolation, publishing fallback, source dataset paging, and a 10,000-record simulated import.

# Changelog

This Actor's version history is a separate document: https://apify.com/coolinbex/webflow-cms-bulk-sync/changelog.md

# Actor input Schema

## `sourceType` (type: `string`):

Use the safe demo, an existing Apify dataset, or inline JSON records.

## `sourceDatasetId` (type: `string`):

Dataset to read when Source is Apify dataset. The Actor reads it in pages and does not load the full dataset into memory.

## `inlineItems` (type: `array`):

Array of source objects used when Source is Inline JSON items.

## `maxItems` (type: `integer`):

Maximum records to process. Set 0 for no Actor-side limit.

## `webflowToken` (type: `string`):

Required for real runs. Use a Webflow token with CMS read access; CMS write access is also needed when dry run is disabled. Stored encrypted by Apify.

## `collectionId` (type: `string`):

The 24-character CMS collection ID to import or sync into.

## `mode` (type: `string`):

Upsert creates missing items and updates matches. Create never updates. Update never creates.

## `matchField` (type: `string`):

Webflow field slug used to identify an existing item. 'slug' is safest for most collections. You may also use 'id' when source rows contain Webflow item IDs.

## `fieldMapping` (type: `object`):

JSON object: Webflow field slug -> source property path. Dot paths are supported, e.g. {"name":"title","company":"company.name"}.

## `autoMapSameNameFields` (type: `boolean`):

Also map source keys whose names exactly match editable Webflow field slugs.

## `autoGenerateSlug` (type: `boolean`):

Generate a lowercase Webflow-friendly slug from the mapped name when slug is missing.

## `coerceFieldTypes` (type: `boolean`):

Convert numbers, booleans, dates, image URLs, multi-images, and option names into Webflow API value formats.

## `skipUnchanged` (type: `boolean`):

Avoid API writes when all mapped values already match the Webflow item.

## `duplicateBehavior` (type: `string`):

What to do if the source contains a duplicate match value or create mode finds an existing match.

## `publish` (type: `boolean`):

Publish successfully created or updated items after staging them. Leave off to keep changes in Webflow draft/staged state.

## `dryRun` (type: `boolean`):

Preview create/update/skip decisions without changing Webflow. Recommended for the first run.

## `includeMappedData` (type: `boolean`):

Include the final mapped Webflow fieldData object in each output row. Disable to keep very large result datasets smaller.

## `batchSize` (type: `integer`):

Number of items per bulk create/update request. Webflow currently allows up to 100.

## `maxRetries` (type: `integer`):

Retries transient network, 429, and 5xx responses with backoff and Webflow Retry-After support.

## `requestDelayMs` (type: `integer`):

Optional extra throttle. Normally leave at 0 because 429 responses are handled automatically.

## `skipInvalidFiles` (type: `boolean`):

Pass Webflow's skipInvalidFiles option so invalid file/image inputs do not necessarily reject an entire API request.

## `debug` (type: `boolean`):

Log Webflow request paths and retry attempts. The API token is never logged.

## Actor input object example

```json
{
  "sourceType": "demo",
  "sourceDatasetId": "",
  "inlineItems": [],
  "maxItems": 1000,
  "collectionId": "",
  "mode": "upsert",
  "matchField": "slug",
  "fieldMapping": {
    "name": "name",
    "slug": "slug"
  },
  "autoMapSameNameFields": true,
  "autoGenerateSlug": true,
  "coerceFieldTypes": true,
  "skipUnchanged": true,
  "duplicateBehavior": "error",
  "publish": false,
  "dryRun": true,
  "includeMappedData": true,
  "batchSize": 100,
  "maxRetries": 5,
  "requestDelayMs": 0,
  "skipInvalidFiles": true,
  "debug": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `collectionSchema` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("coolinbex/webflow-cms-bulk-sync").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("coolinbex/webflow-cms-bulk-sync").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call coolinbex/webflow-cms-bulk-sync --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,coolinbex/webflow-cms-bulk-sync"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ywefMb8jfSJMmgsoc/builds/VJqDvZvWKLlsFqmeY/openapi.json
