> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.outpost.pub/llms.txt
> Use this file to discover all available pages before exploring further.

# Theme Manifest Reference

> Complete reference for the outpost-manifest.yaml schema, including event types, field definitions, constraints, custom fields, and error handling.

This reference documents every option available in the `outpost-manifest.yaml` file. For a quick introduction, see [Getting Started](/themes/manifest-getting-started).

## File Location

The manifest file must be named `outpost-manifest.yaml` and placed in your theme's root directory, alongside `package.json`.

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
your-theme/
├── package.json
├── outpost-manifest.yaml  ← here
├── default.hbs
├── post.hbs
└── ...
```

## Schema Overview

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
manifest_version: 1           # Required — must be 1

events:
  default_tags: []            # Optional — tags for all event types
  types:                      # Required — at least one type
    type_key:                 # Your type identifier (e.g., "webinar")
      label: "Display Name"
      post_template: "custom-template"  # Required
      tags: []
      supported_fields: []
      required_fields: []
      field_constraints: {}
      custom_fields: []
      messaging_states: []
```

## Top-Level Fields

### manifest\_version

**Required.** Must be `1`. This enables future schema evolution without breaking existing manifests.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
manifest_version: 1
```

### events

**Required.** Contains all event-related configuration.

## Events Section

### events.default\_tags

**Optional.** Array of Ghost tag names applied to ALL event types when published.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
events:
  default_tags:
    - "event"
    - "registration-open"
```

Tags are strings. Invalid entries (non-strings) are silently ignored.

### events.types

**Required.** Object where each key is an event type identifier and the value is that type's configuration.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
events:
  types:
    webinar:
      # ... webinar config
    workshop:
      # ... workshop config
    meetup:
      # ... meetup config
```

Type keys should be lowercase with underscores (e.g., `in_person`, `virtual_workshop`). These keys are stored on event records and used for identification.

## Event Type Configuration

Each event type supports the following fields:

### post\_template

**Required.** The Ghost custom template filename (without `.hbs` extension) used for this event type's posts.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
types:
  webinar:
    post_template: "custom-webinar"
```

This template must exist in your theme. Events using this type will have their Ghost posts set to use this template.

### label

**Optional.** Human-readable name shown in the Outpost UI. Defaults to the type key with underscores replaced by spaces and title-cased.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
types:
  in_person:
    label: "In-Person Event"  # Shows as "In-Person Event" in dropdown
```

### tags

**Optional.** Array of Ghost tags specific to this event type. Merged with `default_tags` when publishing.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
types:
  webinar:
    tags:
      - "webinar"
      - "online-event"
```

### supported\_fields

**Optional.** Array of standard field keys to show for this event type. If omitted, all standard fields are shown.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
types:
  webinar:
    supported_fields:
      - name
      - description
      - event_date
      - event_time_start
      - event_time_end
      - event_time_zone
      - platform_url
      - access
```

See [Standard Field Keys](#standard-field-keys) for all available fields.

### required\_fields

**Optional.** Array of field keys that must be filled before publishing. Must be a subset of `supported_fields` (or standard fields if `supported_fields` is omitted).

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
types:
  webinar:
    required_fields:
      - name
      - event_date
      - platform_url
```

<Warning>
  If a required field is not in `supported_fields`, it will be ignored with a warning.
</Warning>

### field\_constraints

**Optional.** Object defining validation constraints for specific fields.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
types:
  in_person:
    field_constraints:
      feature_image:
        aspect_ratio: "16:9"
        min_width: 1200
      description:
        max_length: 500
```

See [Field Constraints](#field-constraints) for available constraint options.

### custom\_fields

**Optional.** Array of custom field definitions for theme-specific data.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
types:
  webinar:
    custom_fields:
      - key: speaker_name
        label: "Speaker Name"
        type: text
        required: true
```

See [Custom Field Types](#custom-field-types) for complete options.

### messaging\_states

**Optional.** Array of messaging states to show in the event settings. If omitted, all seven states are shown.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
types:
  webinar:
    messaging_states:
      - can_register
      - registered
      - event_passed
```

See [Messaging States](#messaging-states) for all available states.

## Standard Field Keys

These are the built-in fields available for `supported_fields` and `required_fields`:

| Key                     | Description                              | Data Type                          |
| ----------------------- | ---------------------------------------- | ---------------------------------- |
| `name`                  | Event name/title                         | string                             |
| `slug`                  | URL-safe identifier                      | string                             |
| `description`           | Event description (used as post excerpt) | string                             |
| `event_date`            | Date of the event                        | date                               |
| `event_time_start`      | Start time                               | string (e.g., "10:00 am")          |
| `event_time_end`        | End time                                 | string (e.g., "11:00 am")          |
| `event_time_zone`       | IANA timezone                            | string (e.g., "America/New\_York") |
| `location`              | Venue or location text                   | string                             |
| `platform_url`          | Virtual event platform link              | URL string                         |
| `max_attendees`         | Capacity limit                           | integer                            |
| `feature_image`         | Featured/hero image                      | URL string                         |
| `access`                | Who can register                         | enum: `anyone`, `free`, `paid`     |
| `rsvp_labels`           | Labels applied to registrants            | array of strings                   |
| `registration_deadline` | Registration cutoff date                 | date                               |
| `deadline_time_end`     | Registration cutoff time                 | string                             |

## Field Constraints

Available constraints vary by field type:

### Text/Textarea Fields

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
field_constraints:
  description:
    max_length: 500        # Maximum character count
    pattern: "^[A-Z]"      # Regex pattern (optional)
```

### Image Fields

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
field_constraints:
  feature_image:
    aspect_ratio: "16:9"   # Width:height ratio
    min_width: 1200        # Minimum width in pixels
```

### Number Fields

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
field_constraints:
  max_attendees:
    min: 1
    max: 1000
```

## Custom Field Types

Custom fields support these types:

### text

Single-line text input.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
- key: speaker_name
  label: "Speaker Name"
  type: text
  required: true
  help_text: "Full name of the presenter"
  max_length: 100
  pattern: "^[A-Za-z\\s]+$"
```

### textarea

Multi-line text input.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
- key: speaker_bio
  label: "Speaker Bio"
  type: textarea
  required: false
  max_length: 500
```

### number

Numeric input with optional range constraints.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
- key: session_duration
  label: "Duration (minutes)"
  type: number
  required: true
  min: 15
  max: 480
```

### select

Dropdown with predefined options.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
- key: session_type
  label: "Session Type"
  type: select
  required: true
  options:
    - Keynote
    - Breakout
    - Workshop
    - Panel
```

<Warning>
  Select fields **must** include an `options` array. Fields without options are skipped with an error.
</Warning>

### url

URL input with validation.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
- key: slides_url
  label: "Slides URL"
  type: url
  required: false
  help_text: "Link to presentation slides"
```

### date

Date picker input.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
- key: early_bird_deadline
  label: "Early Bird Deadline"
  type: date
  required: false
```

### toggle

Boolean switch input.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
- key: is_recorded
  label: "Will Be Recorded"
  type: toggle
  required: false
```

### image

Image upload with optional constraints.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
- key: speaker_photo
  label: "Speaker Photo"
  type: image
  required: false
  aspect_ratio: "1:1"
  min_width: 400
  max_file_size_kb: 2048
```

## Custom Field Properties

All custom field types support these properties:

| Property    | Type    | Required | Description                                          |
| ----------- | ------- | -------- | ---------------------------------------------------- |
| `key`       | string  | Yes      | Unique identifier (no spaces, lowercase recommended) |
| `label`     | string  | No       | Display label (defaults to formatted key)            |
| `type`      | string  | Yes      | One of the field types above                         |
| `required`  | boolean | No       | Whether field must be filled (default: false)        |
| `help_text` | string  | No       | Helper text shown below the field                    |

<Warning>
  Custom field keys **must not** collide with standard field keys. Keys like `name`, `event_date`, `access`, etc. will be rejected with an error.
</Warning>

## Messaging States

Control which messaging states appear in the event settings editor:

| State                 | When Shown to Members                 |
| --------------------- | ------------------------------------- |
| `can_register`        | Eligible member who hasn't registered |
| `login_required_free` | Guest on a free event                 |
| `login_required_paid` | Guest on a paid event                 |
| `upgrade_required`    | Free member on a paid event           |
| `registered`          | Member who has RSVP'd                 |
| `registration_closed` | After registration deadline           |
| `event_passed`        | After the event ends                  |

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
types:
  webinar:
    messaging_states:
      - can_register
      - registered
      - event_passed
```

If omitted, all seven states are configurable.

## Homepage Curation Section

**Optional.** Declares curated homepage regions that a publisher can fill from the Outpost UI. When a theme's manifest includes a top-level `homepage_curation` key, Outpost shows a **Homepage Curation** screen (under Posts) where editors pick which post occupies each slot — without hand-editing tags in Ghost.

`homepage_curation` and `events` are independent top-level keys in the same `outpost-manifest.yaml`; a theme can declare either, both, or neither.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
homepage_curation:
  sections:                              # Required — one or more sections
    - key: featured-us                   # Section identifier (unique)
      name: "Featured (US)"              # Display name in the curation UI
      description: "Top-of-homepage hero carousel."   # Optional helper text
      picker:                            # Optional — controls the post picker
        default_view: recent             # Default tab when picking a post
        recent_limit: 30                 # How many recent posts to offer
        recent_filter:                   # Optional — pre-scope picker to a tag
          tag: local-news
      slots:                             # Required — one or more slots
        - { key: slot-1, label: "Slot 1", tag: hash-home-hero-slot-1 }
        - { key: slot-2, label: "Slot 2", tag: hash-home-hero-slot-2 }
```

### How slots work

Each **slot** binds one Ghost tag to one post. The theme renders whichever single post currently carries that tag; the curation UI simply swaps which post wears it. Slot tags are ordinary Ghost internal tags — a `hash-` prefix maps to a `#` internal tag.

| Field                                 | Required | Description                                       |
| ------------------------------------- | -------- | ------------------------------------------------- |
| `sections[].key`                      | Yes      | Unique identifier for the section                 |
| `sections[].name`                     | Yes      | Display name shown in the curation UI             |
| `sections[].description`              | No       | Helper text shown under the section               |
| `sections[].picker.default_view`      | No       | Default picker tab (e.g. `recent`)                |
| `sections[].picker.recent_limit`      | No       | Number of recent posts to list                    |
| `sections[].picker.recent_filter.tag` | No       | Restrict the picker to posts carrying this tag    |
| `slots[].key`                         | Yes      | Unique identifier for the slot within its section |
| `slots[].label`                       | Yes      | Label shown above the slot in the UI              |
| `slots[].tag`                         | Yes      | Ghost tag that binds a post to this slot          |

<Note>
  Homepage Curation is an early **Labs** feature and currently ships in the single-file manifest format. A complete worked example is bundled with the docs as `cascade-homepage-curation.yaml`.
</Note>

## Error Handling

Outpost validates manifests during theme upload and stores errors in the manifest data. Invalid parts are stripped while valid parts are kept.

### Error Categories

| Category                        | Severity | Behavior                  |
| ------------------------------- | -------- | ------------------------- |
| YAML syntax error               | Error    | Manifest ignored entirely |
| Unknown manifest\_version       | Warning  | Manifest ignored          |
| Missing post\_template          | Error    | Event type skipped        |
| Unknown standard field          | Warning  | Field key ignored         |
| Required field not in supported | Warning  | Requirement ignored       |
| Custom field key collision      | Error    | Custom field skipped      |
| Unknown custom field type       | Error    | Custom field skipped      |
| Missing options on select       | Error    | Custom field skipped      |

### Viewing Errors

Errors are visible in:

* Theme settings table (warning count with info button)
* Event editor (banner at top of form)
* Theme upload/refresh API response

### Example Error Response

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "_errors": [
    {
      "field": "types.webinar.post_template",
      "message": "Missing required 'post_template'",
      "severity": "error"
    },
    {
      "field": "types.in_person.supported_fields",
      "message": "Unknown standard field 'title'",
      "severity": "warning"
    }
  ]
}
```

## Complete Example

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
manifest_version: 1

events:
  default_tags:
    - "event"

  types:
    webinar:
      label: "Webinar"
      post_template: "custom-webinar"
      tags:
        - "webinar"
        - "online"

      supported_fields:
        - name
        - description
        - event_date
        - event_time_start
        - event_time_end
        - event_time_zone
        - platform_url
        - feature_image
        - access
        - registration_deadline
        - deadline_time_end

      required_fields:
        - name
        - event_date
        - platform_url

      field_constraints:
        feature_image:
          aspect_ratio: "16:9"
          min_width: 1200
        description:
          max_length: 300

      custom_fields:
        - key: speaker_name
          label: "Speaker Name"
          type: text
          required: true
          help_text: "Full name of the presenter"

        - key: speaker_bio
          label: "Speaker Bio"
          type: textarea
          required: false
          max_length: 500

        - key: session_type
          label: "Session Type"
          type: select
          required: true
          options:
            - Live Presentation
            - Q&A Session
            - Workshop

        - key: is_recorded
          label: "Will Be Recorded"
          type: toggle

      messaging_states:
        - can_register
        - login_required_free
        - registered
        - event_passed

    in_person:
      label: "In-Person Event"
      post_template: "custom-in-person"
      tags:
        - "in-person"
        - "conference"

      supported_fields:
        - name
        - description
        - event_date
        - event_time_start
        - event_time_end
        - event_time_zone
        - location
        - max_attendees
        - feature_image
        - access

      required_fields:
        - name
        - event_date
        - location
        - feature_image

      custom_fields:
        - key: venue_address
          label: "Venue Address"
          type: textarea
          required: true

        - key: track
          label: "Track"
          type: select
          required: true
          options:
            - Main Stage
            - Workshop Room A
            - Workshop Room B
            - Networking Hall
```
