FIFA World Cup 2026 Media Reservation Platform — Built with Kiro by a Working Media Manager
As Media Manager for FIFA World Cup 2026 in Philadelphia, I built a full-stack platform to coordinate 1,500+ credentialed media across 104 matches and 39 event days — solo, using Kiro. It handles personalized token-based access, a 48-hour lockout system, real-time capacity tracking, email notifications, and printable security manifests. Kiro's Spec workflow and Autopilot mode let a non-full-time engineer ship a production system running live at the World Cup.
FIFA Fan Festival Philadelphia — Media Reservation Platform
Built with Kiro | World Cup 2026 | Philadelphia
What This Is
I'm the Media Manager for the FIFA World Cup 2026 in Philadelphia. My job is to coordinate over 1,500 credentialed media professionals — photographers, journalists, broadcasters, radio hosts, and podcasters — across 39 event days and 104 matches running from June 11 to July 19, 2026.
Managing this at scale, in a chaotic, constantly changing event logistics environment, required a custom platform. Every day brings late arrivals, outlet changes, last-minute approvals, and position conflicts. A spreadsheet wasn't going to cut it.
So I built one — with Kiro.
The Media Reservation Platform is a full-stack web application that handles every aspect of media position management: from building the match schedule to issuing personalized access links, from accepting reservation requests to printing security manifests. It replaces what would otherwise be hundreds of individual emails, calls, and spreadsheet rows.
The Scale of the Problem
- 104 matches across the full tournament
- 39 operating days at the Fan Festival venue
- 4 per-match position types per match, plus daily positions and parking
- 1,500+ credentialed individuals and outlets needing access
- A communications team that must evaluate and action every request within a rolling 48-hour window before each match
- Hard security constraints: approved lists printed and handed to venue staff at every position
Building the Schedule First
Everything starts with the schedule. Before any reservation can be accepted, the platform needs to know when each match happens, where, and in what time window.
The schedule is built entirely through the Admin panel:
- 39 Event Days are created, each covering a full ET calendar day from midnight to 23:59:59 ET, stored internally as UTC.
- Each day has up to 5 Time Periods (Morning, Midday, Afternoon, Evening, Late) with explicit start/end times.
- Each match (up to 104 matches) is placed into a time period, with home team, away team, venue, and kickoff time.
Once a match is in the system, the platform automatically creates the four reservable per-match positions for it:
| Code | Display Name | Soft Cap |
|---|---|---|
platform_a | Crowd View — Platform A | 7 |
platform_b | Crowd View — Platform B | 7 |
platform_c | Photography Platform | 8 |
platform_d | Media Tower (Match) | 15 |
Parking passes, Radio & Broadcast Tents, and Media Tower non-match bookings are available on all 39 event days. On the 6 local Philadelphia match days, additional stadium broadcast positions also open up:
| Surface | Type | Cap |
|---|---|---|
| Parking Pass | Per ET date | 20/day (hard cap) |
| Stadium Broadcast — Camera & Reporter | Per ET date | 10/day (hard cap) |
| Stadium Broadcast — Roaming Reporter | Per ET date | 15/day (hard cap) |
| Radio & Broadcast Tent (3 physical tents) | Time-slotted | 3-hour max per slot |
| Media Tower Non-Match | Time-slotted | Conflict advisory |
The 6 local Philadelphia match dates are fixed in the system — these are the days when stadium broadcast positions open in addition to the standard daily surfaces.
Personalized Access — No Login Required
This was a deliberate design decision. Asking 1,500 media professionals to create accounts, remember passwords, and manage credentials would add friction and create a support burden. Instead, every credentialed person gets a unique, personalized portal link — nothing else required.
How the links work
Each link looks like:
1
https://[platform]/portal/eyJhdHRlbmRlZUlkIjoiYWJj...The token embedded in the URL is an HMAC-SHA256 signed payload containing:
- The attendee's unique ID
- An issued-at timestamp
- An expiry timestamp (30-day default)
The signature is computed server-side using a secret key (
PORTAL_TOKEN_SECRET) that never touches the client. When someone opens their link, the server:- Splits the token at the
.separator - Recomputes the expected HMAC signature
- Compares using
crypto.timingSafeEqual— constant-time comparison that prevents timing attacks, where an attacker could otherwise measure response time differences to infer parts of a valid token - Decodes and validates the payload shape and expiry
- Returns a generic HTTP 404 for any failure — no information leakage about whether a token ever existed
Tokens are stored in the attendee record so the URL can be reconstructed and re-sent if needed. If a new token is issued, the old one is invalidated.
Outlet organizers
For larger media organizations, a single outlet organizer link allows one person to make reservation requests on behalf of their entire team — selecting positions and matches across the full schedule. Smaller outlets and individual credentialed media use their personal link directly.
Rate limiting protects the link-request endpoint: 5 requests per 15 minutes per IP, preventing both link enumeration and attendee spam.
The User Portal
The portal is designed to work equally well on mobile and desktop — because media personnel are often checking it from a phone at the venue.
When a user opens their link, the platform automatically routes them to the correct day view:
- If today falls within the June 11–July 19 range → opens to today
- Before the event starts → opens to the first festival day
- After the event ends → opens to the last day
What the portal shows
The day view lays out every reservable surface for that date:
- Per-match sections: Each match on that day, with the four platform positions listed. Users see current availability, whether the window is open, and the status of any existing request (Pending / Approved / Denied / Cancelled).
- Daily sections: Parking, Broadcast Tents, and Media Tower non-match slots are available every day. On the 6 local Philadelphia match days, stadium broadcast positions also appear.
For each position, the user can:
- Submit a request (when the reservation window is open)
- Cancel an existing request (when the window is still open)
Cancellation behavior
Cancelling a request removes it from the active queue, but it does not disappear from the admin view. The admin can see it was cancelled, when it was cancelled, and by whom. This is intentional — the communications team needs a full picture of outlet intent, not just the current state.
The 48-Hour Lockout Window
All reservation requests automatically lock out 48 hours before the relevant match or day. This is the window the communications team uses to evaluate and act on every pending request without the queue changing underneath them.
How it works
For matches:
Window opens at
Window opens at
match_kickoff - 48 hours, closes at kickoff.For daily surfaces (parking, broadcast tents, stadium positions):
Window opens at 10:00 AM ET, two days before the requested date, and closes at 23:59:59 ET on that date.
Window opens at 10:00 AM ET, two days before the requested date, and closes at 23:59:59 ET on that date.
Once the window opens, attendees receive an error if they attempt to submit or cancel — with a tooltip showing exactly when the window opened and the contact email for late requests (
MediaCredentials@philadelphia2026.com).Admins are never blocked by the lockout. They can approve, deny, create, and modify reservations at any time.
After the window closes, the admin team works through every pending request — approving what fits, denying what doesn't, and entering a reason for any denial (e.g., "Not enough space", "Only one outlet reservation per match", "Photography media is not allowed in broadcast positions").
Transactional Emails via Emailit
Every status change sends an automatic email to the attendee. The platform uses Emailit (
https://api.emailit.com/v2/emails) for all transactional delivery.Email flow
| Trigger | Email sent |
|---|---|
| Request submitted | Pending — your request has been received |
| Admin approves | Approved — with position details |
| Admin denies | Denied — with the admin's reason |
| Request cancelled | Cancellation confirmation |
| Portal link (re)issued | Magic link email |
The transport layer implements exponential backoff retry: up to 3 attempts at 500ms → 1s → 2s intervals, retrying on server errors (5xx) and network failures only — not on client errors (4xx). Emails are dispatched non-blocking after every database commit, so they never slow down the HTTP response. On terminal failure after all retries, an
email.failed entry is written to the audit log.The sender displays as
Fan Festival Media Credentials <[configured address]> — the same identity the communications team uses for all media correspondence.The Admin Dashboard
The admin interface is a two-column shell — a fixed white sidebar on the left (inspired by Calendly's layout), and a scrollable main content area on the right. It requires authentication via Appwrite's Users service; unauthenticated requests to any
/admin/* route are immediately redirected to the login page at the edge (before any server component renders).Sidebar navigation
1
2
3
4
5
6
7
8
9
10
11
├── Schedule ← Build and edit event days, time periods, matches
├── Assignments ← Direct platform assignments (admin-created)
├── Requests ← Review queue for all pending spot requests
│ ├── Platforms ← Per-match surface requests
│ ├── Media Tower ← Non-match tower bookings
│ ├── Tents ← Radio & Broadcast Tent slots
│ ├── Stadium Positions ← Camera & reporter positions (match days only)
│ └── Parking Passes ← Daily parking requests
├── Attendees ← Full credentialed media roster + portal link management
├── Manifests ← PDF generation per day
└── Reports ← Usage analyticsThe Day Picker (Schedule view)
The schedule view shows all 39 event days. For each day, the admin can see at a glance:
- How many requests are Pending
- How many are Approved
- How many are Denied
Clicking into a day shows every match and every day-based surface. Each position displays a live tally — how many spots have been filled versus the total capacity, so the admin always knows exactly how much room is left before approving the next request.
Approving and denying
The admin can approve or deny individual requests from the day view or the surface-specific review queues. Denials include a free-text reason field that flows directly into the email sent to the attendee.
Post-lockout manual additions
Even after the 48-hour window closes, the admin retains full ability to:
- Create new reservations manually (for late requests submitted via email)
- Approve or deny requests that came in before lockout
- Modify existing assignments
This accommodates the reality of live event logistics — sometimes a late accreditation arrives or a position opens up unexpectedly.
The Manifest — Printing for Security
At the end of the review period for each day, the admin can generate a PDF manifest — a clean, black-and-white printed list of every approved reservation for that day.
The manifest is designed specifically for printing and handing to security staff at each venue and position. Hard copies are essential when venue staff don't have device access or connectivity.
What the manifest contains
- Header: ET date, generating admin's name, and a UTC-to-ET formatted generation timestamp
- Per-match sections: For each match on that day, a block showing all four platform positions, each listing the approved attendee's full name, organization, and any notes
- Daily sections (below per-match): Stadium broadcast positions (match days only), Parking Passes, Radio & Broadcast Tents (with time window), Media Tower non-match slots
- Capacity counts: Each position shows
N of CAPso staff know the total expected headcount - Footer on every page: "FIFA Fan Festival Philadelphia — Media Reservations — Manifest for [date] — CONFIDENTIAL"
Manifests are streamed as
application/pdf with Cache-Control: no-store — always freshly generated, never cached, so they reflect the latest approval state at the moment of printing.The Tech Stack
Core framework
- Next.js 16 (App Router) — server components, route handlers, edge middleware
- React 19 — UI rendering
- TypeScript 6 — end-to-end type safety
Styling
- Tailwind CSS 4 — utility-first styling
- DaisyUI 5 — component layer with a fully custom
mrptheme built on FIFA official colors (#193C9Cdark blue,#304FFEcountry blue,#EEFF42yellow) - Phosphor Icons — iconography throughout the admin and portal UIs
Backend / Database
- Appwrite (node-appwrite v26) — backend as a service, handling:
- Admin authentication (
Usersservice) - All application data (
TablesDBservice — the current non-deprecated API) - Atomic transactions (
withTransaction/batchWrite) for consistency on every mutation - 10 tables:
event_days,time_periods,games,platforms,attendees,assignments,spot_requests,reservations,tier_definitions,audit_log
- Admin authentication (
- Emailit — transactional email service, used for all status notifications. REST API v2 with exponential backoff retry and non-blocking dispatch.
PDF generation
- PDFKit (Node.js) — streamed PDF generation for daily manifests. Runs server-only (excluded from the client bundle via
serverExternalPackages).
Dates and time zones
- date-fns 4 + @date-fns/tz — all times stored as UTC, displayed in Eastern Time. Every lockout calculation, manifest timestamp, and tooltip message runs through a centralized
TimeZoneService.
Security
- HMAC-SHA256 portal tokens (
cryptomodule,timingSafeEqualcomparison) - CSRF double-submit cookie pattern (bootstrapped at the edge in
proxy.ts) - Rate limiting — sliding window, in-memory: 10/15min for admin login, 5/15min for portal link requests, 60/min for portal lookups, 20/min for reservation submissions
- Zod — input validation on all API routes
Testing and tooling
- Vitest 4 — unit tests + property-based tests with
fast-check - Playwright 1.59 — end-to-end tests (portal flows, admin dashboard, accessibility, PDF verification, viewport)
- axe-core — automated accessibility scanning in E2E suite
- Prettier + ESLint 10 (flat config) — code formatting and linting
- Netlify — deployment (
@netlify/plugin-nextjs)
Why Kiro Made This Possible
This platform was built by one person — me — as a working media manager, not a full-time engineer. Kiro let me move fast and build right:
- The Spec workflow helped me think through reservation rules, lockout logic, and cap enforcement before writing a single line — catching edge cases early rather than mid-tournament.
- Autopilot mode handled the repetitive scaffolding (route handlers, repo files, email templates) so I could focus on the business logic that actually matters.
- The steering files I built for this project (
nextjs-architecture.md,dependency-management.md) gave Kiro the context it needed to consistently use the right APIs (TablesDB, not Databases;proxy.ts, notmiddleware.ts) throughout a large, complex codebase. - When I needed to trace through the lockout logic or understand the interaction between per-match and daily surfaces, Kiro helped me reason through it in plain language before implementing it.
The result is a production platform that is running live — handling real requests from real media organizations for the World Cup right now.
Media Reservation Platform — FIFA Fan Festival Philadelphia 2026
World Cup Matches: June 11 – July 19, 2026 | 104 Matches | 39 Days | 1,500+ Credentialed Media
World Cup Matches: June 11 – July 19, 2026 | 104 Matches | 39 Days | 1,500+ Credentialed Media
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article