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
Create the Firestore data model for contacts and activities
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.
Build the contact list page with search and filters
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.
Build the contact detail page with info card and activity timeline
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.
Create the Kanban pipeline view
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.
Add bulk actions and contact statistics
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
1Firestore Data Model:2├── contacts/{contactId}3│ ├── name: String ("Jane Smith")4│ ├── email: String5│ ├── phone: String6│ ├── company: String ("Acme Inc")7│ ├── status: String ("lead" | "prospect" | "customer" | "churned")8│ ├── source: String ("website" | "referral" | "cold_outreach" | "social")9│ ├── notes: String10│ ├── assignedTo: String (team member UID)11│ ├── createdAt: Timestamp12│ └── lastContactedAt: Timestamp13└── activities/{activityId}14 ├── contactId: String15 ├── type: String ("call" | "email" | "meeting" | "note")16 ├── description: String ("Discussed pricing options")17 ├── timestamp: Timestamp18 └── userId: String (who logged it)1920Composite Indexes:21├── contacts: status ASC + lastContactedAt DESC22├── contacts: assignedTo ASC + status ASC + lastContactedAt DESC23└── activities: contactId ASC + timestamp DESC2425Contacts 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 └── Row36 ├── Checkbox (multi-select)37 ├── Column → Text (name) + Text (company)38 ├── Container (status badge, colored)39 ├── Text (lastContactedAt)40 └── On Tap → Navigate ContactDetailPage4142Contact 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 Row47 ├── Button (Log Call)48 ├── Button (Log Email)49 ├── Button (Log Meeting)50 └── Button (Add Note)51 └── Each → BottomSheet → TextField + Save5253Pipeline (Kanban) Page:54└── ScrollView.horizontal55 ├── Column (Lead) → Header + ListView56 ├── Column (Prospect) → Header + ListView57 ├── Column (Customer) → Header + ListView58 └── Column (Churned) → Header + ListViewCommon 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.
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.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation