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:
- Webflow publishes content (collections or pages)
- Webhook triggers a sync to the D1 database
- Your app queries the search API or content endpoints
- 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=<query>
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:
- Fetches all CMS collections from Webflow (paginated, 100 items per request)
- Filters out drafts and archived items
- Compares against the database: updates existing items, deletes removed ones
- Fetches all site pages the same way
- Pages are stored with collection_slug set to their directory path
- Updates collection_settings for each collection (new collections default to enabled)
- 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