Search

WCH Search App

A Cloudflare Worker that syncs content from your Webflow site into a searchable database and exposes it via REST API endpoints.

How it works

The WCH Search App runs on Cloudflare Workers and keeps your Webflow content in sync with a D1 SQLite database. When you publish changes in Webflow, a webhook triggers a full sync that pulls all CMS collections and pages into the database. Third-party applications can then query the synced content via simple REST endpoints.

Core flow:

  1. Webflow publishes content (collections or pages)
  2. Webhook triggers a sync to the D1 database
  3. Your app queries the search API or content endpoints
  4. Results are returned as JSON with full field data

Setup

Requirements

  • Cloudflare Workers account with D1 database enabled
  • Webflow API key and site ID
  • wrangler.jsonc configured with environment variables

Environment variables

Configure these in your wrangler.jsonc under vars:

Variable                               Purpose

****WEBFLOW_API_KEY      Bearer token for Webflow API (v2)

WEBFLOW_SITE_ID       Your Webflow site

IDWEBHOOK_SECRET  Secret for webhook verification

Initialize and deploy

npm installnpm run deploy    # Deploys worker and D1 database

Once deployed, configure your Webflow site to send publish webhooks to POST /webhook on your worker URL. The sync will run automatically on each publish.

Using the API

All endpoints return JSON with CORS headers enabled (Access-Control-Allow-Origin: *).

Search

GET /search?q=

Keyword search across all enabled collections.

  • Query is matched against item names, slugs, and full data (for queries 4+ characters)
  • Results are ranked by relevance (exact matches first, then partial matches)
  • Limited to 50 results per request
  • Includes a matchedContext field showing where the match occurred
  • Only returns items from collections marked as enabled

Example: GET /search?q=austin

Collections

GET /collections

Returns all synced collections with their enabled/disabled status. Use this to show users which content is searchable.

POST /collections

Toggle which collections appear in search results.

{  "collections": [    { "id": "abc123", "enabled": true },    { "id": "def456", "enabled": false }  ]}

Content endpoints

GET /locations - All location items with resolved state names.

GET /blogs - Blog posts, blog topics, and blog categories in a single response.

GET /topics | GET /categories - Blog topics or categories individually.

Admin panel

GET /admin

A self-contained HTML page for toggling collection visibility. Accessible at your worker URL /admin.

This page fetches from GET /collections and updates via POST /collections, so no additional setup is needed.

Database schema

The D1 database stores all synced content in three tables:

webflow_items — All CMS items and pages

ColumnPurposeidWebflow item/page ID (primary key)collection_idCollection ID, or webflow_pages for pagescollection_slugCollection slug or page pathnameItem name or page titleslugURL slugdataFull Webflow API response (JSON)created_atTimestamp when first stored (epoch ms)updated_atTimestamp of last update (epoch ms)

collection_settings — Visibility controls

ColumnPurposecollection_idPrimary keycollection_nameDisplay namecollection_slugURL slugenabled1 or 0 — controls search inclusion

sync_history — Audit log

ColumnPurposesynced_atWhen sync started (ISO)completed_atWhen sync finished (ISO)duration_msTotal durationtrigger_type"manual" or Webflow trigger typetriggered_by_id/email/nameWho triggered the syncitems_added/updated/deletedItem countscollections_countNumber of sources syncedcollections_dataPer-collection breakdown (JSON)

Syncing content

Manual sync

Call POST /webhook without a body to manually sync all Webflow content:

curl -X POST https://your-worker.workers.dev/webhook

Automatic sync (webhooks)

Configure Webflow to send webhooks to POST /webhook on publish. The sync process:

  1. Fetches all CMS collections from Webflow (paginated, 100 items per request)
  2. Filters out drafts and archived items
  3. Compares against the database: updates existing items, deletes removed ones
  4. Fetches all site pages the same way
  5. Pages are stored with collection_slug set to their directory path
  6. Updates collection_settings for each collection (new collections default to enabled)
  7. Logs the sync to sync_history with timing and item counts

Response example:

{  "success": true,  "summary": {    "added": 12,    "updated": 8,    "deleted": 2  },  "collections": [    {      "slug": "blog",      "added": 5,      "updated": 3,      "deleted": 0    }  ]}

Webflow API calls

The worker uses Webflow's v2 REST API to pull content:

  • GET /v2/sites/{siteId}/collections — List all collections
  • GET /v2/collections/{id}/items/live — Fetch published items (paginated)
  • GET /v2/sites/{siteId}/pages — Fetch all site pages (paginated)

All requests use bearer token authentication with your WEBFLOW_API_KEY.

Important considerations

  • Sync timing: Full syncs can take several seconds depending on your content volume. Plan webhook triggers accordingly.
  • Search performance: Large datasets (10,000+ items) may slow search queries. Consider filtering by collection or implementing pagination in your client.
  • Enabled collections: Only items from collections marked as enabled appear in search results. New collections default to enabled—adjust in the admin panel if needed.
  • Draft content: Drafts and archived items are always filtered out, even during manual syncs.
  • Static hosting: Location items with fieldData["link-to-post"] set to false are excluded from search (they would 404 on your site).

Client integration examples

JavaScript: Search

const query = "austin";const response = await fetch(https://your-worker.workers.dev/search?q=${encodeURIComponent(query)});const results = await response.json();

JavaScript: Locations

const response = await fetch("https://your-worker.workers.dev/locations");const { locations } = await response.json();

JavaScript: Blog with filtering

const response = await fetch("https://your-worker.workers.dev/blogs");const { blogs, topics, categories } = await response.json();// Filter by topicconst filtered = blogs.filter(post => post["post-topic"] === topicId);

Troubleshooting

  • Webhook not triggering: Verify the webhook URL in Webflow settings and that your worker is deployed and accessible.
  • Missing content: Check the admin panel (/admin) to ensure the collection is marked as enabled.
  • Search returning no results: Confirm the query matches item names or slugs. Queries under 4 characters only search names and slugs, not full data.
  • Sync errors: Check sync_history table for detailed timing and error counts. Manual syncs via POST /webhook will log errors to your Cloudflare dashboard.

Next steps

  • Deploy the worker: npm run deploy
  • Configure Webflow webhooks to your worker URL
  • Integrate search into your app using the endpoints above
  • Use /admin to manage which collections are searchable