Working with the CMS

What We'll Build

In this guide, you'll create a Data Client App that interacts with Webflow's CMS APIs. The frontend, built with React, will display data fetched from the Webflow CMS. The backend, powered by Express, will handle authentication and API calls.


  • A Webflow site. If you’re not sure where to start, clone our Astral Fund site with defined CMS collections.
  • A Webflow App or a Site Token with the following scopes: sites:read, cms:read, and cms:write
  • An Ngrok account and an authentication token
  • Node.js and an IDE of your choice.
  • Additionally, you should have basic knowledge of Node.js and Express.
  1. Clone the starter code.
    Run the following commands in your IDE to clone the example repository and install dependencies:
    # Clone the example repository
    git clone
    cd webflow-example
    # Install dependencies
    npm install
  2. Add Environment Variables
    Add your credentials to the .env file. If you’re using an App, input your App’s CLIENT_ID and CLIENT_SECRET. If using a Site Token, input the token as SITE_TOKEN.
  3. Add Ngrok Auth Token
    Ngrok is required because Webflow Apps must run on https:// URLs, and Ngrok provides a secure tunnel for your local server.

    Get your Ngrok auth token from the Ngrok dashboard. Then, add your token to your environment variables.
    export NGROK_AUTH_TOKEN=your-ngrok-auth-token
  4. Review Project
    In this example, our focus will be on the backend folder. Within this folder, you'll find:
    • server.js: The main server file that sets up and runs our Express server.
    • webflowClientMiddleware.js: Contains the middleware for initializing the Webflow client.
    • routes: Includes various routes for interacting with the Webflow CMS.
    • controllers: Defines the logic for API calls.


Quickstart 🚀

Want to see the demo in action? Run the following command to start your server and follow the instructions in the terminal.

npm start

👇 Keep going to dive deeper into the specifics of how to interact with the Webflow CMS API, set up middleware, and define routes and controllers.

To authenticate all of our API calls to Webflow, we'll need to initialize and authenticate the WebflowClient provided by the Webflow JavaScript SDK. To efficiently manage this for multiple requests, we’ll create middleware for our Express server, which will handle initialization of the WebflowClient and authentication in each request.



We’ve already taken care of the OAuth Handshake in authRoutes.js. To learn more about authorization and authentication for your app, read the guide.

  1. Define Imports
    First, we'll import the necessary modules. The WebflowClient will be imported from the Webflow SDK, and the getToken function will be imported from our tokens.js utility. The getToken function handles retrieving an authentication token from our database.

    Add the following import statements at the top of your webflowClientMiddleware.js file:
  2. import WebflowClient from 'webflow-api'; import getToken from './tokens.js';
  3. Build Middleware Function
    Next, we'll create the middleware function. This function will be an asynchronous function that retrieves the access token, initializes the Webflow client, and attaches it to the request object. It will also handle errors and send appropriate responses if something goes wrong.

    1. Retrieve Access Token
      Within the middleware function, the first step is to retrieve the access token for the user. This is done using the getToken function. If the token is not found, an error is logged, and a 401 Unauthorized response is sent.
    2. Initialize Webflow Client
      Once we have the access token, we'll initialize the Webflow client using the WebflowClient constructor and attach it to the req object. This ensures that the client is available for subsequent middleware and route handlers.
    3. Export the function
      Finally, we'll export the middleware function so it can be used in other parts of the application.
import WebflowClient from 'webflow-api'; import getToken from './tokens.js'; / Middleware function to initialize and authenticate WebflowClient const webflowClientMiddleware = async (req, res), next => { try { // Retrieve Access Token const accessToken = await getToken('user'); if (!accessToken) { console.log('Access token not found for user'); return res.status(401).send('Access token not found'); } // Initialize Webflow Client req.webflow = new WebflowClient({ accessToken }); // console.log('Webflow client initialized'); next(); } catch (error) { console.error('Error initializing Webflow client:', error); res.status(500).send('Failed to initialize Webflow client'); } }; export default webflowClientMiddleware;

Let’s review how controllers and routes are structured in the project. Routes define the endpoints for your application, while controllers contain the logic for handling requests and responses.


Controllers contain the business logic for handling requests and responses. Each controller method is designed to handle a specific type of request, such as listing sites, creating a new collection, or deleting an item.

Example Controller: controllers/sitesController.js

Here’s the listSites controller method which is used to fetch and return the list of sites:

export const listSites = async (req, res) => { // Fetch sites const sites = await req.webflow.sites.list(); // Respond with the list of sites res.json({ sites }); } catch (error) { console.error('Error fetching sites:', error); res.status(500).send('Failed to fetch sites'); } };


Routes are responsible for defining the API endpoints and linking them to the appropriate controller methods. Each route file is dedicated to a specific resource or group of related functionalities. In this example, there are routes for handling authentication, sites, collections, and items data.

Example Route: routes/sitesRoutes.js

Here’s a quick look at how routes are set up in the project:

import express from 'express'; import { listSites } from '../controllers/sitesController.js'; // Import the listSites controller const router = express.Router(); router.get('/', listSites); // Define the route for listing sites and link it to the listSites controller export default router;

In this section, we'll set up routes and controllers for managing Collections in the Webflow CMS. This includes listing Collections, getting Collection details, creating Collections with fields, and deleting Collections.

Collections and Fields

When creating a collection, you’ll need to define required details such as displayName, singularName, and slug. Additionally, all collections have the following default, required fields: name and slug.

Collection Object

{ "id": "6487b589d27f8112c8623bcb", "displayName": "test2", "singularName": "testing123", "slug": "test2", "createdOn": "2023-06-13T00:17:13.320Z", "lastUpdated": "2023-06-13T00:17:13.748Z", "fields": [ { "id": "b24f574b7ab550df74090de0f850c81d", "isEditable": true, "isRequired": true, "type": "PlainText", "slug": "name", "displayName": "Name", "helpText": null }, { "id": "08abacfa7f860006c7785fb6b725b9f5", "isEditable": true, "isRequired": true, "type": "PlainText", "slug": "slug", "displayName": "Slug", "helpText": null } ] }

In addition to a Field’s required type, displayName, and slug properties, you can also add helpText and determine whether a field isRequired for an item.

Field Object

{ "id": "b24f574b7ab550df74090de0f850c81d", "isEditable": true, "isRequired": true, "type": "PlainText", "slug": "name", "displayName": "Name", "helpText": null }

Creating Custom Fields

Once a collection is created, you can create additional fields at any time. Learn more about field types and accepted item values in the Webflow Field Types Documentation.


Unsupported Field Types

Note: At this time, Webflow does not support the creation of the following field types through the API:

These field types can be added to a collection at any time via the Webflow designer.

  1. Setup Collection Controllers

    Let's define the logic for handling the collection routes in the collectionController.js file. In this example, we’re setting up our createCollection logic to accept a preset collection with accompanying fields defined in ./utils/CollectionPresets.js. We’ll provide an example request later on in the guide.

    Create the file in your controllers folder and add the following code. We’ll export each function so they can be used in our routes.

  2. export const listCollections = async (req, res) => { // Fetch collections const collections = await req.webflow.collections.list(req.params.siteId); // Respond with the list of collections res.json({ collections }); } catch (error) { console.error('Error fetching collections:', error); res.status(500).send('Failed to fetch collections'); } }; export const getCollection = async (req, res) => { try { // Fetch collection details const collection = await req.webflow.collections.get(req.params.collectionId); // Respond with the collection details res.json(collection); } catch (error) { console.error('Error fetching collection:', error); res.status(500).send('Failed to fetch collection'); } };
  3. Setup Collection Routes

    Now that we have the controllers, let's define the routes that will use these controllers. Create a file named collectionRoutes.js in your routes folder and add the following code:

  4. import express from 'express'; import { listCollections, getCollection, createCollectionWithFields, deleteCollection } from '../controllers/collectionController.js'; // Import the controllers we just setup const router = express.Router(); // Route to list all collections for a specific site. See listCollections controller for logic. router.get("/:siteId", listCollections); // Route to get details of a specific collection. See getCollection controller for logic. router.get("/:collectionId/details", getCollection); // Route to create a new collection with fields. See createCollectionsWithFields controller for logic."/:siteId", createCollectionWithFields); // Route to delete a specific collection. See deleteCollection controller for logic. router.delete("/:collectionId", deleteCollection); export default router;

In this section, we'll set up endpoints for managing items within a collection in the Webflow CMS. This includes fetching items, adding and updating items, and managing an item’s publish state.

Understanding Items

Items are individual entries within a collection. This section provides detailed information on how to manage these items, including creating, updating, retrieving, and deleting items. You'll also learn about the different states items can be in and how to work with various field types.

Required Properties

  • Archive: Pulls collection items from the site but keeps them accessible from the CMS. Default value is false.
  • Draft: Saves the collection item as a draft. The item will not go live until you change its status, even if the entire site is published. Default value is false.

Live vs. Staging

Items can be added either as a staged item or directly to the live site. The endpoints for these actions differ, and understanding when to use each is crucial for effective content management. For more detailed information on site publishing, visit Webflow University.

  • Staging Endpoints: Use staging endpoints to manage items within a collection without immediately publishing them. Items added or modified through staging endpoints will be published to the live site the next time the entire site is published. Staging read endpoints return both staged and live items, while write actions are saved as staged changes.
  • Live Endpoint: Use endpoints ending in /live to manage items directly on the live site.

Query Parameters

When retrieving items from a collection, you can use various query parameters to filter, sort, and paginate the results. Here are the key query parameters you can use:


  • offset: Use to paginate through a longer list of results.
  • limit: Use to limit the number of items returned.

cmsLocaleId: Retrieve localized collection items by providing a comma-separated string of multiple locales. Refer to the localization guide for more information.


  • name: Filter by the exact name of the item.
  • slug: Filter by the exact slug of the item.
  • lastPublished: Use lte (less than or equal) and gte (greater than or equal) to filter by publication date.


  • sortBy
    • name
    • slug
    • lastPublished
  • sortOrder:
    • asc: Ascending order
    • desc: Descending order
  1. Setup Item Controllers

    First, let's define the logic for handling item operations in the itemsController.js file. Create the file in your controllers folder and add the following code. We’ll export each function so they can be used in our routes.

  2. // List collection items export const listItems = async (req, res) => { try { const data = await req.webflow.collections.items.listItems(req.params.collectionId); res.json(data.items); // Respond with collection items } catch (error) { console.error("Error fetching collection items:", error); res.status(500).send("Failed to fetch collection items"); } }; // Create collection item export const createItem = async (req, res) => { try { const data = await req.webflow.collections.items.createItem(req.params.collectionId, req.body); res.json(data); // Respond with created item } catch (error) { console.error("Error creating collection item:", error); res.status(500).send("Failed to create collection item"); } }; // Delete collection item export const deleteItem = async (req, res) => { try { const data = await req.webflow.collections.items.deleteItem(req.params.collectionId, req.params.itemId); res.json(data); // Respond with deletion result } catch (error) { console.error("Error deleting item:", error); res.status(500).send("Failed to delete item"); } };
  3. Setup Item Routes

    Next, define the routes that will use these controllers. Create a file named itemsRoutes.js in your routes folder and add the following code:

  4. import express from 'express'; import { listItems, createItem, deleteItem } from '../controllers/itemsController.js'; // Import the controllers we just setup const router = express.Router(); // Route to list all items for a specific collection. See listItems controller for logic. router.get("/:collectionId/items", listItems); // Route to create a new item in a specific collection. See createItem controller for logic."/:collectionId/items", createItem); // Route to delete a specific item from a collection. See deleteItem controller for logic. router.delete("/:collectionId/items/:itemId", deleteItem); export default router;

Let's set up the server to handle requests, configure middleware, define routes for interacting with the Webflow API, and tunnel through Ngrok for secure external access.

  1. Import Required Modules

    Start by importing the necessary modules and middleware:

    import express from "express"; import cors from "cors"; import chalk from "chalk"; // Library for styling terminal output import Table from "cli-table3"; // Library for styling terminal output import { startNgrok } from "./utils/ngrokManager.js"; // Logic for handling Ngrok // Import Webflow Client Middleware import webflowClientMiddleware from "./webflowClientMiddleware.js"; // Import Routes import authRoutes from "./routes/authRoutes.js"; import sitesRoutes from "./routes/sitesRoutes.js"; import collectionsRoutes from "./routes/collectionRoutes.js"; import itemRoutes from "./routes/itemRoutes.js";
  2. Initialize Express Application

    Create an instance of the Express application and define the port:

    const app = express(); const PORT = process.env.PORT || 8000;
  3. Configure Middleware

    Setup middleware for CORS, which will allow our backend to accept requests from our frontend running on a different port. Additionally, add middleware for handling JSON data sent in a request body, making it available as req.body in your request handlers.

    app.use( cors({ origin: "http://localhost:3000", // Allow only this origin to access the resources optionsSuccessStatus: 200, // For legacy browser support }) ); app.use(express.json());
  4. Define Routes

    Configure the routes for authentication and accessing sites, collections, and items. Use the Webflow Client middleware for routes that need API interaction:

    app.use("/", authRoutes); app.use("/api/sites", webflowClientMiddleware, sitesRoutes); app.use("/api/collections", webflowClientMiddleware, collectionsRoutes); app.use("/api/collections", webflowClientMiddleware, itemRoutes);
  5. Start the Server with Ngrok

    Create a function to start the server and tunnel through Ngrok for external access:

    // Start server with NGROK const startServer = async () => { try { // Start Ngrok const ngrokUrl = await startNgrok(PORT); // Create a table to output in the CLI const table = Table({ head: ["Location", "URL"], // Define column headers colWidths: [30, 60], // Define column widths }); // Add URL information to the table table.push( ["Development URL (Frontend)", "http://localhost:3000"], ["Development URL (Backend)", `http://localhost:${PORT}`] ); // If using an App, also add the Redirect URI to the table if (!process.env.SITE_TOKEN) { table.push(["Auth Callback URL", `${ngrokUrl}/auth/callback`]); } // Console log the table console.log(table.toString()); // If using an App, send a note to adjust the app's Redirect URI if (!process.env.SITE_TOKEN) { console.log("\n\nNOTE:"),"Update your Redirect URI in your App Settings\n\n") ); } // Start the server app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); }); } catch (error) { console.error("Failed to start the server with ngrok:", error); process.exit(1); } }; // Start the server startServer();

Now that we’ve set up the server, let’s start making requests through our frontend.

  1. Start the server
    First, ensure your server is running. Use the following command to start both the frontend and backend servers, with the backend being tunneled through Ngrok:
    npm start
  2. Authenticate the App
    1. Get the Ngrok redirect URI from the terminal.
    2. Update your app’s redirect URI in the Webflow app settings with this Ngrok redirect URI.
    3. Navigate to http://localhost:8000.
    4. You’ll be taken to a Webflow authentication screen. Add the workspaces/sites you want to authorize.
    5. After authorizing, you’ll be redirected to the frontend.
    If you’re using a Site Token, you’ll be redirected directly to the frontend.

  3. Explore the CMS
    1. Choose a Site
      From the dropdown list, select one of the authorized sites you want to explore.
    2. Choose a Collection
      • Select a collection from the dropdown list to populate the item list.
      • (Optional) You can delete a collection if you wish.
    3. Create an Item
      • Fill out the form and submit it to create a new item.
      • The new item will appear in the item list.
    4. Delete an Item
      • Scroll through the item list to find the delete button.
      • Delete an item and observe the changes.
    5. Create a Collection
      1. In the modal, choose from a list of collection presets and create a collection.
      2. Return to the collection picker to see the new collection appear.
      3. (Optional) Add new items to the newly created collection.


Congratulations! You've successfully navigated through the process of setting up and using the Webflow API with a fully functional backend and frontend application. Here's a quick recap of what you've accomplished:

  1. Webflow Client and Backend Configuration: You configured the WebflowClient, set up middleware, and created routes and controllers for managing collections and items.
  2. Working with Collections: You learned how to create, retrieve, and delete collections, including handling different field types.
  3. Working with Items: You explored how to create, retrieve, update, and delete items within a collection, managing various item states and field types.

Next Steps

  • Extend Functionality: Enhance your application by adding new endpoints - try updating an item - or incorporating additional data processing logic.
  • Explore Localization: Managing content localization is a crucial part of working with the Webflow CMS. Check out our localization guide for more details on how to localize your content effectively.