Integrate a headless CMS like Contentful, Strapi, or Sanity with FlutterFlow via REST API calls. Set up an API Group with your CMS base URL and authentication. Fetch content entries, map JSON responses to FlutterFlow UI elements, and render rich text using a flutter_html Custom Widget. Cache CMS content in Firestore via a webhook-triggered Cloud Function so your app reads from local cache for speed while the CMS remains the source of truth for content editors.
Connecting a Headless CMS to Your FlutterFlow App
Headless CMS platforms like Contentful, Strapi, and Sanity let non-technical editors manage content through a dashboard while your FlutterFlow app consumes it via API. This tutorial covers setting up API connections, mapping JSON responses to widgets, rendering rich text HTML, and caching content locally in Firestore for performance. Content editors work in the CMS dashboard while the app updates automatically.
Prerequisites
- A FlutterFlow project with Firestore configured
- An account on a headless CMS (Contentful, Strapi, or Sanity)
- A CMS API key or access token for read access
- Basic understanding of REST APIs and JSON response structure
Step-by-step guide
Set up a FlutterFlow API Group for your CMS
Set up a FlutterFlow API Group for your CMS
In FlutterFlow, go to API Calls and create a new API Group. Set the base URL to your CMS API endpoint: for Contentful it is https://cdn.contentful.com/spaces/{spaceId}/environments/master, for Strapi it is https://your-strapi-url.com/api, for Sanity it is https://{projectId}.api.sanity.io/v2021-10-21/data/query/production. Add a header for authentication: Contentful uses 'Authorization: Bearer {accessToken}', Strapi uses 'Authorization: Bearer {apiToken}', Sanity uses query parameters or bearer token. Create three API calls within the group: getEntries (list all entries of a content type), getEntryById (single entry by ID), and searchEntries (filtered query).
Expected result: A FlutterFlow API Group configured with your CMS base URL, authentication, and three API call definitions.
Create API calls to fetch content entries and single entries
Create API calls to fetch content entries and single entries
For the getEntries call, set the path to the content type listing endpoint. Contentful: /entries?content_type={contentType}&limit=20&order=-sys.createdAt. Strapi: /{contentType}?sort=createdAt:desc&pagination[limit]=20. Sanity: ?query=*[_type=="{contentType}"][0..19]|order(_createdAt desc). Add contentType as a variable parameter. Test the API call in FlutterFlow's API tester and verify the response. For getEntryById, set the path to fetch a single entry: Contentful /entries/{entryId}, Strapi /{contentType}/{id}, Sanity ?query=*[_id=="{id}"][0]. Map the response fields in FlutterFlow's JSON path viewer to extract title, body, imageUrl, author, publishedDate.
Expected result: API calls fetch content lists and individual entries from the CMS with JSON paths mapped to extract title, body, and image fields.
Display CMS content in a list and detail page
Display CMS content in a list and detail page
Create a ContentListPage with a ListView. Add a Backend Query using the getEntries API call. Map each list item to a ContentCard Container showing: title Text (from JSON path to title field), excerpt Text (first 100 characters of body), featured image using the Image widget (from JSON path to image URL), and published date Text. Tapping a card navigates to a ContentDetailPage passing the entry ID as a route parameter. On the detail page, call getEntryById and display: title as H1 Text, author name, published date, featured image (full width), and body content. For plain text bodies, use a Text widget. For rich text bodies, use the flutter_html Custom Widget in the next step.
Expected result: A content list page shows CMS entries as cards and tapping opens a detail page with the full content.
Render rich text content with a flutter_html Custom Widget
Render rich text content with a flutter_html Custom Widget
Most headless CMS platforms return body content as HTML or rich text. Create a Custom Widget named RichContentRenderer that uses the flutter_html package to render HTML content. Pass the HTML string from the CMS response as a parameter. Configure custom tag styles for headings, paragraphs, lists, blockquotes, and code blocks to match your app's design theme. Handle images within the HTML by setting the customImageRenders to use CachedNetworkImage. Handle links by launching URLs via url_launcher when tapped. This widget converts CMS rich text into properly styled native Flutter elements.
1// Custom Widget: RichContentRenderer2import 'package:flutter/material.dart';3import 'package:flutter_html/flutter_html.dart';4import 'package:url_launcher/url_launcher.dart';56class RichContentRenderer extends StatelessWidget {7 final String htmlContent;8 final double width;9 final double height;10 const RichContentRenderer({11 super.key,12 required this.htmlContent,13 required this.width,14 required this.height,15 });1617 @override18 Widget build(BuildContext context) {19 return SizedBox(20 width: width,21 child: Html(22 data: htmlContent,23 style: {24 'h1': Style(25 fontSize: FontSize(28),26 fontWeight: FontWeight.bold,27 margin: Margins.only(28 bottom: 16, top: 24)),29 'h2': Style(30 fontSize: FontSize(22),31 fontWeight: FontWeight.bold,32 margin: Margins.only(33 bottom: 12, top: 20)),34 'p': Style(35 fontSize: FontSize(16),36 lineHeight: LineHeight(1.6),37 margin: Margins.only(bottom: 12)),38 'blockquote': Style(39 backgroundColor: const Color(0xFFF5F5F5),40 padding: HtmlPaddings.all(16),41 margin: Margins.symmetric(42 vertical: 12)),43 'code': Style(44 backgroundColor: const Color(0xFFF0F0F0),45 fontFamily: 'monospace',46 padding: HtmlPaddings.symmetric(47 horizontal: 4, vertical: 2)),48 },49 onLinkTap: (url, _, __) {50 if (url != null) launchUrl(Uri.parse(url));51 },52 ),53 );54 }55}Expected result: CMS rich text content renders as properly styled native elements with formatted headings, paragraphs, images, links, and code blocks.
Cache CMS content in Firestore via webhook for fast reads
Cache CMS content in Firestore via webhook for fast reads
Calling the CMS API on every page load is slow and may hit rate limits. Set up a caching layer: create a cms_content Firestore collection with fields matching your CMS content fields (title, body, imageUrl, author, publishedDate, contentType, cmsEntryId, lastSynced). Build a Cloud Function triggered by HTTP (webhook). Configure your CMS webhook to call this function URL whenever content is published or updated. The function receives the updated content, transforms it to match your Firestore schema, and writes or updates the document in cms_content. Update your FlutterFlow pages to read from Firestore cms_content instead of the CMS API. Keep the direct API calls as a fallback for cache misses.
Expected result: CMS content is cached in Firestore via webhook. The app reads from fast local Firestore while the CMS remains the editing source of truth.
Complete working example
1API GROUP: CMS2 Base URL: https://cdn.contentful.com/spaces/{spaceId}/environments/master3 Headers: Authorization: Bearer {accessToken}45 API Call: getEntries6 GET /entries?content_type={contentType}&limit=20&order=-sys.createdAt7 Response JSON paths:8 items[].fields.title → title9 items[].fields.body → body (HTML)10 items[].fields.featuredImage.fields.file.url → imageUrl11 items[].sys.id → entryId12 items[].sys.createdAt → publishedDate1314 API Call: getEntryById15 GET /entries/{entryId}16 Response JSON paths: fields.title, fields.body, etc.1718FIRESTORE CACHE:19 cms_content/{docId}20 cmsEntryId: String21 contentType: String22 title: String23 body: String (HTML)24 imageUrl: String25 author: String26 publishedDate: Timestamp27 lastSynced: Timestamp2829CLOUD FUNCTION: cmsWebhook (HTTP triggered)30 Receives: CMS webhook payload on publish/update31 Action: Upsert cms_content doc by cmsEntryId32 CMS Dashboard → Settings → Webhooks → URL: function URL3334PAGE: ContentListPage35 ListView36 Backend Query: cms_content (Firestore, ordered by publishedDate desc)37 ContentCard:38 Row: Image (imageUrl) + Column (title, excerpt, date)39 Tap → ContentDetailPage(entryId)4041PAGE: ContentDetailPage (Route: entryId)42 Column43 ├── Image (full width featured image)44 ├── Text (title, H1 style)45 ├── Text (author + date)46 └── Custom Widget: RichContentRenderer(body HTML)4748CACHE STRATEGY:49 1. App reads from Firestore cms_content (fast)50 2. CMS publish triggers webhook → Cloud Function → Firestore update51 3. Fallback: if cache miss, call CMS API directlyCommon mistakes when integrating a Custom CMS (Content Management System) with FlutterFlow
Why it's a problem: Calling the CMS API directly on every page load without caching
How to avoid: Cache CMS content in Firestore via a webhook. The app reads from Firestore for speed. The webhook keeps the cache fresh when editors publish changes.
Why it's a problem: Rendering rich text HTML as plain text
How to avoid: Use the flutter_html package in a Custom Widget to properly render HTML as styled native Flutter elements with headings, paragraphs, links, and images.
Why it's a problem: Hardcoding CMS API keys in FlutterFlow frontend code
How to avoid: For read-only CMS access (content delivery API), public keys are acceptable. For management APIs, route all calls through Cloud Functions with keys stored in environment variables.
Best practices
- Cache CMS content in Firestore via webhook rather than calling the API on every page load
- Use flutter_html Custom Widget to properly render rich text content from the CMS
- Set up JSON path mappings carefully using FlutterFlow's API response viewer
- Keep CMS API keys for read-only delivery endpoints only in client-side code
- Route management API calls through Cloud Functions for security
- Use the CMS as the single source of truth for content while Firestore acts as a read cache
- Configure webhooks to sync content automatically when editors publish changes
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to integrate a headless CMS (Contentful, Strapi, or Sanity) with my FlutterFlow app. Show me how to set up FlutterFlow API Groups for the CMS, map JSON response fields to UI widgets, render rich text HTML with flutter_html, and cache content in Firestore via webhooks for performance. Include the data model and page layouts.
Create a content list page with a list view of card containers. Each card has an image on the left and a column with title text, short description text, and a date text on the right. Add a floating action button for refreshing content.
Frequently asked questions
Which headless CMS should I choose for FlutterFlow?
Contentful is the most popular with excellent documentation and generous free tier (25K records). Strapi is open-source and self-hostable. Sanity has real-time collaboration and a flexible schema. All three work with FlutterFlow via REST API.
Can non-technical editors update content without touching FlutterFlow?
Yes, that is the main benefit. Editors use the CMS dashboard to create, edit, and publish content. The webhook syncs changes to Firestore automatically. Editors never need to open FlutterFlow.
How do I handle images referenced in CMS content?
CMS platforms host images on their CDN. Contentful provides image URLs via the Asset API. Extract the image URL from the JSON response and use it in FlutterFlow Image widgets. For rich text inline images, flutter_html handles img tags automatically.
What happens if the CMS API is down?
Since your app reads from the Firestore cache, a CMS API outage does not affect users. They continue seeing cached content. New content published during the outage syncs when the CMS recovers and re-sends the webhook.
Can I use multiple content types from the same CMS?
Yes. Create separate API calls in the API Group for each content type (blog posts, product descriptions, FAQs, landing pages). Each content type can have its own FlutterFlow page template with appropriate widget layouts.
Can RapidDev help integrate a CMS with my FlutterFlow app?
Yes. RapidDev can set up headless CMS integration with content modeling, webhook-based syncing, rich text rendering, multilingual content, preview environments, and editorial workflow automation.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation