> ## 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 Data Formats

> Reference for the data formats Outpost injects into Ghost posts, including standard fields, custom fields, and event state values for theme developers.

When an event is published to Ghost, Outpost injects structured data into the post's code injection footer. Your Ghost theme can read this data to render event details, registration CTAs, countdowns, and more.

## Code Injection JSON

Outpost adds a `<script>` tag with JSON data to every event post:

```html theme={"theme":{"light":"github-light","dark":"github-dark"}}
<script id="event" type="application/json">
{
  "date": "March 15, 2026",
  "event_day": "Sunday",
  "start_time": "10:00 am",
  "end_time": "11:00 am",
  "time_zone": "America/New_York",
  "time_zone_abbr": "EDT",
  "location": "Online",
  "platform_url": "https://zoom.us/j/123456789",
  "registration_deadline": "March 14, 2026",
  "access": "anyone",
  "event_state": "registration_open",
  "outpost_ghost_event_id": "march-2026-webinar",
  "event_type": "webinar",
  "custom_fields": {
    "speaker_name": "Jane Smith",
    "session_type": "Workshop"
  }
}
</script>
```

## Reading Data in Your Theme

### JavaScript

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
document.addEventListener('DOMContentLoaded', function() {
  const eventScript = document.getElementById('event');
  if (!eventScript) return;

  try {
    const eventData = JSON.parse(eventScript.textContent);

    // Access standard fields
    console.log(eventData.date);           // "March 15, 2026"
    console.log(eventData.event_state);    // "registration_open"
    console.log(eventData.access);         // "anyone"

    // Access custom fields
    if (eventData.custom_fields) {
      console.log(eventData.custom_fields.speaker_name);  // "Jane Smith"
      console.log(eventData.custom_fields.session_type);  // "Workshop"
    }
  } catch (e) {
    console.error('Failed to parse event data', e);
  }
});
```

### Handlebars (with inline script)

Since Handlebars templates run server-side and the JSON is in code injection (rendered client-side), you'll typically use JavaScript to read the data and update the DOM:

```handlebars theme={"theme":{"light":"github-light","dark":"github-dark"}}
{{!-- In your post.hbs or custom template --}}
<div class="event-details">
  <div class="event-date" data-event-field="date"></div>
  <div class="event-time" data-event-field="start_time"></div>
  <div class="event-location" data-event-field="location"></div>
  <div class="event-speaker" data-event-field="custom_fields.speaker_name"></div>
</div>

<script>
(function() {
  const eventScript = document.getElementById('event');
  if (!eventScript) return;

  const data = JSON.parse(eventScript.textContent);

  document.querySelectorAll('[data-event-field]').forEach(el => {
    const field = el.dataset.eventField;
    const value = field.split('.').reduce((obj, key) => obj?.[key], data);
    if (value) el.textContent = value;
  });
})();
</script>
```

## Standard Field Reference

| Field                       | Type   | Example                       | Description                                                                           |
| --------------------------- | ------ | ----------------------------- | ------------------------------------------------------------------------------------- |
| `date`                      | string | `"March 15, 2026"`            | Formatted event date (uses site's date format)                                        |
| `event_day`                 | string | `"Sunday"`                    | Day of the week                                                                       |
| `start_time`                | string | `"10:00 am"`                  | Event start time                                                                      |
| `end_time`                  | string | `"11:00 am"`                  | Event end time                                                                        |
| `time_zone`                 | string | `"America/New_York"`          | IANA timezone identifier                                                              |
| `time_zone_abbr`            | string | `"EDT"`                       | Localized timezone abbreviation for the event date                                    |
| `location`                  | string | `"Online"`                    | Event location/venue                                                                  |
| `platform_url`              | string | `"https://zoom.us/..."`       | Virtual event platform link                                                           |
| `registration_deadline`     | string | `"March 14, 2026"`            | Registration cutoff date (or event date if not set)                                   |
| `registration_deadline_iso` | string | `"2026-03-14T23:59:00-04:00"` | Registration cutoff as an ISO 8601 timestamp (empty when no explicit deadline is set) |
| `registration_end_time`     | string | `"11:59 pm"`                  | Time the registration window closes (empty when not set)                              |
| `access`                    | string | `"anyone"`                    | Access level enum (see below)                                                         |
| `event_state`               | string | `"registration_open"`         | Current lifecycle state (see below)                                                   |
| `outpost_ghost_event_id`    | string | `"march-2026-webinar"`        | Unique event identifier (GUID/slug)                                                   |
| `event_type`                | string | `"webinar"`                   | Event type key from manifest                                                          |
| `custom_fields`             | object | `{...}`                       | Custom field values defined by manifest                                               |

## Access Levels

The `access` field indicates who can register for the event:

| Value    | Description                           | Theme Behavior                            |
| -------- | ------------------------------------- | ----------------------------------------- |
| `anyone` | All logged-in members (free and paid) | Show standard registration CTA            |
| `free`   | Free-tier members and above           | Show registration CTA for logged-in users |
| `paid`   | Only paid subscribers                 | Show upgrade CTA for free members         |

### Example: Conditional Rendering by Access

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const eventData = JSON.parse(document.getElementById('event').textContent);

if (eventData.access === 'paid') {
  // Show "Paid subscribers only" badge
  document.querySelector('.event-access-badge').textContent = 'Paid Subscribers Only';
  document.querySelector('.event-access-badge').classList.add('premium');
}
```

## Event States

The `event_state` field reflects the event's current lifecycle position:

| Value                 | Description                             | Typical Theme Behavior              |
| --------------------- | --------------------------------------- | ----------------------------------- |
| `registration_open`   | Event is upcoming, registration is open | Show registration CTA               |
| `registration_closed` | Deadline passed, event hasn't happened  | Show "Registration closed" message  |
| `event_passed`        | Event date/time has passed              | Show "This event has ended" message |

### Example: State-Based UI

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const eventData = JSON.parse(document.getElementById('event').textContent);
const ctaContainer = document.querySelector('.event-cta');

switch (eventData.event_state) {
  case 'registration_open':
    ctaContainer.innerHTML = '<button class="register-btn">Register Now</button>';
    break;
  case 'registration_closed':
    ctaContainer.innerHTML = '<p class="closed">Registration is closed</p>';
    break;
  case 'event_passed':
    ctaContainer.innerHTML = '<p class="past">This event has ended</p>';
    break;
}
```

## Custom Fields

Custom fields defined in the manifest are nested under the `custom_fields` object. The keys match what you defined in your `outpost-manifest.yaml`:

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
# In your manifest
custom_fields:
  - key: speaker_name
    type: text
  - key: session_type
    type: select
    options: [Workshop, Keynote, Panel]
```

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
// In your theme JavaScript
const eventData = JSON.parse(document.getElementById('event').textContent);

if (eventData.custom_fields?.speaker_name) {
  document.querySelector('.speaker-name').textContent =
    eventData.custom_fields.speaker_name;
}

if (eventData.custom_fields?.session_type) {
  document.querySelector('.session-type').textContent =
    eventData.custom_fields.session_type;
}
```

### Custom Field Value Types

| Manifest Type | JSON Type | Example Value                         |
| ------------- | --------- | ------------------------------------- |
| `text`        | string    | `"Jane Smith"`                        |
| `textarea`    | string    | `"A longer description..."`           |
| `number`      | number    | `60`                                  |
| `select`      | string    | `"Workshop"`                          |
| `url`         | string    | `"https://example.com"`               |
| `date`        | string    | `"2026-03-15"` (ISO format)           |
| `toggle`      | boolean   | `true`                                |
| `image`       | string    | `"https://cdn.example.com/image.jpg"` |

## Event Defaults Partial

Outpost also injects messaging defaults and portal URLs via the `outpost-event-defaults.hbs` partial during theme processing:

```html theme={"theme":{"light":"github-light","dark":"github-dark"}}
<script id="outpost-event-defaults" type="application/json">
{
  "messaging": {
    "can_register": {
      "cta_text": "Join this event",
      "button_text": "Register"
    },
    "registered": {
      "cta_text": "You're registered!",
      "button_text": "Add to Calendar"
    },
    ...
  },
  "urls": {
    "signin": "#/portal/signin",
    "signup": "#/portal/signup",
    "upgrade": "#/portal/account"
  }
}
</script>
```

This provides:

* **Messaging**: Default CTA text and button labels for each audience state
* **URLs**: Ghost portal links for sign-in, sign-up, and account/upgrade

### Reading Event Defaults

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const defaultsScript = document.getElementById('outpost-event-defaults');
if (defaultsScript) {
  const defaults = JSON.parse(defaultsScript.textContent);

  // Access messaging for current state
  const messaging = defaults.messaging?.can_register;
  if (messaging) {
    document.querySelector('.cta-text').textContent = messaging.cta_text;
    document.querySelector('.cta-button').textContent = messaging.button_text;
  }

  // Access portal URLs
  const signInUrl = defaults.urls?.signin || '#/portal/signin';
}
```

## Complete Theme Example

Here's a complete example of rendering event data in a custom Ghost template:

```handlebars theme={"theme":{"light":"github-light","dark":"github-dark"}}
{{!-- custom-webinar.hbs --}}
{{!< default}}

<article class="event-post">
  <header class="event-header">
    <h1>{{title}}</h1>
    {{#if feature_image}}
      <img src="{{feature_image}}" alt="{{title}}" class="event-image" />
    {{/if}}
  </header>

  <div class="event-meta" id="event-meta-container">
    <!-- Populated by JavaScript from code injection -->
  </div>

  <div class="event-content">
    {{content}}
  </div>

  <div class="event-registration">
    <!-- Outpost widget renders here -->
    <div class="outpost-ghost-event-container"></div>
  </div>
</article>

<script>
document.addEventListener('DOMContentLoaded', function() {
  const eventScript = document.getElementById('event');
  if (!eventScript) return;

  const event = JSON.parse(eventScript.textContent);
  const container = document.getElementById('event-meta-container');

  container.innerHTML = `
    <div class="event-datetime">
      <span class="event-date">${event.date}</span>
      <span class="event-time">${event.start_time} - ${event.end_time}</span>
      <span class="event-timezone">${event.time_zone}</span>
    </div>
    <div class="event-location">${event.location}</div>
    ${event.custom_fields?.speaker_name ? `
      <div class="event-speaker">
        <strong>Speaker:</strong> ${event.custom_fields.speaker_name}
      </div>
    ` : ''}
    ${event.event_state === 'event_passed' ? `
      <div class="event-status past">This event has ended</div>
    ` : ''}
  `;
});
</script>
```

## Ghost Post Properties

In addition to code injection, Outpost sets these Ghost post properties:

| Property          | Value                                          |
| ----------------- | ---------------------------------------------- |
| `title`           | Event name                                     |
| `custom_excerpt`  | Event description                              |
| `feature_image`   | Event featured image (if uploaded)             |
| `custom_template` | Template from manifest `post_template`         |
| `tags`            | Merged from `default_tags` + event type `tags` |

These are accessible via standard Ghost Handlebars helpers:

```handlebars theme={"theme":{"light":"github-light","dark":"github-dark"}}
<h1>{{title}}</h1>
<p class="excerpt">{{excerpt}}</p>
{{#if feature_image}}
  <img src="{{feature_image}}" />
{{/if}}
```

## API Endpoint for Registration

The event widget uses Outpost's plugin API for registration. If you're building a custom registration UI:

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
// Register a member for an event
const response = await fetch(
  `/plugin-api/ghost-event/${apiKey}/${eventGuid}/subscribe/${memberUid}`,
  { method: 'POST' }
);

// Cancel registration
const response = await fetch(
  `/plugin-api/ghost-event/${apiKey}/${eventGuid}/unsubscribe/${memberUid}`,
  { method: 'POST' }
);

// Get calendar file
window.location.href = `/plugin-api/ghost-event/${apiKey}/${eventGuid}/calendar.ics`;
```

The `apiKey` is your site's Outpost GUID (available in the `outpost-api-key.hbs` partial). The `memberUid` is the logged-in member's Ghost UUID.
