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

# Merge Tags

> Reference for Brew merge tags, covering syntax, fallbacks, which contact and trigger fields resolve, and what renders when one doesn't.

Merge tags pull contact and trigger data into an email at send time. They are Brew's dynamic variables: write a field name in the copy, and each recipient sees their own value. This page is the reference for the syntax and for which fields resolve.

<Note>
  **One feature, three names.** These docs and the contacts API call them **merge tags**. The automation surface calls the same thing **variables** (a trigger-payload variable, a body token), and the app's field chooser is the **variable picker**. They all mean `{{ fieldName }}`.
</Note>

## Syntax

A merge tag is a field name inside double braces.

| Form     | Example                    | Renders                          |
| -------- | -------------------------- | -------------------------------- |
| Plain    | `{{ firstName }}`          | `Alex`                           |
| Fallback | `{{ firstName \| there }}` | `there`, when the field is empty |

Spacing inside the braces doesn't matter: `{{firstName}}` resolves exactly like `{{ firstName }}`, with or without a fallback. These docs write the spaced form because it's easier to read.

<Warning>
  Variable names are case-sensitive. `{{ firstName }}` matches a field named `firstName`, not `firstname`. Triple braces (`{{{firstName}}}`) are not merge tags.

  In a one-off Email they ship as literal text. In an automation the publish validation flags
  them as a blocking issue.
</Warning>

### That Is the Whole Syntax

Two forms, plain and fallback. Brew has no conditionals, no loops, and no formatting filters, so none of these work:

```text theme={null}
{% if plan == "pro" %}...{% endif %}     Not supported
{% for item in items %}...{% endfor %}   Not supported
{{ createdAt | date: "%B %d" }}          Not supported
```

If you need copy that changes with a condition, branch the flow instead of the template. A [Split node](/create-emails/automations) sends a different email down each path, which is also easier to read in analytics than one email with hidden variants. To show a formatted date, store it the way you want it read as a custom property.

## Where They Work

* Email body
* Subject line
* Preview text
* `fromName` and `replyTo`, on automation sends
* Filter and Split node conditions, which reference trigger payload fields by name

In the [TypeScript SDK](/sdks/typescript/resources), the `subject` and `previewText` on a `sendEmail` node take the same syntax. They are evaluated against the trigger payload at fire time.

The surfaces fail differently when a field is missing. An unresolvable tag in the body, subject, preview text, `fromName`, or `replyTo` renders empty and the send goes out.

An unresolvable reference in a Filter or Split condition blocks the automation from publishing. A condition that can't evaluate can't route contacts.

## What You Can Reference

### Contact Properties

Every contact carries three default properties that resolve as merge tags.

| Merge tag         | Example           |
| ----------------- | ----------------- |
| `{{ email }}`     | `philip@brew.new` |
| `{{ firstName }}` | `Philip`          |
| `{{ lastName }}`  | `Sørensen`        |

`subscribed`, `created_at`, and `updated_at` are filter and sort fields rather than merge tags. Brew keeps them out of the variable picker because they are bookkeeping. If you want a date in email copy, store it as a custom property.

[Manage Contacts](/audience/manage-contacts) owns the full contact field list and the reserved names you can't use for custom properties.

### Custom Properties

Any custom property resolves by its name, so a property named `plan` is `{{ plan }}`. Custom properties are the way to personalize on anything Brew doesn't track by default.

### Trigger Payload Fields

In an automation, every field declared on the trigger event resolves by name. An event carrying `orderId` gives you `{{ orderId }}`.

Keep payloads flat. Nested objects are harder to reference in email content, which is why [Build an Automation](/create-emails/build-an-automation) recommends a flat schema.

<Note>
  Connected sources declare their own payload fields, and each integration page lists what its events carry. See [Stripe](/integrations/import/stripe), [Clerk](/integrations/import/clerk), [Shopify](/integrations/import/shopify), [Stytch](/integrations/import/stytch), [Supabase](/integrations/import/supabase), [WorkOS](/integrations/import/workos), and [RevenueCat](/integrations/import/revenuecat).
</Note>

## When a Tag Does Not Resolve

An unresolved tag renders as empty rather than as an error, and the email still sends. That makes a typo silent, so it's worth checking a tag before a large send.

Three things cause this:

<AccordionGroup>
  <Accordion title="The name does not match" icon="font">
    Names are case-sensitive and must match the declared field exactly. `{{ firstname }}` does not match a field named `firstName`.
  </Accordion>

  <Accordion title="The field is not declared on the event" icon="list-check">
    Some fields exist only on certain events from a provider. Stripe's `invoiceNumber`, for example, is declared on `invoice.created`, `invoice.paid`, and `invoice.payment_failed`, but not on `invoice.upcoming`.
  </Accordion>

  <Accordion title="The field is internal" icon="eye-slash">
    Brew strips some provider fields before an automation sees them, so they never resolve even though the provider sends them. Stripe's opaque object IDs work this way. The Stripe customer ID still reaches the contact as the `stripe_customer_id` custom field, so filter on it there.
  </Accordion>
</AccordionGroup>

Use a fallback wherever an empty value would read badly. `Hi {{ firstName }},` becomes `Hi ,` for a contact with no name. `Hi {{ firstName | there }},` reads correctly either way.

## Coming From Another Platform

Brew has no per-send data payload. Sending an email takes an audience or a list of
recipients, not a bag of variables. A request that carries its own template data has
no equivalent here.

Data reaches an email one of two ways instead:

| You want to personalize on      | Put the data here                      | Then reference  |
| ------------------------------- | -------------------------------------- | --------------- |
| Something true about the person | A contact property, standard or custom | `{{ plan }}`    |
| Something true about the moment | A field declared on the trigger event  | `{{ orderId }}` |

So the Liquid-style question "how do I pass variables with the send" becomes "is this
about the person or about the event?" Store it on the contact, or declare it on the
trigger and send it when you fire the event. [Build an Automation](/create-emails/build-an-automation)
walks through declaring a payload, and [Add Contacts](/audience/add-contacts) covers
getting properties onto contacts in the first place.

## Check Before You Send

Send yourself a test and confirm each tag resolves, and that the fallback reads naturally when it does not. [QA an Email Before a Big Send](/recipes/qa-before-a-big-send) covers the wider pre-send routine.

For automations there is a machine check too. Publish with `dryRun: true` and the response's `blockingIssues[]` names every unresolvable reference, with its node, surface, and variable.

Fatal issues (conditions, triple braces) block publish; advisory ones (subject, preview text, body tags) would render empty. Run it after swapping a trigger or removing a payload field, which is exactly when references break.

Personalizing on a field most of your contacts have not filled in means most of them
see the fallback.

`GET /v1/fields?include=coverage` reports the percentage of contacts
holding a non-empty value for each field. Check that before you build an email around a field. Pass `audienceId` to scope the stats to
the audience you're sending to.

## Need Help?

Our team is ready to support you at every step of your journey with Brew. Choose the option that works best for you:

<Tabs>
  <Tab title="Self-Service Tools">
    <CardGroup cols="2">
      <Card title="Search Documentation" icon="magnifying-glass" color="#c44925">
        Type in the "Ask any question" search bar at the top left to instantly find relevant documentation pages.
      </Card>

      <Card title="ChatGPT/Claude Integration" icon="robot" color="#c44925">
        Click "Open in ChatGPT" at the top right of any page to explore it further with ChatGPT or Claude.
      </Card>
    </CardGroup>
  </Tab>

  <Tab title="Talk to Our Team">
    <CardGroup cols="2">
      <Card title="Schedule a Call" icon="calendar" color="#c44925" href="https://calendar.google.com/calendar/u/0/appointments/schedules/AcZssZ1iYoRUG1J792XQpbuQLjSRRDupr7MwraFK-HQRCtTYdBmrQi8nZu2qXfzKQigb8gbKJK3KN3-R">
        Book time with our founders for personalized guidance on strategy, best practices, or complex implementation questions.
      </Card>

      <Card title="Call Us Directly" icon="phone" color="#c44925">
        Need immediate assistance? Reach us at **+1-(332)-203-2145** for urgent issues or time-sensitive questions.
      </Card>

      <Card title="Slack Channel" icon="slack" color="#c44925">
        Our preferred support channel. You'll receive an invite after signup for direct founder support and fast responses.
      </Card>

      <Card title="Email Support" icon="envelope" color="#c44925" href="mailto:support@brew.new">
        Contact us at **[support@brew.new](mailto:support@brew.new)** for detailed inquiries or if you prefer not to use Slack.
      </Card>
    </CardGroup>
  </Tab>
</Tabs>
