# Use metadata in workflow templates

Source: https://developer.nylas.com/docs/cookbook/workflows/metadata-in-workflows/

One booking email template can show a different logo for each of your customers. Store each customer's logo URL in a hidden field on their Scheduler configuration. Read the URL in the template, and show a default logo when the URL is missing.

## Which metadata can a workflow template read?

A workflow template can read custom values from 2 places. Scheduler bookings carry the configuration's hidden `metadata` fields on all 5 `booking.*` triggers. Events carry their `metadata` object on `event.created` and `event.updated`.

The two share a name but work differently. An event's `metadata` object is key-value data you write through the Events API. A Scheduler `metadata` field is a hidden field on the booking form: the scheduling page sends its value with each booking, and the guest can read it in the browser.

| Source | Where you set it | Template variable |
| --- | --- | --- |
| Scheduler booking | A `type: "metadata"` field in the configuration's `scheduler.additional_fields` | `booking_info.additional_fields.<key>` |
| Event | `metadata` on an [Events API](/docs/reference/api/events/) request | `metadata.<key>` |

Other objects that accept [metadata](/docs/dev-guide/metadata/) aren't a reliable source for customer email. Message workflows send to the grant owner. A `message.created` payload can arrive without the message's metadata. Tracking triggers such as `message.opened` carry the message's tracking `label`, not its metadata.

## Add a metadata field to a Scheduler configuration

A Scheduler configuration has no top-level `metadata` object. Instead, add an entry to `scheduler.additional_fields` with `type: "metadata"` and the value in `default`. The scheduling page doesn't show the field to the guest, but it sends the `default` value with each booking.

For a logo, host the image anywhere, such as your own storage or the customer's website, and put its public URL in the field. Nylas stores only the URL.

`PUT /v3/grants/{grant_id}/scheduling/configurations/{id}` merges the body into the existing configuration. `scheduler.additional_fields` is the exception. The request replaces it as a whole, so include every field the configuration already has.

```bash
curl -X PUT "https://api.us.nylas.com/v3/grants/$GRANT_ID/scheduling/configurations/$CONFIGURATION_ID" \
  -H "Authorization: Bearer $NYLAS_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "scheduler": {
      "additional_fields": {
        "notify_individually": {
          "label": "Notify individually",
          "type": "metadata",
          "required": false,
          "default": "true"
        },
        "logo_url": {
          "label": "Logo URL",
          "type": "metadata",
          "required": false,
          "default": "https://cdn.example.com/customers/acme/logo.png"
        }
      }
    }
  }'
```

## How does the value get onto a booking?

Bookings made on a scheduling page get the value automatically. When a guest books through the hosted page or the `nylas-scheduling` component, the page sends each hidden `metadata` field's `default` along with the guest's answers. The component's `bookingInfo.additionalFields` overrides the `default`, so 2 embeds of one configuration can pass 2 different logos.

If your own code creates bookings with `POST /v3/scheduling/bookings`, include the value in the request. The Bookings API doesn't read `default`:

```json
{
  "start_time": 1790182800,
  "end_time": 1790184600,
  "guest": { "name": "Leyah Miller", "email": "leyah@example.com" },
  "participants": [],
  "additional_fields": {
    "notify_individually": "true",
    "logo_url": "https://cdn.example.com/customers/acme/logo.png"
  }
}
```

The Bookings API checks that each key exists in the configuration and returns `400 Additional field '<key>' not found in configuration` if it doesn't. It doesn't check the value.

## Use the value in a template with a fallback

Always include a fallback. A booking has no value in 3 cases: your code booked through the API without the value, the booking is older than the field, or the trigger is `booking.reminder` for a group event. That reminder sends `additional_fields` as `null`.

Templates render in [strict mode](/docs/cookbook/workflows/workflow-not-sending/), so a missing value stops the email from sending. Check `additional_fields` first, then check the key. Put the default in each `{{else}}`:

```handlebars
{{#if booking_info.additional_fields}}{{#if booking_info.additional_fields.logo_url}}
  <img src="{{booking_info.additional_fields.logo_url}}" alt="Company logo" style="max-width: 150px;" />
{{else}}
  <img src="https://cdn.example.com/brand/default-logo.png" alt="Company logo" style="max-width: 150px;" />
{{/if}}{{else}}
  <img src="https://cdn.example.com/brand/default-logo.png" alt="Company logo" style="max-width: 150px;" />
{{/if}}
```

A booking with `logo_url` shows that logo in every copy of the email. A booking without it shows the default logo. The same pattern works for other values, such as a `brand_color` in an inline style.

Each booking keeps the values it was created with. If you change a field's `default`, only new bookings get the new value.

## Use event metadata in an event template

An `event.created` or `event.updated` template reads event metadata at `metadata.<key>`. Each event holds up to 50 metadata pairs, with keys up to 40 characters and values up to 500. Check that `metadata` exists before you read a key, as in the booking example.

Two sets of keys have fixed meanings:

- **`notify_individually`**: when the value is the string `"true"`, the workflow sends one message per participant and fills in `recipient`. Any other value, including the boolean `true`, sends one message to all participants.
- **Keys Scheduler sets**: events that Scheduler creates for non-group Configurations carry `source` (`scheduler`), `key5` (the configuration ID), `scheduler_last_start`, `scheduler_last_end`, and `scheduler_updated_at`. Don't overwrite them.

## Which values are safe to store in metadata?

Store only values a guest could see and change without harm, such as logos, colors, and display names. Scheduling pages load the configuration's hidden fields in the browser, so a guest can read them and change what the page submits.

Don't use hidden fields for routing, pricing, authorization, or secrets. Workflows also can't skip a send based on a field's value. For values that must be authoritative, handle the [booking webhook](/docs/reference/notifications/scheduler/) yourself. All 5 `booking.*` webhooks include `configuration_id`. Use it to look up the value in your own database.

## What's next

- [Customize Scheduler email for your app](/docs/cookbook/workflows/scheduler-booking-email/) for the full booking templates
- [Customize Scheduler email for each user](/docs/cookbook/workflows/scheduler-email-per-user/) when each customer needs their own template or sender
- [Email attendees when a meeting is booked](/docs/cookbook/workflows/event-booked-email/) for the `event.created` payload
- [Using metadata in Nylas](/docs/dev-guide/metadata/) for metadata limits, reserved keys, and filtering
- [Templates and workflows](/docs/v3/email/templates-workflows/) for template variables and helpers