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

How to Create a Custom CRM System in FlutterFlow

Build a CRM using Firestore collections for contacts (with status pipeline fields like lead, prospect, customer, churned) and activities (call, email, meeting, and note logs per contact). Display contacts in a searchable ListView with ChoiceChips status filters, view each contact's activity timeline sorted by date, and visualize the sales pipeline as a horizontal Kanban board with contacts grouped by status column.

What you'll learn

  • How to model contacts and activity logs in Firestore for a CRM
  • How to build a searchable contact list with status and assignee filters
  • How to display a contact's activity timeline with logged interactions
  • How to create a Kanban-style pipeline view grouping contacts by status
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read25-35 minFlutterFlow Free+March 2026RapidDev Engineering Team
TL;DR

Build a CRM using Firestore collections for contacts (with status pipeline fields like lead, prospect, customer, churned) and activities (call, email, meeting, and note logs per contact). Display contacts in a searchable ListView with ChoiceChips status filters, view each contact's activity timeline sorted by date, and visualize the sales pipeline as a horizontal Kanban board with contacts grouped by status column.

Contact management with activity tracking and pipeline visualization

A CRM helps businesses track relationships with leads, prospects, and customers. This tutorial builds one in FlutterFlow: a contacts collection stores contact info and sales pipeline status, an activities collection logs every interaction (calls, emails, meetings, notes), and the UI provides a searchable contact list, a detailed activity timeline per contact, and a Kanban-style pipeline board. The system gives teams visibility into their sales funnel and ensures no follow-up falls through the cracks.

Prerequisites

  • A FlutterFlow project with Firebase/Firestore connected
  • Firebase Authentication enabled with team members signed in
  • Basic understanding of Backend Queries, ListView, and Conditional Styling
  • Firestore composite indexes for multi-field queries

Step-by-step guide

1

Create the Firestore data model for contacts and activities

Create a contacts collection with fields: name (String), email (String), phone (String), company (String), status (String: 'lead', 'prospect', 'customer', 'churned'), source (String: 'website', 'referral', 'cold_outreach', 'social'), notes (String), assignedTo (String: user UID of the team member), createdAt (Timestamp), and lastContactedAt (Timestamp). Create an activities collection with fields: contactId (String), type (String: 'call', 'email', 'meeting', 'note'), description (String), timestamp (Timestamp), and userId (String: who logged the activity). Set Firestore rules so any authenticated team member can read and write contacts and activities. Create composite indexes for: status + assignedTo + lastContactedAt (for filtered and sorted contact queries).

Expected result: Firestore has contacts and activities collections with the necessary fields and composite indexes for filtered queries.

2

Build the contact list page with search and filters

Create a ContactsListPage. At the top, add a TextField with a search icon prefix for searching by name or company. Below, add a Row of ChoiceChips for status filtering: All, Lead, Prospect, Customer, Churned. Add a DropDown for sorting: by name (alphabetical), by lastContactedAt (most recent first), or by createdAt (newest first). Below the filters, add a ListView bound to a Backend Query on the contacts collection. Apply filters dynamically: if a status chip is selected (not 'All'), filter where status equals the selected value. For search, use whereGreaterThanOrEqualTo on the name field. If an assignee filter is active, add where assignedTo equals the selected team member. Each list item is a Component showing: name (bold), company, email, a colored status badge Container (green for customer, blue for prospect, amber for lead, red for churned), and the lastContactedAt date. Tap navigates to ContactDetailPage.

Expected result: A contact list with working search, status chip filters, sort options, and colored status badges. Tapping a contact opens the detail page.

3

Build the contact detail page with info card and activity timeline

Create a ContactDetailPage receiving the contact document reference as a parameter. Display a header Card with the contact's name, company, email, phone, status badge, assigned team member, and source. Add an Edit IconButton that navigates to a ContactEditPage with pre-filled fields. Below the header, add an 'Activity Timeline' section with a ListView bound to a Backend Query on activities where contactId equals this contact's ID, ordered by timestamp descending. Each activity row shows: an icon based on type (Icons.phone for call, Icons.email for email, Icons.meeting_room for meeting, Icons.note for note), the description text, the timestamp, and who logged it. At the bottom, add a Row of quick-action buttons: Log Call, Log Email, Log Meeting, Add Note. Each opens a BottomSheet with a description TextField and a Save button that creates an activity doc and updates the contact's lastContactedAt timestamp.

Expected result: The contact detail page shows all contact info and a chronological activity timeline. Quick-action buttons let users log new interactions.

4

Create the Kanban pipeline view

Create a PipelinePage with a horizontal ScrollView containing four Column widgets side by side, one for each status: Lead, Prospect, Customer, Churned. Each column has a header Container showing the status name and the count of contacts in that status. Below the header, add a ListView bound to a Backend Query on contacts where status equals the column's status value, ordered by lastContactedAt descending. Each item is a compact card showing name, company, and days since last contact. To move a contact between columns, add a long-press action on each card that shows a PopupMenu with status options. Selecting a new status updates the contact's status field and the card moves to the appropriate column on the next query refresh. Set the horizontal ScrollView to scroll so all four columns are accessible on mobile screens.

Expected result: A horizontal Kanban board shows contacts grouped by pipeline status. Long-pressing a card lets users move it to a different status column.

5

Add bulk actions and contact statistics

On the ContactsListPage, add a multi-select mode. Add a Checkbox to each contact row and a Page State selectedContactIds (List of Strings). When one or more contacts are selected, show a bulk action bar at the bottom with buttons: Change Status (DropDown selection applies to all selected), Change Assignee (DropDown of team members), and Delete (with confirmation dialog). Each bulk action loops through selectedContactIds and updates the corresponding documents. Above the contact list, add a summary Row showing KPI cards: Total Contacts, Leads This Month, Contacts Without Activity in 7+ Days (stale leads), and Conversion Rate (customers / total contacts). Bind each to Backend Queries with appropriate filters and Custom Functions for calculations.

Expected result: Users can select multiple contacts for bulk status or assignee changes. KPI cards show pipeline health metrics at a glance.

Complete working example

CRM Architecture
1Firestore Data Model:
2 contacts/{contactId}
3 name: String ("Jane Smith")
4 email: String
5 phone: String
6 company: String ("Acme Inc")
7 status: String ("lead" | "prospect" | "customer" | "churned")
8 source: String ("website" | "referral" | "cold_outreach" | "social")
9 notes: String
10 assignedTo: String (team member UID)
11 createdAt: Timestamp
12 lastContactedAt: Timestamp
13 activities/{activityId}
14 contactId: String
15 type: String ("call" | "email" | "meeting" | "note")
16 description: String ("Discussed pricing options")
17 timestamp: Timestamp
18 userId: String (who logged it)
19
20Composite Indexes:
21 contacts: status ASC + lastContactedAt DESC
22 contacts: assignedTo ASC + status ASC + lastContactedAt DESC
23 activities: contactId ASC + timestamp DESC
24
25Contacts List Page:
26 Row (KPI cards)
27 Card (Total Contacts)
28 Card (Leads This Month)
29 Card (Stale > 7 days)
30 Card (Conversion Rate %)
31 TextField (search by name/company)
32 ChoiceChips (All | Lead | Prospect | Customer | Churned)
33 DropDown (sort by: name | lastContacted | created)
34 ListView (filtered + sorted contacts)
35 Row
36 Checkbox (multi-select)
37 Column Text (name) + Text (company)
38 Container (status badge, colored)
39 Text (lastContactedAt)
40 On Tap Navigate ContactDetailPage
41
42Contact Detail Page:
43 Card (contact info: name, company, email, phone, status, assignee)
44 Activity Timeline ListView (activities for this contact)
45 Row Icon (type-based) + Text (description) + Text (date)
46 Quick Actions Row
47 Button (Log Call)
48 Button (Log Email)
49 Button (Log Meeting)
50 Button (Add Note)
51 Each BottomSheet TextField + Save
52
53Pipeline (Kanban) Page:
54 ScrollView.horizontal
55 Column (Lead) Header + ListView
56 Column (Prospect) Header + ListView
57 Column (Customer) Header + ListView
58 Column (Churned) Header + ListView

Common mistakes when creating a Custom CRM System in FlutterFlow

Why it's a problem: Not indexing the contacts collection for compound queries combining status, assignedTo, and sort by lastContactedAt

How to avoid: Create composite indexes proactively in the Firebase Console for your most common query patterns: status + lastContactedAt descending, and assignedTo + status + lastContactedAt descending. Check the Firestore error logs for missing index links.

Why it's a problem: Querying the activities collection without a contactId filter for the timeline

How to avoid: Always filter activities where contactId equals the current contact's document ID. Add a composite index on contactId + timestamp descending for efficient sorted retrieval.

Why it's a problem: Not updating lastContactedAt when logging a new activity

How to avoid: In every Log Call, Log Email, and Log Meeting Action Flow, after creating the activity document, add an Update Document action on the contact to set lastContactedAt to the current timestamp.

Best practices

  • Create composite Firestore indexes proactively for all filter and sort combinations used in the contact list
  • Update lastContactedAt on the contact doc every time an activity is logged
  • Use colored status badges for instant visual identification of pipeline stage
  • Denormalize the assignee's displayName on the contact doc to avoid extra user doc lookups in the list
  • Paginate the contact list with limit 20 and infinite scroll for large contact databases
  • Add a Kanban view for pipeline visualization alongside the list view for different workflows
  • Log all interactions (even brief notes) to build a complete relationship timeline

Still stuck?

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

ChatGPT Prompt

Design a Firestore data model for a CRM with contacts (name, email, company, pipeline status, assignee) and an activities collection for logging calls, emails, meetings, and notes per contact. Include the composite indexes needed for filtered and sorted queries.

FlutterFlow Prompt

Create a contacts list page with a search TextField, status ChoiceChips (Lead, Prospect, Customer, Churned), and a sorted ListView. Each contact row shows name, company, a colored status badge, and last contacted date.

Frequently asked questions

How do I implement drag-and-drop between Kanban columns?

FlutterFlow does not support native drag-and-drop. Use a long-press action on each contact card to show a PopupMenu with status options. Selecting a new status updates the contact document and the query refreshes the columns. For true drag-and-drop, create a Custom Widget using Flutter's Draggable and DragTarget widgets.

How do I search contacts by name, email, and company simultaneously?

Firestore does not support full-text search natively. For simple prefix matching, use whereGreaterThanOrEqualTo on one field. For multi-field search, add a searchTerms array field on each contact containing lowercase fragments of name, email, and company, then use arrayContainsAny. For production, integrate Algolia or Typesense as a search index.

Can I add email integration to send emails directly from the CRM?

Yes. Create a Cloud Function that sends emails via SendGrid, Mailgun, or the Gmail API. Add a Send Email button on the contact detail page that opens a compose form (to, subject, body). On send, call the Cloud Function and log the email as an activity.

How do I track conversion rates in the pipeline?

Create a Custom Function that calculates conversions: query contacts by status and count results. Conversion rate = (customers / total contacts) * 100. For time-based metrics, filter by createdAt date range. Display these metrics in the KPI cards at the top of the contacts list page.

How do I import existing contacts from a CSV file?

Create a Custom Action that reads a CSV file uploaded via FlutterFlowUploadButton, parses each row, and creates Firestore documents. Alternatively, use a Cloud Function that accepts a CSV file URL, parses it server-side, and batch-writes contacts. Always validate and deduplicate by email before importing.

Can RapidDev help build a full-featured CRM?

Yes. A production CRM needs email sequences, automated follow-up reminders, lead scoring, deal value tracking, reporting dashboards, calendar integration, and third-party sync with tools like HubSpot or Salesforce. RapidDev can build the complete system with Cloud Functions and integrations.

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.