# Events

URL: https://developers.uidu.org/docs/domains/events

> Fetch event listings, event details, and submit RSVPs (attendance) against the uidu Events API.

* **Use it for:** [Public website](https://developers.uidu.org/docs/paths/public-website.md) · [Server integration](https://developers.uidu.org/docs/paths/server-integration.md)
* **Reads:** public token — works in the browser
* **Writes:** Bearer (`apiKey`) — server-side only
* **CLI:** `uidu events`, `uidu attendances`
* **Auth:** what each token can do and where it may run — [Authentication](https://developers.uidu.org/docs/foundations/authentication.md)

Events are what your workspace runs in time — a workshop, a gala, a weekly class. You list them
and show their details with the public token, and record RSVPs (**attendances**) from your server.

## Mental model

* **`Event`** — the metadata: `name`, `description`, `body`, `cover`, `published`, `publicPath` /
  `publicUrl`, `isPaidEvent`, `hasSeats`, `currentCapacity`, a `primaryAddress`
  (`formattedAddress`, `name`, `city`, `postalCode`, `countryCode`, `latitude`, `longitude`), and
  ready-made calendar links: `toGoogleCalendarUrl` and `toIcsUrl` (an `.ics` download).
* **`EventInstance`** — one occurrence in time: `id`, `beginsAt`, `finishesAt` (ISO timestamps). An
  event has one or more. `event.instance` is the **next or current** one — what most pages show —
  and `event.instances` is all of them, for a series view or a date picker. Only `getEvent` returns
  `instances` (and `instance.endDate` / `endTime`); `listEvents` summaries carry just `instance`.
* **`Attendance`** — a contact's RSVP, attached to an **instance, never to the event**: its
  `attendableId` is an `EventInstance.id`. An event with several dates has several instances, each
  attended independently. An attendance comes back with `id`, `checkedIn` and `contact.id`.

Shapes and gotchas:

* Passing the event id as `attendableId` is the classic mistake — always take it from
  `event.instance.id` or one of `event.instances[].id`.
* `beginsAt` / `finishesAt` are typed loosely (ISO8601 scalars come through as `any`); parse them with
  `new Date(...)` before comparing or formatting.
* `listEvents` has no date filter: it returns the workspace's events (`first`/`after` page through
  them, see [Pagination](https://developers.uidu.org/docs/foundations/pagination-and-field-values.md)). Filter and sort on
  `instance.beginsAt` yourself.
* `createAttendance` and `createEvent` return the payload as-is — `{ errors, attendance }` and
  `{ errors, event }`, or null; validation failures come back in `errors`, not as a throw. Both need
  the `apiKey` client, so they run on your server or from the CLI (`uidu attendances create`,
  `uidu events create`), never with the public token in the browser. See
  [Writes](https://developers.uidu.org/docs/foundations/writes.md) and [Authentication](https://developers.uidu.org/docs/foundations/authentication.md).

## Use cases

### Upcoming events page

Goal: a page listing the events that haven't started yet, soonest first.

```tsx
// app/events/page.tsx — a Server Component
import Link from 'next/link';
import { listEvents } from '@uidu/client';
import { uidu } from '@/lib/uidu';

export default async function EventsPage() {
  const events = await listEvents(uidu, { first: 50 });
  const now = Date.now();

  const upcoming = events
    .filter(
      (e) =>
        e.instance?.beginsAt && new Date(e.instance.beginsAt).getTime() > now,
    )
    .sort(
      (a, b) =>
        new Date(a.instance!.beginsAt).getTime() -
        new Date(b.instance!.beginsAt).getTime(),
    );

  return (
    <ul>
      {upcoming.map((event) => (
        <li key={event.id}>
          <Link href={`/event/${encodeURIComponent(event.id)}`}>
            {event.name}
          </Link>
          <time>{new Date(event.instance!.beginsAt).toLocaleDateString()}</time>
          {event.primaryAddress?.city && (
            <span> · {event.primaryAddress.city}</span>
          )}
        </li>
      ))}
    </ul>
  );
}
```

* Each summary has `id`, `name`, `description`, `cover`, `published`, `publicPath`, `isPaidEvent`,
  `primaryAddress` (`formattedAddress`, `city`) and `instance` (`id`, `beginsAt`, `finishesAt`). An
  event with no upcoming occurrence has no `instance` to show.
* Put the detail route under a path segment (`/event/[id]`), not a root `/[id]` that would also catch
  `/favicon.ico`.
* Need every event, for a sitemap or `generateStaticParams`? Use `paginate` with
  `ListEventsDocument` — [Pagination & field values](https://developers.uidu.org/docs/foundations/pagination-and-field-values.md).

### Event page with RSVP

Goal: show one event with its dates and calendar links, and let a visitor RSVP for a date.

```tsx
// app/event/[id]/page.tsx
import { createAttendance, getEvent } from '@uidu/client';
import { notFound } from 'next/navigation';
import { uidu } from '@/lib/uidu'; // createClient({ workspace, publicToken, apiKey })

export default async function EventPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const event = await getEvent(uidu, { id: decodeURIComponent(id) });
  if (!event) notFound();

  async function rsvp(formData: FormData) {
    'use server';
    const result = await createAttendance(uidu, {
      input: {
        attributes: {
          attendableId: String(formData.get('instanceId')), // an INSTANCE id, not event.id
          contactAttributes: {
            email: String(formData.get('email')),
            contactableAttributes: {
              kind: 'person',
              firstName: String(formData.get('firstName')),
            },
          },
        },
      },
    });
    if (result?.errors?.length) {
      // validation failure: return result.errors to the form
      return;
    }
    // result?.attendance?.id, result?.attendance?.contact?.id
  }

  return (
    <article>
      <h1>{event.name}</h1>
      <p>{event.primaryAddress?.formattedAddress}</p>
      <a href={event.toGoogleCalendarUrl ?? undefined}>
        Add to Google Calendar
      </a>
      <a href={event.toIcsUrl ?? undefined}>Download .ics</a>

      <form action={rsvp}>
        <select name="instanceId" defaultValue={event.instance?.id}>
          {(event.instances ?? []).map((i) => (
            <option key={i.id} value={i.id}>
              {new Date(i.beginsAt).toLocaleString()}
            </option>
          ))}
        </select>
        <input name="firstName" required />
        <input name="email" type="email" required />
        <button>RSVP</button>
      </form>
    </article>
  );
}
```

* For a single-date event, skip the picker and send `event.instance.id`.
* `AttendanceAttributes` beyond the instance: `contactAttributes` creates the attendee in the same
  mutation, or `contactId` attaches an existing contact instead; `formResponseAttributes` carries
  extra answers (dietary needs…) in the shape described in [Forms](https://developers.uidu.org/docs/domains/forms.md) — every
  `content` is `{ value }`; `rsvpStatus` is `pending`, `accepted`, `declined` or `tentative`.
* `isPaidEvent`, `hasSeats` and `currentCapacity` tell you whether a plain RSVP fits the event;
  paid tickets go through uidu's checkout, which this domain doesn't wrap.
* The demo's version, with error handling, is `submitRsvp` in `apps/demo/src/lib/actions.ts`.

### Publish an event from your server

Goal: create an event with its first date from a script or an admin tool.

```ts
import { createEvent, updateEvent } from '@uidu/client';
import { uidu } from '@/lib/uidu'; // must carry apiKey

const created = await createEvent(uidu, {
  input: {
    attributes: {
      name: 'Launch party',
      eventInstancesAttributes: [
        {
          startDate: '2026-11-20',
          startTime: '19:00',
          endDate: '2026-11-20',
          endTime: '23:00',
        },
      ],
    },
  },
});
if (created?.errors?.length)
  throw new Error(created.errors.map((e) => e.name).join(', '));

const id = created?.event?.id;
if (id)
  await updateEvent(uidu, { input: { id, attributes: { hasSeats: true } } });
```

* Dates are written per instance as `startDate` / `endDate` (`YYYY-MM-DD`) and `startTime` /
  `endTime` (`HH:MM`); an instance without `endTime` lasts one hour. Reads give you the combined
  `beginsAt` / `finishesAt` instead.
* `EventAttributes` also takes `body` (rich text), `cover`, `addressesAttributes`, `eventTagList`,
  `preferences` — the full list is in [Schema](https://developers.uidu.org/docs/reference/schema.md). The payload returns the new
  event's `id` and `name`.
* The same from a terminal: `uidu events create`, `uidu events update` — [CLI](https://developers.uidu.org/docs/tools/cli.md).
  Events can't be deleted through the SDK or the CLI.

## API at a glance

Every `@uidu/client` function in **Events**, generated from the source — the [full reference](https://developers.uidu.org/docs/reference/client.md#events) has the rest of the SDK.

| Function                                                                                         | Kind  | Auth         | CLI                  | Description                                                                                                                   |
| ------------------------------------------------------------------------------------------------ | ----- | ------------ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| [`createAttendance`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/events.ts) | write | Bearer       | `attendances create` | RSVP a contact to an event: `attendableId` is an event *instance* id, never the event id.                                     |
| [`createEvent`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/events.ts)      | write | Bearer       | `events create`      | Create an event; returns the payload as-is, `{ errors, event }`, validation failures included.                                |
| [`getEvent`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/events.ts)         | read  | public token | `events get`         | One event with its primary address, its next or current `instance` and all its `instances`, or null.                          |
| [`listEvents`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/events.ts)       | read  | public token | `events list`        | The workspace's events as a flat array of summaries, each with its next or current `instance`; page with `first`/`after`.     |
| [`updateEvent`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/mutations.ts)   | write | Bearer       | `events update`      | Update an event: `{ input: { id, attributes } }` (create it with `createEvent`). Returns the payload (with `errors`) or null. |

**Types:** `Event`

**CLI:**

| Entity        | list | get | create | update | delete |
| ------------- | :--: | :-: | :----: | :----: | :----: |
| `events`      |   ✓  |  ✓  |    ✓   |    ✓   |    —   |
| `attendances` |   —  |  —  |    ✓   |    —   |    —   |
