Event registration

Event registration7 min read

Volunteer signup form with shifts, for events and festivals

A volunteer signup form where people pick shifts, with a phone number for the day, and a script that shows which shifts are short and builds the rota.

Preet SutharFounder, Ikiform

Let volunteers tick every shift they can do on one form, with a phone number, a T-shirt size and an emergency contact, and post it to Ikiform. Ikiform stores each signup and emails the coordinators, but it doesn't fill or close shifts, so run a short script that counts volunteers per shift against what you need and writes a rota. Fill short shifts by asking the spare volunteers directly.

What's different about volunteers

An RSVP asks one question: are you coming? A volunteer signup asks when, and the answer only matters against what each shift needs. Setup at 7 a.m. needs six people and gets two; the sunny afternoon needs ten and gets nineteen. The job isn't counting heads, it's coverage.

  • Availability, not a single choice. People can do several shifts. Ask for every shift they could do and assign from there, instead of making them guess what's needed.
  • Contact on the day. Email is for planning. On the day you need a mobile number, and an emergency contact for each volunteer.
  • Things to order. T-shirts, lanyards and lunches, by size and count.
  • Age. Many events need an adult, or a guardian's agreement, for each volunteer. Ask your organisation what applies; a checkbox records the answer but isn't a legal check.

Volunteer platforms such as SignUpGenius run slot-based signups that close a slot when it's full. If hard per-slot limits are what you need, use one. Ikiform suits organisers who want the form on their own site and are happy to assign shifts by hand from a clean list.

Turning volunteer availability into a rota with the gaps showingsignup formevery shift theycan doIkiformstores, emailscoordinatorsrota scriptvolunteers pershift vs neededrota.csvnames and phonesper shiftsetup: 2 of 6ask the spareonespostAPIshortTurning volunteer availability into a rota with the gaps showingsignup formevery shift they candoIkiformstores, emailscoordinatorsrota scriptvolunteers per shiftvs neededrota.csvnames and phones pershiftsetup: 2of 6ask thespare onespostAPIshort
  1. Volunteers tick every shift they could do on the signup form, which posts to Ikiform.
  2. Ikiform stores each signup and emails the coordinators.
  3. A script reads the signups through the API and counts volunteers per shift against what each shift needs.
  4. It writes rota.csv with names and phone numbers for each shift.
  5. Shifts that are short show up straight away, such as setup with 2 of 6, so you can ask volunteers who are spare elsewhere.
Ikiform holds the availability. The script turns it into coverage, and a coordinator makes the final calls.

The form

Group shifts by day so people can scan them. The checkboxes are named shifts[], so even a single tick is stored as a list. It works as plain HTML on any site or builder that accepts an embed:

volunteer.html
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <label>Name <input name="name" autocomplete="name" required></label>
  <label>Email <input name="email" type="email" autocomplete="email" required></label>
  <label>Mobile number for the day <input name="phone" type="tel" autocomplete="tel" placeholder="+44 7700 900123" required></label>

  <fieldset>
    <legend>Shifts you can do</legend>
    <p>Saturday</p>
    <label><input type="checkbox" name="shifts[]" value="sat_setup"> 7:00 to 10:00, setup</label>
    <label><input type="checkbox" name="shifts[]" value="sat_day"> 10:00 to 14:00</label>
    <label><input type="checkbox" name="shifts[]" value="sat_late"> 14:00 to 18:00</label>
    <p>Sunday</p>
    <label><input type="checkbox" name="shifts[]" value="sun_day"> 10:00 to 14:00</label>
    <label><input type="checkbox" name="shifts[]" value="sun_pack"> 14:00 to 17:00, pack-down</label>
  </fieldset>

  <label>Where you'd like to help
    <select name="role">
      <option value="anywhere">Anywhere</option>
      <option value="welcome">Welcome desk</option>
      <option value="food">Food stall</option>
      <option value="stewarding">Stewarding</option>
    </select>
  </label>
  <label>T-shirt size
    <select name="tshirt" required>
      <option value="s">S</option><option value="m" selected>M</option><option value="l">L</option>
      <option value="xl">XL</option><option value="xxl">XXL</option>
    </select>
  </label>
  <label>Emergency contact name and number <input name="emergency_contact" required maxlength="160"></label>
  <label><input type="checkbox" name="adult" required> I'm 18 or over, or a parent or guardian is filling this in with me</label>

  <input type="hidden" name="_redirect" value="https://fair.example/volunteers/thanks">
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden>
  <button type="submit">Sign me up</button>
</form>

Paste this definition on the form's Fields tab. The server then refuses a signup with no shifts, a phone number that isn't one, or an unticked age box, whatever page it came from:

Preview

Runs Ikiform's validator in your browser. Nothing is sent.

We text shift changes. Include your country code.

Shifts you can do
shifts is a multi_select with min_items: 1. phone is a phone field, which checks the number's shape. adult must be ticked.
FieldWhy it's here
phoneTexting shift changes and finding people on the day
shiftsAvailability you assign from
rolePreferences, so the welcome desk gets the people who asked for it
tshirtThe order you place two weeks out
emergency_contactFor the first-aid team, not for marketing
adultYour organisation's age rule, recorded on a submission that keeps its own created_at
Phone numbers and emergency contacts are personal data about people who aren't signing up themselves. Keep the export in a private place, share the rota only with shift leads, and delete the submissions after the event.

Build the rota and see the gaps

Run this with Node and a secret API key whenever you want the picture. It takes each volunteer's newest signup, prints each shift as filled, short or spare, and writes rota.csv with names and numbers for each shift:

rota.ts
import { writeFileSync } from "node:fs";
import { createClient } from "@ikiform/sdk";

const NEEDED: Record<string, number> = { sat_setup: 6, sat_day: 10, sat_late: 10, sun_day: 8, sun_pack: 6 };
const ikiform = createClient({ apiKey: process.env.IKIFORM_SECRET_KEY! });
const people = new Map<string, Record<string, unknown>>();

for await (const submission of ikiform.submissions.iterate({ formId: "frm_xxxxxxxxxxxxxxxx", status: "received", limit: 100 })) {
  const email = String(submission.fields.email ?? "").trim().toLowerCase();
  if (email && !people.has(email)) people.set(email, submission.fields);
}

const cell = (value: unknown) => `"${String(value ?? "").replaceAll('"', '""')}"`;
const rows = [["shift", "name", "phone", "role", "tshirt"].join(",")];

for (const [shift, needed] of Object.entries(NEEDED)) {
  const team = [...people.values()].filter((fields) => (fields.shifts as string[] | undefined)?.includes(shift));
  const state = team.length < needed ? `SHORT ${needed - team.length}` : team.length > needed ? "spare" : "ok";
  console.log(`${shift.padEnd(10)} ${team.length}/${needed} ${state}`);
  for (const fields of team) {
    rows.push([shift, fields.name, fields.phone, fields.role, fields.tshirt].map(cell).join(","));
  }
}

writeFileSync("rota.csv", `${rows.join("\n")}\n`);
  • Lists come newest first, so a volunteer who signs up again with changed availability is counted by their latest answer.
  • A person appears under every shift they ticked. That's availability. Cut it down to assignments in the CSV, and tell each person their final shifts.
  • Commas and quotes in names are escaped, so the CSV opens cleanly in any spreadsheet.

For a quick look without code, export the CSV from Submissions and filter the shifts column for a shift's value, such as sat_setup. Multi-select values in the export are joined in one cell.

From availability to assignments

  1. Two weeks out, run the rota and order T-shirts from the tshirt counts.
  2. For each short shift, message the volunteers who are spare and also ticked that shift. They already said they could.
  3. Send each volunteer their final shifts from your email or messaging tool. The notification email's Reply goes straight to them.
  4. The day before, print the rota for shift leads, with phone numbers.

Ikiform doesn't email the person who fills in the form. Notification emails go to you and your team only, so there is no autoresponder, confirmation email or double opt-in. Send those from your email tool, triggered by a webhook. Volunteers forget what they signed up for, so send the final rota personally a week before, and again the evening before.

To hear about each signup where your team already talks, send the webhook to Slack or Discord. Coordinators then see a short shift fill up as it happens.

Check it before you recruit

  1. Under the form's Allowed domains, add your site's domain, plus localhost while you develop. Leave it empty to accept posts from anywhere.
  2. Sign up with one shift ticked. In the dashboard, shifts is a list with one value.
  3. Try to sign up with no shifts, or with the age box unticked. The browser stops you, and a crafted post gets 422.
  4. Check the inbox of each notification recipient. Each gets an email with the fields, and Reply goes to the submitter when the form has an email field.
  5. Run the rota script. Your test shows under its shift, and every other shift shows as short.
  6. Open rota.csv in a spreadsheet and check the phone number column.

Gotchas

ProblemWhat to do
A single shift is stored as text, not a listThe checkboxes are named shifts without [], and no field is defined. Rename them to shifts[], or define shifts as a multi_select.
422 on phoneThe number doesn't look like a phone number. Ask for the country code in the help text, as the definition does.
A volunteer appears twice in the rotaThey signed up with two different emails. Merge them by hand; the script keys on email.
422 validation_failedThe post doesn't match the form's field definition. error.fields names each field and the reason. Match the input name to the field id.
429 rate_limitedOne visitor sent more than the form's rate limit allows. Standard allows 5 a minute per visitor. Wait for Retry-After, or choose Relaxed or Custom for forms that post from one shared IP.

Questions

Stuck on a step, or want a guide for your situation? Email hello@ikiform.com.