WeWeb has no native headless CMS plugins — you connect Strapi, Contentful, or Sanity through the REST API or GraphQL plugin. Once connected, create a collection from your CMS data, bind it to repeating containers, and build dynamic content pages. The full setup takes about 30 minutes and works with any REST-based CMS.
Connecting a Headless CMS to WeWeb
WeWeb is a frontend-only builder — it has no built-in content management system and no native plugins for Strapi, Contentful, or Sanity. Instead, you connect your CMS through the REST API plugin (or GraphQL plugin for GraphQL-native CMSes). This gives you full flexibility: your content editors work in the CMS of their choice while your WeWeb app fetches and renders that content at runtime. This tutorial walks through setting up a connection to a headless CMS, creating collections, binding content to repeating containers, building dynamic detail pages, and handling the quirks of rich text and image URLs.
Prerequisites
- A WeWeb project open in the editor (Free plan or above)
- A headless CMS account with published content — Strapi (self-hosted or Strapi Cloud), Contentful, or Sanity
- Your CMS API endpoint URL and an API token or access key
- Basic familiarity with WeWeb's Data panel and Collections
Step-by-step guide
Install the REST API data source plugin
Install the REST API data source plugin
In your WeWeb editor, open the left sidebar and click the plug icon to open the Plugins panel. Under the 'Data sources' section click 'REST API' and then click 'Add'. The REST API plugin requires no additional configuration at the plugin level — it just enables the REST API collection type. Once added you will see 'REST API' listed under your data sources. If you plan to use a GraphQL-native CMS like Sanity with GROQ, install the 'GraphQL' plugin instead in the same panel.
Expected result: REST API appears under Plugins → Data sources with a green checkmark.
Create a CMS collection in the Data panel
Create a CMS collection in the Data panel
Open the Data panel (database icon in the left sidebar). Click '+ New' to add a collection. Select 'REST API' as the source. A configuration panel opens — this is where you specify the CMS endpoint. For Strapi: set Method to GET, URL to `https://your-strapi-url/api/articles` (replace 'articles' with your content type), add an Authorization header with value `Bearer YOUR_STRAPI_TOKEN`, and set the Result key to `data` (Strapi wraps results in a data array). For Contentful: URL is `https://cdn.contentful.com/spaces/SPACE_ID/environments/master/entries?content_type=blogPost`, add header `Authorization: Bearer YOUR_DELIVERY_TOKEN`, Result key is `items`. For Sanity: URL is `https://PROJECT_ID.api.sanity.io/v2021-10-21/data/query/production?query=*[_type==%22post%22]`, and the Result key is `result`. Name your collection descriptively (e.g. 'cms-articles') and click Save.
1// Strapi REST API collection config (reference, not code)2// Method: GET3// URL: https://your-strapi-url/api/articles?populate=*4// Headers:5// Authorization: Bearer YOUR_STRAPI_API_TOKEN6// Result key: data78// Contentful config9// URL: https://cdn.contentful.com/spaces/SPACE_ID/environments/master/entries?content_type=blogPost10// Headers:11// Authorization: Bearer YOUR_DELIVERY_TOKEN12// Result key: items1314// Sanity GROQ config15// URL: https://PROJECT_ID.api.sanity.io/v2021-10-21/data/query/production16// Query param: query = *[_type == "post"]{_id, title, slug, body, mainImage}17// Result key: resultExpected result: Collection appears in the Data panel and shows a record count when you click 'Fetch'.
Add URL parameters for pagination and filtering
Add URL parameters for pagination and filtering
Most headless CMSes support URL query parameters for pagination and filtering. In your collection config (Data panel → select your collection → Edit), add Query Parameters below the URL. For Strapi add `pagination[page]` and `pagination[pageSize]` to control how many items load. For Contentful add `limit` (max 1000) and `skip`. You can bind these values to WeWeb variables — open the formula editor by clicking the plug icon next to any parameter field, then reference a variable like `currentPage`. This lets you build paginated content lists. Also add `populate=*` for Strapi to include relationships like author and featured image in the response.
Expected result: Collection fetches the correct number of items; changing the variable updates the displayed data.
Bind CMS data to a repeating container
Bind CMS data to a repeating container
Add a Container element to your page canvas (click the + in the left panel → Layout → Container). With the container selected, go to the Settings panel (right sidebar, Settings tab). Find the 'Repeat items' property and click the plug icon next to it. In the formula editor, select your CMS collection: type the collection name (e.g. `cms-articles.data`). The container will now duplicate itself for each item in the array. Add a Text element inside the container. Select it, go to the Settings tab, find the Text property, click the plug icon, and reference `item.attributes.title` for Strapi or `item.fields.title` for Contentful. Add an Image element and bind its Src property to `item.attributes.featuredImage.data.attributes.url` (Strapi) or `'https:' + item.fields.thumbnail.fields.file.url` (Contentful — note the protocol prefix needed for Contentful URLs).
Expected result: A list of CMS content items renders on the page, each showing title and image from the CMS.
Create a dynamic detail page with URL routing
Create a dynamic detail page with URL routing
In the Pages panel (page icon in left sidebar), click '+ New page'. Name it 'Article' and set its path to `/articles/:slug`. The `:slug` part is a URL path parameter. Now create a second collection (Data panel → + New) for a single item. Configure it just like your list collection but add a filter: for Strapi, append `?filters[slug][$eq]={slug}` to the URL and bind `{slug}` to the URL parameter (`wwPage.params.slug` in the formula editor). For Contentful, add query param `fields.slug` bound to `wwPage.params.slug`. Name this collection 'cms-article-detail'. On the Article page, bind all element content to this single-item collection. Set the collection fetch mode to 'On page load' so it fires as soon as the user navigates to the page.
Expected result: Navigating to /articles/my-post-slug fetches and displays the matching CMS content item.
Render rich text content from the CMS
Render rich text content from the CMS
Headless CMS rich text fields return HTML strings (Contentful via contentful-rich-text renderer, Strapi as HTML). In WeWeb, do NOT bind rich text HTML to a standard Text element — it will display raw HTML tags. Instead, add a Rich Text element (click + in the left panel → Display → Rich Text). Select it, go to Settings, and bind the 'Content' property to the rich text field from your collection item. For Contentful, the rich text field is a JSON structure — you need to first convert it. In a Page workflow (trigger: On page load → action: Custom JavaScript), convert the Contentful rich text object to HTML using the `@contentful/rich-text-html-renderer` package (install via the NPM plugin), store the result in a page variable, and bind the Rich Text element to that variable.
Expected result: Blog post body renders as formatted HTML with headings, paragraphs, bold text, and links correctly styled.
Handle CMS image URLs and configure fetch timing
Handle CMS image URLs and configure fetch timing
Images from different CMSes come in different formats. Contentful image URLs start with `//` (no protocol) — prepend `https:` in a formula: `concat('https:', item.fields.image.fields.file.url)`. Sanity images use a CDN URL builder — the asset reference ID must be converted to a full URL using the pattern `https://cdn.sanity.io/images/PROJECT_ID/DATASET/IMAGE_ID-WxH.jpg`. For Strapi, if self-hosted, the image URL is relative (`/uploads/image.jpg`) — prepend your Strapi base URL. Set your list collection's fetch trigger to 'On page load' via the collection's Settings. For the detail collection, set it to 'On page load' as well but ensure the URL parameter is available first (it always will be on a page load). Verify everything works by navigating to a list page, clicking an item, and confirming the detail page loads the correct CMS entry.
Expected result: All CMS images display correctly on both list and detail pages across Strapi, Contentful, and Sanity.
Complete working example
1// Injection point: Workflow Custom JavaScript action on page load2// Purpose: Convert Contentful rich text JSON to HTML and store in page variable3// Requires: NPM plugin with @contentful/rich-text-html-renderer installed45// Step 1: Get the raw rich text object from the collection6// (Replace 'cms_article_detail' with your actual collection variable name)7const richTextDoc = variables['cms_article_detail']?.data?.[0]?.fields?.body;89if (!richTextDoc) {10 return { html: '' };11}1213// Step 2: Import the renderer (available via NPM plugin)14const { documentToHtmlString } = require('@contentful/rich-text-html-renderer');1516// Step 3: Convert with custom rendering options17const options = {18 renderNode: {19 'embedded-asset-block': (node) => {20 const { file, title } = node.data.target.fields;21 const url = 'https:' + file.url;22 return `<img src="${url}" alt="${title}" style="max-width:100%;height:auto;" />`;23 },24 'hyperlink': (node, next) => {25 return `<a href="${node.data.uri}" target="_blank" rel="noopener">${next(node.content)}</a>`;26 }27 }28};2930const htmlString = documentToHtmlString(richTextDoc, options);3132// Step 4: Return the HTML string33// In the next workflow step, use 'Change variable value' to store this34// in a page variable named 'articleBodyHtml'35return { html: htmlString };Common mistakes
Why it's a problem: Setting the wrong Result key and getting an empty collection
How to avoid: Each CMS wraps its response differently. Strapi uses 'data', Contentful uses 'items', Sanity uses 'result'. Open your collection in the Data panel, click Fetch, and inspect the raw JSON response to find the correct key path.
Why it's a problem: Binding rich text HTML to a standard Text element, causing raw HTML tags to show
How to avoid: Use the Rich Text element (Display → Rich Text) for CMS body content, not a standard Text element. The Rich Text element renders HTML safely.
Why it's a problem: Contentful image URLs missing the https:// protocol prefix
How to avoid: Contentful returns image URLs as protocol-relative strings starting with '//'. In your image Src formula, prepend 'https:' using concat('https:', item.fields.image.fields.file.url).
Why it's a problem: Dynamic detail page collection not filtering by the URL parameter
How to avoid: In the detail collection config, add a filter that references wwPage.params.slug (or whatever your URL parameter is named). Without this filter, the collection returns all items instead of the one matching the current URL.
Best practices
- Use Cached collection mode for CMS data that changes infrequently — this reduces API calls and avoids CMS rate limits
- Always populate relationships in Strapi with ?populate=* or specific populate parameters to avoid making separate API calls for related data
- Store your CMS API tokens as environment-specific values — use different tokens for editor/staging vs production environments in the collection config
- Security warning: REST API collections are client-side. Your CMS API token is visible in browser dev tools. Use read-only delivery tokens (Contentful's CDN key, Strapi's public token) for public content, never admin tokens
- Test your collection in the editor by clicking Fetch and inspecting the raw data structure before binding to elements
- For dynamic detail pages, set a loading state variable to true while the collection fetches, and show a skeleton loader bound to that variable
- Build a sitemap for SEO: WeWeb's auto-generated sitemap excludes dynamic pages. Export your CMS slugs and upload a custom sitemap via Page settings → SEO & Meta Tags
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm connecting a headless CMS to WeWeb using the REST API plugin. My CMS is [Strapi/Contentful/Sanity] and it returns data in this structure: [paste JSON sample]. Write me the Result key path and a formula to bind the title, body, and featured image URL to WeWeb element properties.
In WeWeb, I have a REST API collection connected to Contentful. The collection fetches successfully and I can see data in the formula editor. Help me create a dynamic page at /articles/:slug that fetches only the article matching the URL parameter, and show me how to bind the title, body, and featured image to page elements.
Frequently asked questions
Does WeWeb have a native Strapi, Contentful, or Sanity plugin?
No. WeWeb has no native plugins for any headless CMS. You connect them using the REST API plugin (for REST-based CMSes) or the GraphQL plugin (for GraphQL-native setups). The connection works well but requires manual configuration of the endpoint URL, authentication headers, and result key for each CMS.
Will my CMS API key be visible to users in the browser?
Yes. REST API collections in WeWeb are client-side — the API token is included in the network request headers and visible to anyone who opens browser dev tools. Always use read-only delivery tokens for public content, never admin or management tokens. Contentful's CDN delivery token is read-only by design. For Strapi, create a dedicated token with read-only permissions.
Can I use WeWeb with a self-hosted Strapi instance?
Yes. In the REST API collection config, use your self-hosted Strapi URL (e.g., https://cms.yourcompany.com/api/articles). Make sure your Strapi instance has CORS configured to allow requests from your WeWeb app's domain. In WeWeb, you can also enable the 'Proxy the request to bypass CORS issues' checkbox in the REST API Request action.
How do I update my WeWeb page when CMS content changes?
WeWeb fetches CMS data at runtime, not at build time (unless you use Static collection mode). This means published CMS changes appear the next time a user loads the page without requiring a WeWeb republish. If you use the After Deploy Hook in WeWeb Project Settings, you can trigger CMS cache purges or static site regenerations after each WeWeb deployment.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation