# How to Set Up an Event Ticketing System in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 40-55 min
- Compatibility: FlutterFlow Free+ (Cloud Functions and Stripe require Firebase Blaze plan)
- Last updated: March 2026

## TL;DR

Create an event ticketing system by building a Firestore-backed seat map Custom Widget with color-coded availability, connecting Stripe Checkout via a Cloud Function for payment, generating unique QR code tickets with qr_flutter, and scanning them at the door with mobile_scanner. A seat hold mechanism prevents double-booking during checkout.

## Building a Full Event Ticketing Flow with Seat Selection and QR Check-In

Event ticketing requires coordinating seat inventory, payment processing, ticket generation, and check-in validation. This tutorial walks through each piece using FlutterFlow's visual builder, Custom Widgets for the seat map and QR codes, Cloud Functions for payment and ticket generation, and mobile_scanner for staff check-in.

## Before you start

- A FlutterFlow project with Firestore and Firebase Authentication configured
- Firebase Blaze plan for Cloud Functions deployment
- A Stripe account with API keys stored in Cloud Function environment config
- Basic understanding of Firestore collections and document structure

## Step-by-step guide

### 1. Create the Firestore data model for events, ticket types, and tickets

Create an events collection with fields: title (String), date (Timestamp), venue (String), description (String), imageUrl (String), and a ticketTypes array of maps, each containing name (String like General Admission or VIP), price (Double), totalSeats (Int), availableSeats (Int). Create a tickets collection with fields: eventId (Reference), userId (Reference), ticketType (String), seatNumber (String like A12), qrCodeData (String, unique UUID), status (String enum: held/purchased/used/cancelled), heldUntil (Timestamp, null after purchase). Add a Firestore composite index on tickets for eventId + status to support seat availability queries.

**Expected result:** Firestore has events and tickets collections with the proper schema, and seat availability can be queried efficiently by event and status.

### 2. Build the venue seat map Custom Widget with color-coded seats

Create a Custom Widget named VenueSeatMap with parameters: eventId (String), rows (Int), seatsPerRow (Int), and an Action Parameter callback onSeatsSelected that returns the selected seat list. Inside the widget, query all tickets for the event. Build a GridView where each cell is a GestureDetector Container representing a seat. Color code: green for available, red for sold (status purchased or used), orange for held (status held and heldUntil is in the future), blue for user-selected. Tapping a green seat adds it to a local selectedSeats list and turns it blue. Tapping a blue seat deselects it. Display a legend Row at the top explaining the colors. Pass the selected seats list back via the callback when the user taps a Confirm Selection button below the grid.

**Expected result:** The seat map renders the venue layout with real-time color coding, and users can select multiple available seats that get passed to the parent page.

### 3. Implement a 10-minute seat hold to prevent double-booking during checkout

When the user confirms seat selection and taps Proceed to Checkout, run a Cloud Function holdSeats(eventId, seatNumbers, userId). The function uses a Firestore batch write: for each seat, check if a ticket doc already exists with status held or purchased. If not, create a ticket doc with status held, heldUntil set to now + 10 minutes, and the userId. If any seat is already taken, abort the entire batch and return an error. On success, navigate to the checkout page. A scheduled Cloud Function runs every 5 minutes to find tickets where status is held and heldUntil is in the past, then deletes them to release expired holds.

**Expected result:** Selected seats are temporarily reserved for 10 minutes during checkout, and expired holds are automatically released.

### 4. Process payment through Stripe Checkout and generate QR tickets

On the checkout page, show the selected seats, ticket type, and total price. On Pay Now button tap, call a Cloud Function createTicketCheckout(eventId, ticketDocIds, totalAmount). The function creates a Stripe Checkout Session with the line items and a success_url pointing back to your app. Return the Checkout URL and open it in a WebView or browser. On Stripe webhook checkout.session.completed, a separate Cloud Function updates each held ticket to status purchased, generates a unique UUID for qrCodeData, decrements the event's availableSeats, and sends a confirmation email with ticket details.

**Expected result:** Payment completes through Stripe, ticket statuses update to purchased, each ticket gets a unique QR code identifier, and the user receives confirmation.

### 5. Display tickets with QR codes and build the staff check-in scanner

Create a My Tickets page querying tickets where userId equals current user and status is purchased. Display each ticket in a Card with event name, date, seat number, and a QR code rendered by a Custom Widget using qr_flutter: QrImageView(data: ticket.qrCodeData, size: 200). For staff check-in, create a separate Check-In page with a Custom Widget using mobile_scanner. When a QR is scanned, call a Cloud Function validateTicket(qrCodeData). The function finds the ticket by qrCodeData, checks status is purchased (not already used), updates status to used, and returns the ticket details. Display a green checkmark with seat info on valid scan, or a red X for invalid or already-used tickets.

**Expected result:** Attendees see their tickets with scannable QR codes, and staff can scan tickets at the venue entrance with instant validation feedback.

## Complete code example

File: `FlutterFlow Event Ticketing System Setup`

```text
FIRESTORE DATA MODEL:
  events/{eventId}
    title: String
    date: Timestamp
    venue: String
    description: String
    imageUrl: String
    ticketTypes: Array of Maps
      [{ name: 'General', price: 25.00, totalSeats: 200, availableSeats: 147 },
       { name: 'VIP', price: 75.00, totalSeats: 50, availableSeats: 32 }]

  tickets/{ticketId}
    eventId: Reference (events)
    userId: Reference (users)
    ticketType: String
    seatNumber: String (e.g., 'A12')
    qrCodeData: String (UUID)
    status: String (held | purchased | used | cancelled)
    heldUntil: Timestamp (null after purchase)
    purchasedAt: Timestamp

PAGE: EventDetailPage
  Backend Query: single event doc
  Column
    Image (event imageUrl)
    Text (title, titleLarge)
    Row: Icon calendar + Text (formatted date) | Icon location + Text (venue)
    Text (description, bodyMedium)
    VenueSeatMap Custom Widget
      params: eventId, rows: 10, seatsPerRow: 20
      callback: onSeatsSelected → update Page State selectedSeats
    Button "Proceed to Checkout" (enabled when selectedSeats not empty)
      On Tap → Cloud Function holdSeats → Navigate CheckoutPage

PAGE: CheckoutPage
  ListView (selected seats with ticket type + price per seat)
  Divider
  Row: Text "Total" + Text (formatted total)
  Button "Pay Now"
    On Tap → Cloud Function createTicketCheckout → Open Stripe URL

PAGE: MyTicketsPage
  Backend Query: tickets where userId == currentUser, status == purchased
  ListView
    Card (padding 16)
      Text (event title, titleMedium)
      Row: Text (date) + Text (seat number)
      Center: QrImageView Custom Widget (data: qrCodeData, size: 200)

PAGE: StaffCheckInPage
  Custom Widget: mobile_scanner
    onScan → Cloud Function validateTicket(qrCodeData)
    Success → green Container with checkmark + ticket details
    Failure → red Container with X + error message

SEAT MAP LEGEND:
  Row (mainAxisAlignment: spaceEvenly)
    Container(green, 16x16) + Text "Available"
    Container(red, 16x16) + Text "Sold"
    Container(orange, 16x16) + Text "Held"
    Container(blue, 16x16) + Text "Selected"
```

## Common mistakes

- **Not locking selected seats during checkout with a hold timer** — A user selects seats, takes several minutes to complete payment, and another user purchases the same seats in the meantime, causing a double-booking conflict. Fix: Implement a 10-minute seat hold: mark seats as held with an expiry timestamp when checkout begins. A scheduled Cloud Function releases expired holds automatically.
- **Generating QR codes with predictable sequential data instead of UUIDs** — Sequential ticket numbers like TICKET-001 are easy to guess, allowing someone to forge QR codes for fake tickets. Fix: Generate a random UUID (v4) for each ticket's qrCodeData. The Cloud Function validates the exact UUID against Firestore on scan.
- **Decrementing availableSeats on hold instead of on purchase** — If a user abandons checkout, the held seats show as unavailable even after the hold expires, creating phantom sold-out events. Fix: Only decrement availableSeats in the Stripe webhook handler after confirmed payment. Release holds restore availability automatically.

## Best practices

- Use Firestore batch writes for seat holds to ensure all-or-nothing reservation of multiple seats
- Store the Stripe Checkout Session ID on ticket docs to reconcile payments with tickets
- Run a scheduled Cloud Function every 5 minutes to clean up expired seat holds
- Add Firestore Security Rules preventing users from modifying ticket status directly — only Cloud Functions should change status
- Include event name, date, and seat number in the QR code display so attendees can verify details without scanning
- Cache the seat map query result in Page State and refresh on navigation return to avoid excessive Firestore reads
- Show a countdown timer on the checkout page so users know how long their seats are held

## Frequently asked questions

### How do I prevent the same seat from being sold to two people?

Use a Cloud Function with Firestore batch writes to create held ticket documents atomically. The batch checks if a ticket already exists for each seat before writing. If any seat is taken, the entire batch aborts. A separate scheduled function releases expired holds.

### Can I support general admission tickets without assigned seats?

Yes. Skip the seat map widget entirely and use a quantity selector instead. Each ticket gets a unique QR code but no seatNumber. Decrement the availableSeats count atomically in the Cloud Function on purchase.

### How do I handle refunds and ticket cancellations?

Create a Cloud Function that calls Stripe's refund API, updates the ticket status to cancelled, and increments the event's availableSeats. Trigger it from an admin panel or a user-facing Cancel Ticket button.

### Can I email the QR code ticket to the attendee?

Yes. In the Stripe webhook Cloud Function, after generating the QR code data, use a mail service like SendGrid to send an email containing the ticket details and a QR code image generated server-side using a Node.js QR library.

### What if a ticket QR code is shared or screenshotted?

The validateTicket Cloud Function marks the ticket as used on first scan. Any subsequent scan of the same QR code returns an already-used error. This prevents duplicate entry from shared screenshots.

### What if I need help building a complex ticketing system with multiple venues?

RapidDev has built event ticketing platforms with dynamic venue maps, multi-tier pricing, waitlists, and real-time capacity management across 600+ FlutterFlow projects. They can handle the full build.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-set-up-an-event-ticketing-system-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-set-up-an-event-ticketing-system-in-flutterflow
