# Merge Tags Hook

> Add your own merge tags to broadcasts and sequence emails. Values are fetched in batches. Full example and common mistakes.

Source: https://sendbroadcast.net/docs/plugins-hook-merge-tags

> Coming in Broadcast 3.0. Until then, this page applies only to the Upcoming (edge) build.

Use this hook to put a value in an email, different for each person.

## How it works

You write a tag in an email:

```liquid
You are a {{ plugins.local_loyalty_tier.tier }} member.
```

The middle part is your plugin ID, with `.` and `-` written as `_`. So `local.loyalty-tier` becomes `local_loyalty_tier`.

When the email is sent, Broadcast calls your function once for each batch of up to 100 people. You return one value for each person.

## Full example: loyalty tier

This plugin works out a tier from a custom field named `orders`.

`manifest.json`:

```json
{
  "id": "local.loyalty-tier",
  "name": "Loyalty tier",
  "version": "1.0.0",
  "requires": { "broadcast": ">=3.0.0 <4.0.0", "pluginApi": "1" },
  "permissions": {
    "subscribers": "read"
  },
  "hooks": {
    "mergeTags": ["tier"]
  }
}
```

`dist/main.js`:

```javascript
function tierFor(orders) {
  if (orders >= 10) return "Gold";
  if (orders >= 3) return "Silver";
  return "Bronze";
}

export default definePlugin({
  mergeTags: {
    async tier({ subscribers }) {
      const values = {};
      for (const person of subscribers) {
        const orders = Number(person.custom_data?.orders ?? 0);
        values[person.id] = tierFor(orders);
      }
      return values;
    },
  },
});
```

## What you get and return

You get this:

```json
{
  "name": "tier",
  "subscribers": [
    { "id": 12, "email": "ada@example.com", "first_name": "Ada", "last_name": null, "custom_data": { "orders": 12 } }
  ]
}
```

Each person always has `id` and `email`. `first_name`, `last_name` and `custom_data` are there only if the plugin has a `subscribers` permission.

You return an object, with the subscriber ID as the key:

```json
{ "12": "Gold" }
```

A person with no value gets an empty string. A value can be a string or a number, up to 10,000 characters.

## HTML values

A value is plain text. Broadcast escapes it, so `**` shows as those characters. To return HTML, you need two changes in the manifest:
```json
"permissions": { "output": "html" },
"hooks": { "mergeTags": [{ "name": "badge", "output": "html" }] }
```

 This is a sensitive permission. Broadcast cleans the HTML before it is sent, and it removes scripts. In a subject line, the tags are removed. ## Personal links A tag whose value is a link that is different for each person, for example a referral link, is declared with `tracked: false`:
```json
"hooks": { "mergeTags": [{ "name": "referral_link", "tracked": false }] }
```

 Click tracking leaves that link as it is. Without this, a tracked copy of each person's link is made, one redirect row per person, and the click report shows one line per person. ## Common mistakes **You make one call per person.** You have 5 seconds for 100 people. `ctx.broadcast.subscribers.get` in a loop is too slow. Use the data you are given. Fetch one thing and use it for the whole batch. **You expect the send to wait for you.** If your function is slow or fails, the value is empty and the email goes out without it. A failure still counts toward the 20 failures that turn a plugin off. Write your text so that it still reads well with an empty value. **You expect it in a test send.** Plugin hooks run in broadcasts and sequence emails. They do not run in transactional emails or test sends. To see a real value, send a broadcast to a small segment. **You name a tag `actions`.** `plugins..actions` is the place for [public action links](https://sendbroadcast.net/docs/plugins-hook-public-actions).**
