Build your event management app with two Firestore collections: events and event_guests (as a separate collection, not an array on the event). Add RSVP tracking with status field (invited/confirmed/declined/checked_in), QR code check-in using mobile_scanner Custom Widget, email invitations via Cloud Function, and a live attendance dashboard using Firestore aggregate queries.
Event Management Built Right in Firestore
Event management apps look straightforward until you hit the Firestore 1MB document limit with a large guest list, or try to query all confirmed attendees across hundreds of events. The correct data model separates guests into their own collection from day one. This guide builds the full stack: event creation, invitation sending, RSVP tracking, QR code check-in at the door, and a live dashboard — all wired to Firestore real-time listeners so organizers see attendance update as guests arrive.
Prerequisites
- FlutterFlow project with Firebase and Firestore configured
- Firebase Authentication set up for organizer login
- Cloud Functions enabled (Firebase Blaze plan) for email invitation sending
- Basic understanding of Firestore collections and queries
Step-by-step guide
Design the Firestore Data Model for Events and Guests
Design the Firestore Data Model for Events and Guests
Create two top-level Firestore collections. The 'events' collection stores: title, description, date_time, location, organizer_id, max_capacity, cover_image_url, is_public, and created_at. The 'event_guests' collection stores each invitation as a separate document with: event_id, guest_email, guest_name, guest_user_id (nullable for non-app users), status (String — 'invited', 'confirmed', 'declined', 'checked_in'), invited_at, responded_at, checked_in_at, and qr_code (a unique token for check-in). This separation means you can have 10,000 guests per event without hitting Firestore's document size limit, and you can query all events a specific guest has been invited to across your entire database.
Expected result: Firestore shows an 'events' collection and an 'event_guests' collection. Each event_guests document has a unique qr_code token field.
Build the Event Creation and RSVP Form
Build the Event Creation and RSVP Form
Create an EventCreationPage with text fields for title, description, and location, a DateTimePicker for the event date and time, an image upload for the cover photo, and a NumberField for max_capacity. On form submission, create the event document in Firestore and navigate to the event detail page. On the EventDetailPage, add an RSVP section with Confirm and Decline buttons. Tapping Confirm creates an event_guests document (if not already present) with status 'confirmed', or updates an existing invitation's status. Show the current RSVP counts (confirmed, declined, pending) in a Row of stat chips at the top of the page using a Firestore aggregate query or a count from a StreamBuilder.
Expected result: Organizers can create events and see them in a list. Guests can tap Confirm or Decline and see their status reflected immediately.
Send Email Invitations via Cloud Function
Send Email Invitations via Cloud Function
Create a Cloud Function called sendEventInvitations that accepts an event_id and a list of email addresses. For each email, check if the address already has an event_guests document for this event — skip if already invited. Create the event_guests document with status 'invited' and a generated UUID as the qr_code. Send an invitation email using SendGrid or Firebase's Email Trigger Extension with the event details, RSVP links, and a note that their QR code will be sent separately upon confirmation. In FlutterFlow, add an Invite Guests button on the EventDetailPage that opens a dialog with a multi-line TextField for comma-separated email addresses.
1const functions = require('firebase-functions');2const admin = require('firebase-admin');3const { v4: uuidv4 } = require('uuid');45exports.sendEventInvitations = functions.https.onCall(async (data, context) => {6 if (!context.auth) throw new functions.https.HttpsError('unauthenticated', 'Login required');78 const { eventId, emails } = data;9 const db = admin.firestore();1011 const eventSnap = await db.collection('events').doc(eventId).get();12 if (!eventSnap.exists) throw new functions.https.HttpsError('not-found', 'Event not found');1314 const event = eventSnap.data();15 const results = { sent: 0, skipped: 0 };1617 for (const email of emails) {18 const cleanEmail = email.trim().toLowerCase();19 if (!cleanEmail) continue;2021 // Check for existing invitation22 const existing = await db.collection('event_guests')23 .where('event_id', '==', eventId)24 .where('guest_email', '==', cleanEmail)25 .limit(1).get();2627 if (!existing.empty) { results.skipped++; continue; }2829 const qrToken = uuidv4();30 await db.collection('event_guests').add({31 event_id: eventId,32 guest_email: cleanEmail,33 guest_name: cleanEmail.split('@')[0],34 guest_user_id: null,35 status: 'invited',36 qr_code: qrToken,37 invited_at: admin.firestore.FieldValue.serverTimestamp(),38 responded_at: null,39 checked_in_at: null,40 });4142 // Send email via trigger extension or SendGrid43 await db.collection('mail').add({44 to: cleanEmail,45 template: { name: 'event-invitation', data: { eventTitle: event.title, eventDate: event.date_time.toDate().toLocaleDateString(), qrToken } },46 });4748 results.sent++;49 }5051 return results;52});Expected result: Pasting 10 email addresses and tapping Send Invitations sends emails to all new invitees and shows a summary 'Sent: 8, Skipped: 2 (already invited)'.
Build the QR Code Check-In Scanner
Build the QR Code Check-In Scanner
Create a Custom Widget called QRScannerWidget using the mobile_scanner package. The widget shows the camera viewfinder and calls a callback with the scanned QR code string when detected. On the EventCheckinPage, display this widget and wire the scan callback to a Custom Action called processCheckin. The processCheckin action queries event_guests where qr_code equals the scanned value AND event_id equals the current event. If found and status is 'confirmed', update status to 'checked_in' and checked_in_at to server timestamp. Show a green success overlay with the guest name. If status is already 'checked_in', show a yellow warning 'Already checked in'. If not found, show a red error overlay.
1import 'package:mobile_scanner/mobile_scanner.dart';2import 'package:flutter/material.dart';34class QRScannerWidget extends StatefulWidget {5 final ValueChanged<String> onScanned;6 const QRScannerWidget({super.key, required this.onScanned});78 @override9 State<QRScannerWidget> createState() => _QRScannerWidgetState();10}1112class _QRScannerWidgetState extends State<QRScannerWidget> {13 final MobileScannerController _controller = MobileScannerController();14 bool _isProcessing = false;1516 @override17 Widget build(BuildContext context) {18 return MobileScanner(19 controller: _controller,20 onDetect: (capture) {21 if (_isProcessing) return;22 final barcode = capture.barcodes.firstOrNull;23 final value = barcode?.rawValue;24 if (value != null) {25 setState(() => _isProcessing = true);26 widget.onScanned(value);27 // Reset after 2 seconds to allow next scan28 Future.delayed(const Duration(seconds: 2), () {29 if (mounted) setState(() => _isProcessing = false);30 });31 }32 },33 );34 }3536 @override37 void dispose() {38 _controller.dispose();39 super.dispose();40 }41}Expected result: Pointing the camera at a guest's QR code shows their name in a green banner within 1 second. The check-in status updates in Firestore and the attendance count increments on the dashboard.
Build the Real-Time Attendance Dashboard
Build the Real-Time Attendance Dashboard
Create an AttendanceDashboardPage with stat cards showing: Total Invited (count of all event_guests for this event), Confirmed (count where status == 'confirmed'), Checked In (count where status == 'checked_in'), Declined (count where status == 'declined'), and a percentage capacity bar (checked_in / max_capacity). Use Firestore StreamBuilders for each count query. Add a ListView of recent check-ins ordered by checked_in_at descending showing the last 20 arrivals with guest name and time. Add a search TextField that filters the full guest list by name or email for organizers who need to manually look up a guest.
Expected result: As guests check in, the Checked In counter increments in real time. The capacity bar fills gradually. The Recent Arrivals list updates within seconds of each scan.
Complete working example
1// FlutterFlow Custom Action: processCheckin2// Called when QR scanner detects a code3// Returns: { status: 'success'|'already_checked_in'|'not_found', guestName: String }45import 'package:cloud_firestore/cloud_firestore.dart';67Future<Map<String, dynamic>> processCheckin(8 String qrCode,9 String eventId,10) async {11 final db = FirebaseFirestore.instance;1213 // Find the guest document by QR code and event ID14 final querySnap = await db15 .collection('event_guests')16 .where('qr_code', isEqualTo: qrCode)17 .where('event_id', isEqualTo: eventId)18 .limit(1)19 .get();2021 if (querySnap.docs.isEmpty) {22 return {'status': 'not_found', 'guest_name': ''};23 }2425 final doc = querySnap.docs.first;26 final data = doc.data();27 final currentStatus = data['status'] as String? ?? '';2829 if (currentStatus == 'checked_in') {30 return {31 'status': 'already_checked_in',32 'guest_name': data['guest_name'] ?? 'Guest',33 'checked_in_at': data['checked_in_at'],34 };35 }3637 if (currentStatus == 'declined') {38 return {39 'status': 'declined',40 'guest_name': data['guest_name'] ?? 'Guest',41 };42 }4344 // Update status to checked_in45 await doc.reference.update({46 'status': 'checked_in',47 'checked_in_at': FieldValue.serverTimestamp(),48 });4950 // Increment event checked_in counter51 await db.collection('events').doc(eventId).update({52 'checked_in_count': FieldValue.increment(1),53 });5455 return {56 'status': 'success',57 'guest_name': data['guest_name'] ?? 'Guest',58 'guest_email': data['guest_email'] ?? '',59 };60}Common mistakes when creating an Event Planning and Guest Management App in FlutterFlow
Why it's a problem: Storing the guest list as an Array field on the event document
How to avoid: Store guests as a separate 'event_guests' collection with an event_id field. This scales to millions of guests and allows cross-event queries like 'show all events this user was invited to'.
Why it's a problem: Using a sequential integer as the QR code token
How to avoid: Always use a UUID or cryptographically random token as the QR code value. UUIDs are unguessable and unique across your entire database.
Why it's a problem: Not handling the already-checked-in case in the scanner
How to avoid: Check the current status before updating. If already 'checked_in', show a yellow warning with the original check-in timestamp instead of a success message, and do not increment the counter again.
Best practices
- Add a counter field (confirmed_count, checked_in_count) directly on the event document and use FieldValue.increment() when statuses change — this avoids expensive aggregate queries for the dashboard.
- Generate QR codes as UUIDs on the server (Cloud Function) not on the client to ensure uniqueness and prevent manipulation.
- Add a waitlist feature: when confirmed_count reaches max_capacity, change RSVP flow to add to a waitlist status and promote when others decline.
- Cache the event document in App State on the checkin page so the scanner does not need to re-fetch event details for every scan.
- Add a torch toggle button to the QR scanner for dimly lit event venues — this dramatically improves scan reliability.
- For paid events, add a payment_status field alongside the RSVP status and block check-in if payment is not confirmed.
- Export attendance as CSV from a Cloud Function for post-event analytics — include guest name, email, RSVP response time, and check-in time.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I am building an event management app in FlutterFlow with Firebase. Show me the correct Firestore data model for events and guest lists at scale, how to send invitation emails via Cloud Function, how to implement QR code check-in using mobile_scanner in a Custom Widget, and how to build a real-time attendance dashboard. Include Dart and JavaScript code.
In my FlutterFlow app, create a Custom Action called processCheckin that takes a qrCode string and eventId string, queries the event_guests Firestore collection for the matching document, returns 'success' with the guest name if status is confirmed, returns 'already_checked_in' if status is already checked_in, and returns 'not_found' if no matching document exists. Update the status and checked_in_at field on success.
Frequently asked questions
How do I let attendees add events to their phone calendar?
Use the add_2_calendar Flutter package in a Custom Action. Create a CustomEvent object with the event title, description, location, start time, and end time, then call Add2Calendar.addEvent2Cal(event). On iOS, this opens the native Calendar add dialog. On Android, it opens the default calendar app. This requires no additional permissions — the user confirms by tapping Add in the calendar dialog.
Can guests RSVP without creating an account in my app?
Yes, using anonymous authentication. When a guest clicks the RSVP link in their invitation email, sign them in anonymously with Firebase Auth, link their email to the anonymous account, and create/update their event_guests document. You can later upgrade the anonymous account to a full email/password account when they create a profile. This allows frictionless RSVP without requiring sign-up.
How do I generate a printable PDF guest list for the event?
Create a Cloud Function that queries event_guests for the event, formats the data as an HTML table, converts it to PDF using a library like pdf or puppeteer, uploads it to Firebase Storage, and returns the download URL. In FlutterFlow, add a Download Guest List button that calls this Cloud Function and opens the returned URL in a browser for download.
What is the maximum number of guests this architecture supports?
The separate event_guests collection approach scales to millions of guests per event. Firestore can handle this volume. The practical limit is your Cloud Function memory when sending bulk email invitations — process invitations in batches of 500 to avoid timeout issues. Use Cloud Tasks for very large invitation lists (10,000+) to queue emails as background tasks.
How do I handle plus-ones and group RSVPs?
Add a plus_one_count field to the event_guests document when the guest confirms. For group RSVPs, add a party_size field. Update the event's confirmed_count by the party_size amount rather than 1. Add a party_members array field to store additional guest names if you need them individually tracked for the check-in scanner.
Can I send reminder notifications to guests who have not RSVPed?
Yes. Create a scheduled Cloud Function that runs daily, queries event_guests where status equals 'invited' and invited_at is more than 48 hours ago and the event date is in the future. Send a reminder email to each. Add a reminder_sent boolean field to avoid sending multiple reminders and update it after the first reminder is sent.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation