# Event registration form with payment and attendee lists

Canonical: https://useformwork.com/use-cases/event-registration

Updated: 2026-10-09

An event registration form that books several attendees at once, prices each place from a data table and takes payment through Stripe Checkout.

An event registration form has to do more than collect names. An employer books four staff onto a course, pays by card and expects joining instructions; your team needs to know who is coming, who needs a step-free room and when a session is full. This is a configuration pattern for Tideway Learning, a fictional training provider, not a case study, and the prices are sample values.

## The job, and what goes wrong without a system

Without one, bookings arrive by email, payment links go out by hand, and the attendee list is a spreadsheet retyped the week before. Places get double-counted and dietary needs get lost. The aim is one record per booking: who is coming, what they paid, its stage, and a list you can export for the day.

## Keep sessions and prices in a data table

Create a **Sessions** [data table](/docs/data-tables/overview) with columns for Session (Text), Date (Date), Venue (Text), Standard price and Concession price (Currency), Places (Number), Booked (Number) and Open for booking (Yes or no).

The form reads prices from the table, so changing a row changes what new bookings see without publishing. FormWork has no early-bird rule based on today's date; to end an early-bird price, change it in the table on the day.

## Build the event registration form

1. **Session.** A **Dropdown** with its options from the Sessions table, using **Only show rows where** Open for booking is `true`. See [options from a data table](/docs/data-tables/options-source). Read-only fields use **Look up from a dropdown** to show the date, venue and both prices from the chosen row.
2. **Attendees.** A [repeatable group](/product/nested-data) with **Min items** 1 and **Max items** 10. Each attendee has a name, an email address, a **Ticket type** (Standard or Concession) and a **Dropdown** for dietary needs. A read-only **Ticket price** [calculation](/docs/logic/calculations) uses **Current instance** to pick the concession or standard price for that attendee. A read-only **Places** field counts the attendees.
3. **Booker and payment.** The booker's name, email and organisation; "Does anyone need adjustments or have dietary needs we should discuss?" (**Yes or no**), with a **Long text** box shown only on Yes; a booking terms box set to **Must be on**; then the **Payment** field.

Put the Payment field after everything that affects the price.

## Take payment with Stripe Checkout

Install the [Stripe extension](/docs/extensions/stripe) with test keys first, then add a **Payment** field:

- **Line items**: **Reference source**, with Attendees as the **Line item source**, the attendee's name as the **Line item name reference** and Ticket price as the **Line item price reference**. Stripe's Checkout page lists one line per attendee.
- **Currency** `gbp` and **Amount Unit** `major`, because the prices are decimal amounts such as 180.00.
- **Customer Email Field**: the booker's email.

At submission, FormWork asks Stripe about the payment and only accepts the booking if it succeeded, belongs to this entry and still matches the current total and currency. Points that matter for events:

- Every line needs a price above zero. A £0 line leaves the field waiting for a price, so handle free or complimentary places on a separate form without a Payment field.
- Leave **Allow Promotion Codes** off. A discounted payment no longer matches the form's total, so the check before submission rejects it. Put discounts in the prices instead.
- If someone adds an attendee after paying, the field asks them to pay again. The first payment isn't refunded automatically, so look for duplicates in Stripe.
- FormWork has no refund step. Refund in the Stripe dashboard and record the reference in a [team field](/docs/entries/team-fields).
- Set up the Stripe [webhook](/docs/extensions/stripe#webhooks) so a payment is saved even if the booker closes the page. That booking stays **In progress** until they submit, so filter on **Status** and the Payment answer to catch paid but unsubmitted entries.

## Capacity: what FormWork can and can't enforce

FormWork has no per-form or per-session entry limit and no closing date. A published form takes bookings until you change it, and nothing blocks a submission because a session is full. What you can do:

- **Close a session by hand.** Untick Open for booking and the session leaves the dropdown. Someone already part-way through keeps their choice.
- **Keep a running count.** An automation adds each booking's places to the session's Booked column and flags bookings that go over (below). This is a count, not a lock: two bookings submitted at the same moment can both get in, so keep the Over capacity stage for that case.
- **Limit group size** with **Max items** on Attendees.

## Set up the booking process

On the form's **Process** page, set up these [stages](/product/process):

| Stage | Kind | Owner and due time | Actions |
| --- | --- | --- | --- |
| New booking | Open | The Events team group, due in 1 day | **Confirm booking**, **Over capacity** (comment required) |
| Confirmed | Open | Nobody | **Mark attended**, **Cancel booking** (comment required, with the confirmation question "Cancel this booking? Refund it in Stripe first.") |
| Over capacity | Waiting | Keep the current owner, due in 2 days | **Moved to another date** (comment required), **Refund and cancel** (comment required) |
| Attended, Cancelled | Closed | None | |

Bookings that need a conversation wait for the Events team in [My work](/docs/process/my-work). Due times count calendar time, not working hours. Stages belong to the whole booking, so attendance is recorded per booking, not per attendee.

## Automations for counting, confirming and reminding

- **Count places and route**, on **Form is submitted**: **Calculate** adds Places to the Booked value of the session's selected row, and **Update entry or row** saves it on that row. An **If** checks the result. Over the session's Places: **Move to stage** Over capacity and **Send email** to the Events team. No adjustments needed: **Move to stage** Confirmed. Otherwise the booking stays in New booking. A second **If** sets Open for booking to No once the session is full.
- **Release places**, on **Stage changes** to Cancelled: the same steps in reverse, so the count stays honest.
- **Joining instructions**, on **Stage changes** to Confirmed: **For each** attendee, **Send email** to their address, then **Update entry or row** sets the Joining instructions team field.
- **Course reminder**, on **Before or after a date answer**: 2 days before the read-only session date, at 09:00, with **Only run if** the stage is Confirmed. A booking made after that time doesn't get one.

For the booker's receipt, turn on **Email the person who filled it in** in [notifications](/docs/forms/notifications). Its copy of the answers numbers the attendees. If you turn on [respondent status](/docs/process/respondent-status), use neutral labels such as "Booking received", "Confirmed", "We'll be in touch about your date" and "Cancelled".

A failed email never undoes a booking, so check [automation runs](/docs/automations/runs) and turn on the admin email for failed runs.

## Export attendee lists

For a register or a caterer's list, filter the entries table to Confirmed and one session, then select **Export entries**. Tick **One row per repeater item** to give each attendee their own row, with the booker's organisation repeated on each, and untick columns the recipient doesn't need. See [import and export](/docs/entries/import-export).

## Access and sensitive details

Everyone in the account can open every booking, including dietary and access needs, whichever group owns it. Ask only what you need, check exports before sharing them, and review [team members and roles](/docs/platform/users-permissions).

## Test these cases before you publish

- One attendee, ten attendees, and an attempt to add an eleventh.
- Mixed Standard and Concession tickets, checked against Stripe's total.
- Paying, then adding an attendee; cancelling on Checkout; closing the tab after paying.
- A session closed while someone is mid-booking, and two bookings for the last places.
- Adjustments answered Yes and No, and a cancellation that releases places.
- A booking made after its reminder time, and an export with one row per attendee.

## Change it safely

Publish question, process and automation changes as a [new form version](/product/versioned-forms); bookings keep the questions they were answered with. Data table edits, such as prices and Open for booking, apply at once without publishing, so test them too. Switch the Payment field to the live Stripe environment only after an end-to-end test.

## What success looks like

Each booking is paid and checked at submission, sits in the right stage, has joining instructions sent and puts every attendee on the session's export. Cancelled bookings carry a refund reference. Set your own targets; this page claims no measured results.

See the [Stripe guide](/docs/extensions/stripe), compare [pricing](/pricing), or [talk through your events](/#contact).