Events Management
Complete guide to creating literary events, workshops, and webinars in the Admin Console, configuring the Events Listing and Event Detail pages, and handling Reader Portal RSVP/registration with capacity control.
What This Guide Covers
Events Management lets you publish literary events — readings, workshops, panels, lectures, festivals, meetups, and webinars — and collect attendee registrations through the Reader Portal. This guide covers the full lifecycle: authoring an event in the Admin Console, how it reaches reader-facing pages, how RSVP/registration works (including capacity), and every field and option you can set.
Where events live (important): Events are a first-class entity in the Admin Console, stored in PostgreSQL via Prisma (the
eventsandevent_registrationstables). The Admin Console is the source of truth. Events are then synced one-way to Strapi so the Reader Portal can render them, and registrations are written back to Postgres. You author and manage events in the Admin Console, not directly in Strapi.
Key Concepts
| Concept | What It Means |
|---|---|
| Event | A Prisma record (events table) with title, type, dates, location, capacity, speakers, schedule, and registration settings. |
| Event Registration | A Prisma record (event_registrations table) capturing one attendee's submitted form data, deduplicated by email per event. |
| Events Listing Page | The reader-facing /events URL showing upcoming and past events. |
| Event Detail Page | The reader-facing /event/[slug] URL showing one event in full. |
| Registration Page | /event/[slug]/register — the standalone RSVP form. |
Capacity (rsvpCapacity) | Maximum confirmed registrations. Empty/null = unlimited. |
| Strapi Sync | One-way push of each event from Postgres to Strapi so the portal can read it. |
Who Should Use This
| Role | What You'll Do | Required Permission |
|---|---|---|
| Content Editors | Create, edit, and publish events | events:read, events:create, events:update |
| Operations / Publishing Staff | Manage event listings, capacity, archive past events | events:update, events:delete |
| Marketing Team | Set up event landing pages, registration forms, promote events | events:create, events:update |
| Admin / Super Admin | Full event management plus page template configuration | All events:* |
Permissions are enforced in the Admin Console. The Events area lives under Content Entities → Events, and the UI hides Create/Edit/Delete controls when you lack the matching events:* permission.
Before You Begin
- You have Admin Console access with at least
events:read(andevents:createto add events). - PostgreSQL/Prisma is running (events are stored there).
- Strapi is reachable and
STRAPI_API_TOKENis configured, so events sync for the Reader Portal to render. (Sync is non-fatal — an admin save never fails because of a Strapi outage, but the portal won't see the event until sync succeeds.) - You understand the basics of the Page Template System if you want to customize the event page layouts (optional — fallback layouts work out of the box).
How It Works (Behind the Scenes)
End-to-end, an event passes through three systems: the Admin Console (authoring + storage of truth), Strapi (read model for the portal), and the Reader Portal (rendering + registration capture).
The authoring path
- An editor creates or edits the event in Content Entities → Events in the Admin Console.
- On save, the event is written to the Postgres
eventstable (with audit fields:created_by_id,created_at,updated_at). - The Admin Console calls
syncEventToStrapito upsert the event into Strapi by slug, keeping Strapi's publication state in lockstep with the eventstatus. This is best-effort: failures log a warning and do not block the save.
The reader path
- The Reader Portal renders
/events(listing) and/event/[slug](detail) by reading events from Strapi. Layout is driven by the page template system, with built-in fallback sections so pages work even with zero template configuration. - A visitor opens
/event/[slug]/register(or clicks the RSVP/Register button on the detail page).
The registration path
- The form posts to the Reader Portal route
POST /api/events/[slug]/register, which is a thin server-side proxy (it avoids CORS) that forwards to the Admin Console's public endpointPOST /api/content-entities/events/public/[slug]/register. - The Admin Console endpoint runs the registration rules (below), creates an
event_registrationsrow, and atomically incrementsrsvpCounton the event.
Registration rules enforced by the backend
The public registration endpoint applies, in order:
- Rate limiting — 5 attempts per 15 minutes per IP (HTTP 429 if exceeded).
- Event must exist (404 otherwise).
- Event must be
publishedorfeatured—draft,cancelled, andarchivedreject with 400. - RSVP must be enabled (
rsvpEnabled = true), else 400. - Event must not have ended — past events reject with 400 ("This event has already occurred"). An event that has started but not yet ended (Live) is not treated as past; the detail-page RSVP form shows a Live state rather than the registration form.
- Capacity check — if
rsvpCapacityis set andrsvpCount >= rsvpCapacity, the response is HTTP 409 withstatus: "full". - Email required — extracted from the submitted form data; missing email = 400.
- Required-field validation — every field marked
requiredin the event's registration form schema must be present. - Duplicate guard — one registration per email per event (DB unique constraint
(event_id, email)); a repeat returns 409 ("already registered"). - Atomic write — the registration row and the
rsvpCountincrement happen in a single transaction; new registrations are created withstatus = confirmedandsource = reader_portalby default.
Confirmation email: The registration endpoint itself returns
{ success, status: "confirmed" }and does not send a confirmation email inline. Transactional/registration emails are governed centrally by the Notifications & Email System (event registry + templates). If you need attendees to receive a confirmation, configure it through the email system rather than expecting it from the registration call itself.
Concepts: Event Model, Registration & Capacity
Event lifecycle (status)
An event's status controls both reader visibility and whether it accepts registrations.
| Status | Reader Visibility | Accepts Registration |
|---|---|---|
draft | Hidden | No |
published | Visible on listing and detail | Yes (if RSVP enabled & upcoming) |
featured | Visible + "Featured" highlight | Yes (if RSVP enabled & upcoming) |
cancelled | Hidden from listing (direct URL may still resolve) | No |
archived | Hidden from listing | No |
Lifecycle (Live / Upcoming / Past) is computed automatically from the dates, independent of the editorial status above:
- Upcoming —
startDateis in the future. - Live — the event has started (
startDate≤ now) and has not yet ended (endDate≥ now). Live requires an End Date; without one, an event flips straight from upcoming to past at its start time and is never bucketed as live. - Past — the event has ended (
endDatein the past, orstartDatein the past when no end date is set).
Past events never accept new registrations regardless of editorial status. A Live event is treated as still-running, not past: its detail page shows a Live badge, and a live online event with an Online URL surfaces a Join now call-to-action (see Registration / RSVP flow below).
Registration / RSVP flow
The detail-page RSVP form resolves to one of several views: loading, not-open-yet / opens-soon (registration-open date in the future), external (a Registration URL is set), full, past, live (happening now — online events show a Join now link), form, and success. A live online event surfaces a Join link even when RSVP is disabled, as long as an Online URL is present.
Capacity and waitlist
- Capacity is the
rsvpCapacityinteger. Leave it empty/null for unlimited registrations. rsvpCountis the live confirmed count, auto-incremented on each successful registration.- When
rsvpCountreachesrsvpCapacity, new registrations are rejected withstatus: "full". - Waitlist: the data model supports a waitlisted state —
EventRegistrationStatusis an enum ofconfirmed | cancelled | waitlisted, so registration records can be markedwaitlisted. However, the public reader-facing endpoint currently creates registrations asconfirmedand rejects once full rather than auto-waitlisting. Treatwaitlistedas an administratively settable status (manual waitlist management) rather than an automatic overflow queue.
Registration form schema
Each event has a registrationFields JSON array describing the form readers fill in. Each field is a FormField (built with the Admin Console's Form Field Builder) with id, type, label, placeholder, required, and order. If you enable RSVP without customizing the form, the default fields are:
| Field ID | Type | Label | Required | Order |
|---|---|---|---|---|
name | text | Full Name | Yes | 1 |
email | Email Address | Yes | 2 | |
phone | phone | Phone Number | No | 3 |
The name, email, and phone values are denormalized onto the registration row (for indexed queries and dedup); all submitted values are also stored verbatim in formData.
Configuration Reference
All event fields are configured in the Admin Console → Content Entities → Events (Create New Event, or open an existing event). The portal reads the synced copy from Strapi. The table below is the authoritative field list (backed by the Prisma Event model).
Event content & classification
| Field / Option | What it does | Type | Mandatory? | Default | Where configured | Role |
|---|---|---|---|---|---|---|
| Title | Event name shown everywhere | String | Yes | — | Admin → Events → New | events:create |
| Slug | URL identifier (/event/[slug]), unique | String (UID) | Yes | Auto from title | Admin → Events | events:create |
| Type | Classification badge | Enum: workshop, panel, reading, lecture, festival, meetup, webinar, other | Yes | other | Admin → Events | events:create |
| Status | Lifecycle / visibility | Enum: draft, published, featured, cancelled, archived | No | draft | Admin → Events | events:update |
| Description | Short summary on cards | Text | No | — | Admin → Events | events:create |
| Content | Rich-text/HTML body on detail page | Text (HTML) | No | — | Admin → Events | events:create |
| Is Featured | Highlights the event | Boolean | No | false | Admin → Events | events:update |
| Priority | Sort weight | Integer | No | 0 | Admin → Events | events:update |
| Series Name | Groups recurring events | String | No | — | Admin → Events | events:update |
| Tag IDs | Topical tags (JSON array of tag IDs) | JSON | No | — | Admin → Events | events:update |
Date / time
| Field / Option | What it does | Type | Mandatory? | Default | Where configured | Role |
|---|---|---|---|---|---|---|
| Start Date | Event start; drives upcoming/past | DateTime | Yes | — | Admin → Events | events:create |
| End Date | Event end | DateTime | No | — | Admin → Events | events:create |
| Timezone | Display timezone | String | No | Asia/Kolkata | Admin → Events | events:create |
Location (offline / online)
| Field / Option | What it does | Type | Mandatory? | Default | Where configured | Role |
|---|---|---|---|---|---|---|
| Is Online | Marks event as virtual | Boolean | No | false | Admin → Events | events:create |
| Online URL | Virtual meeting link (also powers the Join now CTA for live online events) | String | No | — | Admin → Events | events:create |
| Online Details | Human-readable access note shown as the venue line on the detail page, e.g. Online (Zoom) | Free, with subscriber priority | String (max 255) | No | — | Admin → Events | events:create |
| Venue Name | Physical venue | String | No | — | Admin → Events | events:create |
| Address | Street address | Text | No | — | Admin → Events | events:create |
| City | City | String | No | — | Admin → Events | events:create |
| Map URL | Google Maps link (offline events) | String | No | — | Admin → Events | events:create |
Media, speakers & schedule
| Field / Option | What it does | Type | Mandatory? | Default | Where configured | Role |
|---|---|---|---|---|---|---|
| Cover Image | Card + hero image, chosen via the Media Library picker (parity with the Campaign Hero); synced to Strapi as coverImageUrl. On the shared event-detail template, the per-event cover takes precedence over any section background image. | String (URL) | No | — | Admin → Events | events:create |
| Gallery | Multiple image URLs | JSON array | No | — | Admin → Events | events:update |
| Speakers | [{ name, role, bio, photo }] | JSON array | No | — | Admin → Events | events:update |
| Schedule | Agenda rows for the detail page. Each item is { time, title, description, speakerId } — a single free-text time per row (event agenda rows do not carry a separate end time; that is a Marketing campaign-schedule feature, not events). | JSON array | No | — | Admin → Events | events:update |
RSVP / registration
| Field / Option | What it does | Type | Mandatory? | Default | Where configured | Role |
|---|---|---|---|---|---|---|
| RSVP Enabled | Shows the Register button + form | Boolean | No | false | Admin → Events | events:update |
| RSVP Capacity | Max confirmed registrations (null = unlimited) | Integer | No | — (unlimited) | Admin → Events | events:update |
| RSVP Count | Live confirmed count (system-maintained) | Integer | No (read-only) | 0 | System | — |
| Registration Open Date | When registration becomes available | DateTime | No | — | Admin → Events | events:update |
| Registration URL | External link (e.g. Eventbrite); button links here if set | String | No | — | Admin → Events | events:update |
| Registration Fields | Custom form schema (Form Field Builder) | JSON (FormField[]) | No | name/email/phone defaults | Admin → Events (when RSVP enabled) | events:update |
Post-event
| Field / Option | What it does | Type | Mandatory? | Default | Where configured | Role |
|---|---|---|---|---|---|---|
| Recording URL | Recording playback for past events | String | No | — | Admin → Events | events:update |
| Recap URL | "Read the recap" link on past-visits listing | String | No | — | Admin → Events | events:update |
Creating an Event (Admin Console)
- Go to Content Entities → Events in the Admin Console.
- Click + New Event (visible only with
events:create). - Fill in the mandatory fields: Title, Slug (auto-generated), Type, Start Date.
- Add description, rich-text content, location (toggle Is Online for virtual events and add the Online URL plus an optional Online Details access note, or fill venue/address/city/map for in-person). Set an End Date if the event spans hours — it is what enables the Live state.
- Pick a Cover Image from the Media Library and optionally add a gallery.
- Add Speakers and Schedule as needed.
- To collect attendees, enable RSVP Enabled, set RSVP Capacity (leave blank for unlimited), and customize the Registration Form Fields (or accept the name/email/phone defaults).
- Set Status to
published(orfeaturedto highlight it). - Save. The event is written to Postgres and synced to Strapi; it appears on the Reader Portal once sync completes.

Reader-Facing Pages & URLs
| Page | URL | What it shows |
|---|---|---|
| Events Listing | /events | Upcoming and past events (cards / calendar / list variants) |
| Event Detail | /event/[slug] | Full event: header, description, schedule, speakers, RSVP |
| Registration | /event/[slug]/register | Standalone registration form |
Route note: The reader detail/registration routes are singular —
/event/[slug]and/event/[slug]/register. The listing is plural —/events. Do not link to/event-detail.

Template-driven rendering
Event pages are template-driven, following the Hyphen design system. The page layout is composed of section variants from the Page Template System; each section variant is a self-contained, restyled component (shared event-card UI: a 96×120 calendar date badge and a 120×36 RSVP/Details button, reused across the grid and Related Events). The variants are resolved and their data is fetched server-side in the reader portal's section resolver, so what controls a given event page is its page template — not hard-coded markup.
- Which template controls a page? The
eventpage type drives the listing (/events) and theevent-detailpage type drives/event/[slug]. If no template is configured for those page types, built-in fallback sections render a complete layout automatically (header, hero, listing cards / detail header, description, speakers, RSVP form, related events, footer). Template configuration is optional and only needed to reorder or swap section variants. - Event listing controls. The listing section supports Manual (hand-picked) selection — render exactly the events you choose, in order (matched by Strapi id or the underlying admin event id) — and a dynamic Lifecycle multi-select (
Live/Upcoming/Past). Lifecycle bucketing is computed fromstartDate/endDate(legacy singletimeFilterof upcoming/past is still honoured). The listing also renders a View More link. - Related Events appears on event detail pages (
campaign-related-eventsvariant) — see Related Events below. - Campaigns stay out of Events. Marketing campaign events are not written into the reader event collection, so they do not appear in the reader
/eventsgrids or event search. Reader event listings show genuine Events only.
See the Page Templates guide for the Canvas Editor workflow; the event section variants (event-hero, event-listing-cards, event-calendar-view, events-list, event-detail-header, event-detail-description, event-speakers-grid, event-rsvp-form, event-past-recordings, event-series-header, campaign-related-events) are available there.
Related Events (reader discovery)
Event detail pages can show a Related Events section (campaign-related-events variant) so readers can discover sibling events. It lists upcoming, published events that share the opened event's Event Type, excluding the opened event itself. The set is resolved entirely server-side: the resolver reads the current event slug from the page path, looks up its type, and fetches up to the configured number of upcoming events of the same type (4 by default), honouring any status filter and explicit exclusions. The section self-hides when there are no related events (or when rendered off an event page, or when the opened event has no Event Type). Selection is fully automatic — there is no manual picker for this section.
SEO and Structured Data
Event pages automatically include meta/Open Graph/Twitter tags, breadcrumb JSON-LD (Home > Events > [Event Title]), and Schema.org Event JSON-LD on detail pages (name, description, dates, attendance mode online/offline, location, organizer). See the SEO/AEO practices in the analytics area for measurement.
Dependencies & Impact
Events touch several other modules. Changes here can ripple outward:
- Editorial Workflow — Events are authored alongside articles in the Admin Console under Content Entities; the same publish/status discipline (draft → published → featured/archived) applies.
- Notifications & Email System — Registration confirmation and reminder emails are owned by the email system (event registry + templates), not the registration endpoint. Configure attendee emails there.
- Marketing & Outreach — The registration form uses the shared Form Field Builder (same primitive as marketing forms). Events can be promoted through marketing campaigns and conversion surfaces.
- Analytics & Reporting — Registration counts (
rsvpCount), capacity utilization, and conversion surfaces feed analytics for measuring event performance.
Operational dependencies: PostgreSQL/Prisma (storage of truth), Strapi + STRAPI_API_TOKEN (read model for the portal — events won't appear on the portal until sync succeeds), and IP-based rate limiting on the registration endpoint.
Common Mistakes and How to Fix Them
| Problem | Cause | Fix |
|---|---|---|
Event not on /events | Status is draft/cancelled/archived, or Strapi sync failed | Set status to published/featured; confirm Strapi is up and STRAPI_API_TOKEN is set |
| 404 on event page | Wrong route | Use /event/[slug] (singular) for detail, /events (plural) for listing |
| Register button missing | RSVP not enabled, or event has ended | Enable RSVP Enabled; verify the event has not ended (set an End Date if it spans hours) |
| Event won't show as "Live" | No End Date set, so it can't be in the started-but-not-ended window | Add an End Date; Live is computed as startDate ≤ now ≤ endDate |
| "Join now" missing on a live online event | Not marked online, or no Online URL | Set Is Online and an Online URL; Join now needs both |
A marketing campaign appears in /events | Legacy data from a prior dual-write | Campaign events are excluded from reader event grids/search; re-sync if you still see legacy rows |
| "Registration capacity reached" (409 full) | rsvpCount >= rsvpCapacity | Increase RSVP Capacity or clear it for unlimited |
| "Already registered" (409) | Email already used for this event | One registration per email per event by design |
| "Too many attempts" (429) | More than 5 tries in 15 min from one IP | Wait and retry; this is anti-abuse rate limiting |
| Required field rejected | A required registration field was empty | Fill all required fields, or adjust the form schema in Admin |
| No confirmation email | Registration endpoint does not send email inline | Configure registration emails via the Email System |
| Cover image not on portal | Sync to Strapi didn't run/failed | Re-save the event; check Strapi connectivity (coverImage → coverImageUrl) |
FAQ
Paywall & Access
How content gating works end-to-end — access levels, the magazine access ladder, entitlement resolution, content access rules, metered preview, and where every access setting is configured.
Download Guides
Download user guides as PDF or Word documents for offline use or client handoffs