The Event Ticketing app writes event data onto the Shopify product and its variants as metafields in the event_ticketing namespace. A Liquid theme reads these to render dates, times, venue, and ticket details on your storefront. Every value is stored as a JSON metafield.
Requirements: A theme (or theme app extension) that reads Liquid metafields. Headless storefronts read the same metafields through the Storefront API after a one-time setup step you can run yourself, covered in the FAQ.
The three metafields
The app writes three metafields per event:
| Owner | Namespace | Key | Holds |
|---|---|---|---|
| Product | event_ticketing |
event_v2 |
The top-level event |
| Each variant | event_ticketing |
event_v2 |
That variant’s date |
| Each variant | event_ticketing |
ticket |
That ticket’s settings |
A variant is one ticket. Note that event_v2 lives on both the product and each variant, and that ticket-level detail (group size, purchase limits, the attendee form) is a separate ticket key — it is not nested inside event_v2.
For a one-time event the product event_v2 and the variant event_v2 describe the same single date. For a multi-date or recurring event they differ: the product event_v2 is the parent event, and each variant’s event_v2 is the specific date that variant sells.
Reading metafields in Liquid
Read event_v2 from the product or the variant:
{{ product.metafields.event_ticketing.event_v2.value }}
{{ variant.metafields.event_ticketing.event_v2.value }}
Read the per-variant ticket settings from the ticket key:
{{ variant.metafields.event_ticketing.ticket.value }}
Each value is a JSON object — access its fields by key:
{{ variant.metafields.event_ticketing.event_v2.value.starts_at | date: "%a, %b %d, %Y" }}
{{ variant.metafields.event_ticketing.ticket.value.quantity }}
Some date fields are empty when the event has no date of its own (for example, the parent of a multi-date event). Null-check before formatting:
{% if variant.metafields.event_ticketing.event_v2.value.starts_at %}
{{ variant.metafields.event_ticketing.event_v2.value.starts_at | date: "%b %d" }}
{% endif %}
event_v2 fields
The event_v2 JSON object contains:
| Field | Description |
|---|---|
id |
Event ID |
name |
Event name |
type |
Event kind — e.g. a single date, the parent of a multi-date event, or a recurring event |
starts_at |
Start, in the event’s time zone (empty if the event has no own date) |
ends_at |
End, in the event’s time zone (empty if the event has no own date) |
date |
Pre-formatted start date (empty if no date) |
time |
Pre-formatted start–end time range (empty if no date) |
venue |
Venue object — name, description, address, time_zone, iana, images |
capacity |
Total event capacity |
tags |
Event tag names |
images.banner |
Event banner image URL, if one is set |
ticket fields
The per-variant ticket JSON object contains:
| Field | Description |
|---|---|
name |
Ticket name |
kind |
Ticket kind (defaults to ticket) |
quantity |
Group size — number of tickets issued per unit sold |
service_fee |
The booking fee for this ticket, as a single amount |
limits.min / limits.max |
Minimum and maximum purchasable per order |
ticket_type.name |
Ticket type name |
ticket_type.fields |
Which attendee fields are collected (name, email, photo), each with included and required |
ticket_type.questions |
Custom attendee questions — each with id, kind, name, position, required, and options |
Stability
These two payloads are frozen. The fields documented above are what the app writes, they are safe to build on, and we do not plan to change their shape.
The trade-off is that the set is closed: we are not adding fields to event_v2 or ticket. If you need event data that is not listed above, the metaobject graph below is where it lives.
Advanced: the metaobject graph
Alongside these metafields the app maintains a structured metaobject graph, and it is a much better data source than the JSON above. Where event_v2 repeats the entire event on every variant as an opaque blob, the graph gives you typed fields and real references: an event links to its sessions, a session links to its tiers and its location, a tier links to the ticket it is a variant of. It is the same data the app’s own storefront blocks read.
It is unsupported, and you use it at your own risk. It is the app’s internal model. It carries no version number, it is not covered by the stability promise above, and it can change without notice or a migration path. In practice the core records (event, session, tier, ticket, location, address) have been stable for some time and we expect them to stay that way, but that is an observation, not a commitment. If you build on it, pin your integration to a review whenever you update the app.
If that trade is worth it to you, three things to know:
Finding the type names. The app’s metaobject types carry an app-specific prefix, so the literal type string is not session but something of the form app--000000--session. Read the exact strings for your store from Settings > Custom data > Metaobjects in your admin, or by querying metaobjectDefinitions on the Admin API. The same applies to the app’s metafield namespaces.
Where to enter the graph. From a product, read the event metafield in the app’s namespace, which references the event record. From a variant, read the session and tier metafields. Some other product-level metafields are deliberately not exposed to the Storefront API, so start from those three. Your storefront access token needs the unauthenticated_read_metaobjects scope.
Checking freshness before you render. A sync writes many records, so a product can be read halfway through one. Two counters let you detect it: event_sync_version on the product, and sync_version on the event record it references. Compare them and only render when they agree:
- both present and equal: the product is fully synced, safe to render
- present but different, or only one present: a sync is in flight or failed partway, skip the product and let the next sync settle it
- both absent: the event predates these counters, treat it as fine
Skipping a product for a few seconds is much better than rendering an event with half its sessions missing.
Related articles
FAQ
Can I read these metafields from the Storefront API or a headless build (Hydrogen, Next.js)?
Yes, and you can enable it yourself. Nothing is needed from us.
The app writes raw metafield values without creating metafield definitions. Liquid themes read raw metafields, which is why the examples above work in a normal theme, but the Storefront API only returns metafields that have a definition with storefront access set to PUBLIC_READ. That is why a headless build sees empty values.
Because event_ticketing is not an app-reserved namespace, those definitions are yours to create. Run this once against the GraphQL Admin API, or create the equivalent definitions under Settings > Custom data in your Shopify admin:
mutation {
a: metafieldDefinitionCreate(definition: {
namespace: "event_ticketing", key: "event_v2", name: "Event",
type: "json", ownerType: PRODUCT,
access: { storefront: PUBLIC_READ }
}) { createdDefinition { id } userErrors { field message } }
b: metafieldDefinitionCreate(definition: {
namespace: "event_ticketing", key: "event_v2", name: "Event",
type: "json", ownerType: PRODUCTVARIANT,
access: { storefront: PUBLIC_READ }
}) { createdDefinition { id } userErrors { field message } }
c: metafieldDefinitionCreate(definition: {
namespace: "event_ticketing", key: "ticket", name: "Ticket",
type: "json", ownerType: PRODUCTVARIANT,
access: { storefront: PUBLIC_READ }
}) { createdDefinition { id } userErrors { field message } }
}
Keep the type as json, which is what the app writes. A definition applies to values that are already on your products, so there is no need to re-sync. If a product still looks empty afterwards, it has not been synced yet: open the event in the app and save it once.
If one of those returns a TAKEN error, a definition for that key already exists on your store. Run metafieldDefinitionUpdate for it instead, with the same namespace, key and owner type, setting access: { storefront: PUBLIC_READ }.
This step is only needed for the Storefront API. Liquid themes read these metafields without any definition, which is why the theme examples above work on a standard store with no setup.
What’s the difference between the product event_v2 and the variant event_v2?
For a one-time event they describe the same date. For a multi-date or recurring event the product event_v2 is the overall event, and each variant’s event_v2 is the specific date that variant sells. Read from the variant when you need the date a customer is actually buying.
Why is starts_at (or date / time) empty?
An event without its own single date — the parent record of a multi-date event, for example — has no start or end, so those fields are empty. Read the per-variant event_v2, which carries the concrete date, and null-check in your theme.
Should I store my own data in the event_ticketing namespace?
No. The app rewrites these metafields whenever the event is saved, which would overwrite anything you add. Use your own namespace for custom data.
Creating metafield definitions over the app’s keys is fine, and is exactly what the Storefront API answer above asks you to do. The rule is about values: do not write your own values into event_ticketing.
Should I use the metafields or the metaobject graph?
Use the metafields if you want a stable contract and the fields documented here are enough. They will not change, and they are the right default for a Liquid theme.
Use the graph if you are building something more involved, especially a headless storefront: the data is typed and properly linked rather than duplicated as JSON, and it carries event data the metafields do not expose. The cost is that it is unsupported and can change without notice. See Advanced: the metaobject graph.
quantity isn’t the number of tickets for sale — what is it?
quantity on the ticket object is the group size: how many tickets are issued for each unit sold (a “table of 8” issues 8). Sellable inventory is the variant’s normal Shopify inventory, not this field. Per-order purchase limits are limits.min and limits.max.