# How to Create an Event Planning and Guest Management App in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 60-80 min
- Compatibility: FlutterFlow Free+ (Firebase required; Pro for QR scanner Custom Widget)
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

### 2. 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.

### 3. 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.

```
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const { v4: uuidv4 } = require('uuid');

exports.sendEventInvitations = functions.https.onCall(async (data, context) => {
  if (!context.auth) throw new functions.https.HttpsError('unauthenticated', 'Login required');

  const { eventId, emails } = data;
  const db = admin.firestore();

  const eventSnap = await db.collection('events').doc(eventId).get();
  if (!eventSnap.exists) throw new functions.https.HttpsError('not-found', 'Event not found');

  const event = eventSnap.data();
  const results = { sent: 0, skipped: 0 };

  for (const email of emails) {
    const cleanEmail = email.trim().toLowerCase();
    if (!cleanEmail) continue;

    // Check for existing invitation
    const existing = await db.collection('event_guests')
      .where('event_id', '==', eventId)
      .where('guest_email', '==', cleanEmail)
      .limit(1).get();

    if (!existing.empty) { results.skipped++; continue; }

    const qrToken = uuidv4();
    await db.collection('event_guests').add({
      event_id: eventId,
      guest_email: cleanEmail,
      guest_name: cleanEmail.split('@')[0],
      guest_user_id: null,
      status: 'invited',
      qr_code: qrToken,
      invited_at: admin.firestore.FieldValue.serverTimestamp(),
      responded_at: null,
      checked_in_at: null,
    });

    // Send email via trigger extension or SendGrid
    await db.collection('mail').add({
      to: cleanEmail,
      template: { name: 'event-invitation', data: { eventTitle: event.title, eventDate: event.date_time.toDate().toLocaleDateString(), qrToken } },
    });

    results.sent++;
  }

  return results;
});
```

**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)'.

### 4. 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.

```
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:flutter/material.dart';

class QRScannerWidget extends StatefulWidget {
  final ValueChanged<String> onScanned;
  const QRScannerWidget({super.key, required this.onScanned});

  @override
  State<QRScannerWidget> createState() => _QRScannerWidgetState();
}

class _QRScannerWidgetState extends State<QRScannerWidget> {
  final MobileScannerController _controller = MobileScannerController();
  bool _isProcessing = false;

  @override
  Widget build(BuildContext context) {
    return MobileScanner(
      controller: _controller,
      onDetect: (capture) {
        if (_isProcessing) return;
        final barcode = capture.barcodes.firstOrNull;
        final value = barcode?.rawValue;
        if (value != null) {
          setState(() => _isProcessing = true);
          widget.onScanned(value);
          // Reset after 2 seconds to allow next scan
          Future.delayed(const Duration(seconds: 2), () {
            if (mounted) setState(() => _isProcessing = false);
          });
        }
      },
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}
```

**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.

### 5. 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 code example

File: `event_checkin_action.dart`

```dart
// FlutterFlow Custom Action: processCheckin
// Called when QR scanner detects a code
// Returns: { status: 'success'|'already_checked_in'|'not_found', guestName: String }

import 'package:cloud_firestore/cloud_firestore.dart';

Future<Map<String, dynamic>> processCheckin(
  String qrCode,
  String eventId,
) async {
  final db = FirebaseFirestore.instance;

  // Find the guest document by QR code and event ID
  final querySnap = await db
      .collection('event_guests')
      .where('qr_code', isEqualTo: qrCode)
      .where('event_id', isEqualTo: eventId)
      .limit(1)
      .get();

  if (querySnap.docs.isEmpty) {
    return {'status': 'not_found', 'guest_name': ''};
  }

  final doc = querySnap.docs.first;
  final data = doc.data();
  final currentStatus = data['status'] as String? ?? '';

  if (currentStatus == 'checked_in') {
    return {
      'status': 'already_checked_in',
      'guest_name': data['guest_name'] ?? 'Guest',
      'checked_in_at': data['checked_in_at'],
    };
  }

  if (currentStatus == 'declined') {
    return {
      'status': 'declined',
      'guest_name': data['guest_name'] ?? 'Guest',
    };
  }

  // Update status to checked_in
  await doc.reference.update({
    'status': 'checked_in',
    'checked_in_at': FieldValue.serverTimestamp(),
  });

  // Increment event checked_in counter
  await db.collection('events').doc(eventId).update({
    'checked_in_count': FieldValue.increment(1),
  });

  return {
    'status': 'success',
    'guest_name': data['guest_name'] ?? 'Guest',
    'guest_email': data['guest_email'] ?? '',
  };
}
```

## Common mistakes

- **Storing the guest list as an Array field on the event document** — Firestore documents have a 1MB size limit. An event with 5,000 guests, each with name, email, and status, easily exceeds this limit. Additionally, you cannot query guests by status across events when they are embedded in event documents. Fix: 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'.
- **Using a sequential integer as the QR code token** — Sequential integers (guest #1, #2, #3) are predictable. An attendee could modify the number in their QR code to check in as a different guest. For paid events, this is a security and revenue issue. Fix: Always use a UUID or cryptographically random token as the QR code value. UUIDs are unguessable and unique across your entire database.
- **Not handling the already-checked-in case in the scanner** — Without this check, scanning the same QR code twice marks the guest as checked in twice, which inflates attendance counts and can cause issues in capacity-limited events. Fix: 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.

## 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.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-an-event-planning-and-guest-management-app-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-an-event-planning-and-guest-management-app-in-flutterflow
