Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Create a Custom Landing Page with Dynamic Content in FlutterFlow

Build a CMS-driven landing page in FlutterFlow by storing section content in Firestore landing_sections and landing_settings collections. A Backend Query fetches sections ordered by a numeric order field. Reusable Section Components render Hero, Features, Testimonials, and CTA sections dynamically. An admin page lets you edit all copy without touching the FlutterFlow editor or republishing the app.

What you'll learn

  • Design a Firestore schema for CMS-driven landing page sections
  • Build reusable Section Components for Hero, Features, Testimonials, and CTA
  • Fetch and render sections dynamically in ordered sequence using a Backend Query
  • Create an admin editor page so non-technical team members can update content
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner10 min read40-55 minFlutterFlow Free+March 2026RapidDev Engineering Team
TL;DR

Build a CMS-driven landing page in FlutterFlow by storing section content in Firestore landing_sections and landing_settings collections. A Backend Query fetches sections ordered by a numeric order field. Reusable Section Components render Hero, Features, Testimonials, and CTA sections dynamically. An admin page lets you edit all copy without touching the FlutterFlow editor or republishing the app.

CMS-Driven Landing Page Without Republishing

A hardcoded landing page means every copy change — a new headline, an updated price, a fresh testimonial — requires opening FlutterFlow, editing widget properties, and republishing the app. For a web app this takes minutes; for mobile it takes App Store review time. This tutorial solves the problem by storing all landing page content in Firestore. The landing page fetches sections on load, renders them using a set of reusable Section Components (Hero, Features, Testimonials, CTA), and reflects content updates immediately. An admin page with TextFields and Toggle widgets lets you or your marketing team update any text without touching FlutterFlow.

Prerequisites

  • FlutterFlow account with Firebase connected and Firestore enabled
  • Firebase Authentication enabled (for admin page access control)
  • Basic familiarity with FlutterFlow Components and Backend Queries
  • An existing FlutterFlow project to add the landing page to

Step-by-step guide

1

Design the Firestore Schema for Landing Page Sections

In Firestore, create two collections. The first is landing_sections with fields: sectionType (String — 'hero', 'features', 'testimonials', 'cta'), order (Integer — controls display sequence), isActive (Boolean — toggle sections on/off), headline (String), subheadline (String), bodyText (String), ctaLabel (String), ctaUrl (String), imageUrl (String), items (Array of Maps — for features list or testimonials). The second collection is landing_settings with a single document (ID: config) containing: appName (String), primaryColor (String hex), logoUrl (String), footerText (String), contactEmail (String). In FlutterFlow's Firestore panel, import both schemas and create Document Types. Create three initial landing_sections documents with sectionType hero (order 1), features (order 2), and cta (order 3).

Expected result: Two Firestore collections are created with sample data. FlutterFlow shows the Document Types in its Firestore panel.

2

Build Reusable Section Components

In FlutterFlow, open the Components panel and create four components. The first component is HeroSection — add a full-width Column with a large Text widget bound to the headline parameter, a smaller Text for subheadline, a body Text, and a Button bound to ctaLabel whose On Tap action navigates to ctaUrl. The second component is FeaturesSection — add a headline Text and a GridView with 2 columns. Inside the GridView, show each item's icon (from items[].icon), title (items[].title), and description (items[].description). The third component is TestimonialsSection — a horizontal ListView of cards each showing items[].quote and items[].author. The fourth component is CTASection — a gradient background Column with headline, bodyText, and a prominent Button. Each component accepts the full landing_sections Document as a parameter.

Expected result: Four components appear in the FlutterFlow Components panel, each rendering correctly with test data.

3

Build the Landing Page with Dynamic Section Rendering

Create a new page called LandingPage. Add a Backend Query at the page level that queries the landing_sections collection ordered by the order field (ascending) with an isActive equals true filter. This ensures sections appear in the correct order and deactivated sections are hidden. Add a SingleChildScrollView containing a Column. Inside the Column, add a ListView or use Generate Dynamic Children to iterate over the query results. For each section, use a Conditional Builder (or if-else action) based on the sectionType field to render the appropriate component: if sectionType equals 'hero' show HeroSection, if 'features' show FeaturesSection, if 'testimonials' show TestimonialsSection, if 'cta' show CTASection. Pass the full section document to the component.

Expected result: The landing page renders all active Firestore sections in the correct order. Changing Firestore data updates the page on refresh.

4

Create the Admin Editor for Content Updates

Create a new page called AdminEditor. Protect it with an On Page Load action that checks if currentUser.email is in an admin allowlist stored in landing_settings — if not, navigate back to Home. Add a Backend Query streaming all landing_sections documents. Display them in a ListView. Each item shows the sectionType, order number, and an isActive Toggle. Tapping a section opens a Bottom Sheet with TextFields pre-filled with the section's headline, subheadline, bodyText, ctaLabel, ctaUrl, and imageUrl values. The Save button fires a Firestore Update Document action. Add a New Section button at the top that opens a creation dialog where the admin picks a sectionType, sets the order, and writes a new document.

Expected result: The admin page shows all sections with live toggles. Editing a section and saving immediately updates the live landing page for all users.

5

Add Section Reordering with Drag-and-Drop

To make the admin page more powerful, add drag-and-drop section reordering. Create a Custom Widget named ReorderableSectionList using Flutter's built-in ReorderableListView widget. The widget accepts a list of section documents and an onReorder callback. When the user drags sections to reorder them, onReorder fires with the old and new indices. In the callback, calculate new order values and write them back to Firestore using a batch update — update each moved section's order field. In FlutterFlow, place the ReorderableSectionList Custom Widget on the AdminEditor page replacing the standard ListView.

reorderable_section_list.dart
1// Custom Widget: ReorderableSectionList
2// Accepts: sections (List<dynamic>), onReorder callback
3import 'package:flutter/material.dart';
4
5class ReorderableSectionList extends StatefulWidget {
6 final List<dynamic> sections;
7 final Future<dynamic> Function(int oldIndex, int newIndex) onReorder;
8 final double width;
9 final double height;
10
11 const ReorderableSectionList({
12 Key? key,
13 required this.sections,
14 required this.onReorder,
15 required this.width,
16 required this.height,
17 }) : super(key: key);
18
19 @override
20 State<ReorderableSectionList> createState() => _State();
21}
22
23class _State extends State<ReorderableSectionList> {
24 late List<dynamic> _sections;
25
26 @override
27 void initState() {
28 super.initState();
29 _sections = List.from(widget.sections);
30 }
31
32 @override
33 Widget build(BuildContext context) {
34 return SizedBox(
35 width: widget.width,
36 height: widget.height,
37 child: ReorderableListView(
38 onReorder: (oldIndex, newIndex) {
39 setState(() {
40 if (newIndex > oldIndex) newIndex--;
41 final item = _sections.removeAt(oldIndex);
42 _sections.insert(newIndex, item);
43 });
44 widget.onReorder(oldIndex, newIndex);
45 },
46 children: _sections.map((s) {
47 final data = s.data() as Map<String, dynamic>? ?? {};
48 return ListTile(
49 key: ValueKey(s.id),
50 leading: const Icon(Icons.drag_handle),
51 title: Text(data['sectionType'] ?? 'Unknown'),
52 subtitle: Text('Order: ${data['order'] ?? 0}'),
53 );
54 }).toList(),
55 ),
56 );
57 }
58}

Expected result: The admin can drag sections up and down in the list. Releasing the drag saves new order values to Firestore and the landing page updates immediately.

Complete working example

reorderable_section_list.dart
1// Custom Widget: ReorderableSectionList for FlutterFlow Admin Editor
2// Allows drag-and-drop reordering of landing page sections
3
4import 'package:flutter/material.dart';
5
6class ReorderableSectionList extends StatefulWidget {
7 final List<dynamic> sections;
8 final Future<dynamic> Function(int oldIndex, int newIndex) onReorder;
9 final Future<dynamic> Function(String sectionId) onToggleActive;
10 final double width;
11 final double height;
12
13 const ReorderableSectionList({
14 Key? key,
15 required this.sections,
16 required this.onReorder,
17 required this.onToggleActive,
18 required this.width,
19 required this.height,
20 }) : super(key: key);
21
22 @override
23 State<ReorderableSectionList> createState() => _ReorderableSectionListState();
24}
25
26class _ReorderableSectionListState extends State<ReorderableSectionList> {
27 late List<dynamic> _sections;
28
29 @override
30 void initState() {
31 super.initState();
32 _sections = List.from(widget.sections);
33 }
34
35 @override
36 void didUpdateWidget(covariant ReorderableSectionList oldWidget) {
37 super.didUpdateWidget(oldWidget);
38 if (widget.sections != oldWidget.sections) {
39 setState(() => _sections = List.from(widget.sections));
40 }
41 }
42
43 Color _typeColor(String type) {
44 switch (type) {
45 case 'hero': return Colors.purple.shade100;
46 case 'features': return Colors.blue.shade100;
47 case 'testimonials': return Colors.green.shade100;
48 case 'cta': return Colors.orange.shade100;
49 default: return Colors.grey.shade100;
50 }
51 }
52
53 @override
54 Widget build(BuildContext context) {
55 return SizedBox(
56 width: widget.width,
57 height: widget.height,
58 child: ReorderableListView(
59 padding: const EdgeInsets.symmetric(vertical: 8),
60 onReorder: (int oldIndex, int newIndex) {
61 setState(() {
62 if (newIndex > oldIndex) newIndex--;
63 final item = _sections.removeAt(oldIndex);
64 _sections.insert(newIndex, item);
65 });
66 widget.onReorder(oldIndex, newIndex);
67 },
68 children: List.generate(_sections.length, (i) {
69 final s = _sections[i];
70 final data = s.data() as Map<String, dynamic>? ?? {};
71 final type = data['sectionType'] as String? ?? 'unknown';
72 final isActive = data['isActive'] as bool? ?? true;
73 return Card(
74 key: ValueKey(s.id),
75 color: _typeColor(type),
76 margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
77 child: ListTile(
78 leading: const Icon(Icons.drag_handle, color: Colors.grey),
79 title: Text(
80 type.toUpperCase(),
81 style: const TextStyle(fontWeight: FontWeight.bold),
82 ),
83 subtitle: Text(data['headline'] ?? '(no headline)'),
84 trailing: Switch(
85 value: isActive,
86 onChanged: (_) => widget.onToggleActive(s.id),
87 ),
88 ),
89 );
90 }),
91 ),
92 );
93 }
94}

Common mistakes when creating a Custom Landing Page with Dynamic Content in FlutterFlow

Why it's a problem: Hardcoding all landing page text directly in FlutterFlow widget properties

How to avoid: Store all content strings in Firestore landing_sections documents. Bind Text widget values to Firestore fields. Updates take effect immediately for all users with no republish required.

Why it's a problem: Creating a separate Firestore document for every individual text string on the page

How to avoid: Group all text for a section (headline, subheadline, body, CTA) into a single Firestore document. One document read provides all the text for an entire section.

Why it's a problem: Not protecting the admin editor page with an authentication and role check

How to avoid: Add an On Page Load action that checks currentUser.email against an admin allowlist in landing_settings, or check for an admin custom claim. Redirect non-admins to the home page immediately.

Best practices

  • Use integer order values with gaps of 10 so you can insert new sections between existing ones without renumbering.
  • Add an isActive toggle to each section so you can stage content and activate it on launch day without deleting and recreating documents.
  • Cache landing_settings in App State on app start since this document changes rarely and avoids a Firestore read on every page visit.
  • Build each section type as a separate FlutterFlow Component rather than inline widgets — components are reusable across multiple pages.
  • Add a createdAt and updatedAt timestamp to each landing_sections document for audit purposes.
  • Store image URLs in Firestore pointing to Firebase Storage — never hardcode image asset paths in Firestore since Storage URLs can change.
  • For A/B testing, add an abVariant field to sections and show different variants to different users based on their userId hash.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building a landing page in FlutterFlow where all the text content is stored in Firestore so my marketing team can update it without republishing the app. I have a landing_sections collection with fields: sectionType, order, headline, subheadline, ctaLabel, ctaUrl, and isActive. How do I build a FlutterFlow page that fetches all active sections ordered by the order field and conditionally renders different FlutterFlow Components based on the sectionType value?

FlutterFlow Prompt

Create a FlutterFlow Backend Query on a page that fetches documents from the landing_sections Firestore collection, filters by isActive equals true, and orders by the order field ascending. Then show me how to use a Conditional Builder to render different Components based on the sectionType field value.

Frequently asked questions

How long does it take for Firestore content changes to appear on the landing page?

If you use a streaming Backend Query (the default in FlutterFlow), changes appear within 1-2 seconds in real time without the user refreshing. If you use a one-time Backend Query, changes appear the next time the user navigates to the page.

Can I add new section types without updating the FlutterFlow app?

No — adding a new section type requires building a new Component in FlutterFlow and republishing. What the CMS approach avoids is changing existing content (text, images, URLs). If you want fully dynamic section types, consider a Custom Widget that renders section content from a JSON schema.

How do I handle images in the landing sections?

Store image URLs in the imageUrl field of each landing_sections document. Upload images to Firebase Storage, get the download URL, and paste it into Firestore. The Image widget in your Section Component is bound to this field. To update an image, upload a new file to Storage, get the new URL, and update the Firestore document — no republish needed.

Can my marketing team update content without a FlutterFlow or Firebase account?

Yes — that is the purpose of the admin editor page built in Step 4. The admin page is part of your FlutterFlow app and is protected by Firebase Authentication. Give your marketing team an account with admin role, and they can log in and edit content through the admin page without any Firebase Console access.

How do I add a features list with icons to the FeaturesSection component?

Store the features as an Array of Maps in the items field of the Firestore document. Each map has fields: icon (String — icon name or emoji), title (String), and description (String). In the FeaturesSection Component, add a ListView or GridView bound to the items array and display each item's fields.

Is this approach suitable for SEO if I am building a web app?

FlutterFlow web apps use Flutter's web rendering which is not ideal for SEO since content is rendered client-side. For maximum SEO, consider using a static section for the above-the-fold hero content and loading dynamic content below. For apps where SEO is the primary concern, a Next.js or Webflow solution may be more appropriate.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.