> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.brevo.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.brevo.com/_mcp/server.

# Action links

An action link is a UI component you add to a Brevo record: a menu entry or a card. Clicking it opens a URL you host, in a new tab, with the record's details passed along as query parameters.

Under the hood, an action link is a **UI app**: an `app-config.json` with an empty `auth: {}` and a populated `ui_app` block, instead of the `auth.scopes` / `auth.redirect_uris` an [OAuth app](/docs/oauth) uses. The presence of `ui_app` is what makes it a UI app. There's no OAuth callback and no local server to run.

## Create one

```bash
brevo app create
```

UI apps can only be created interactively. The prompts walk you through:

1. **App type**: choose **UI app**.
2. **Integration type**: currently only **Link** is offered (iframe-based extensions aren't ready in the guided flow yet).
3. **Record page**: which Brevo record type the link appears on (e.g. Contact, Deal).
4. **Placement**: where on that record page.
5. **Label**: the menu entry's text, or the card's button (48 characters max).
6. **Supporting text** *(optional)*: a second line of description (255 characters max).
7. **Destination URL**: the page the link opens.

This authors exactly one placement. To add more, edit `app-config.json` by hand and run `brevo app upload`.

## `app-config.json`

```json
{
  "app_id": "e22eb778-a2a8-488a-a5e8-466b6dad9385",
  "app_name": "my-action-link",
  "version": "1",
  "logo_uri": "https://example.com/logo.png",
  "distribution_type": "private",
  "app_type": "ui",
  "auth": {},
  "ui_app": {
    "extension_type": "actionLink",
    "surface_point_list": [
      {
        "surface_point_name": "contactDetails.header.menu",
        "context": ["recordId", "recordType", "userId", "locale", "clientId", "extId"],
        "label": "View in my app",
        "more_info": "Opens this contact in Acme",
        "redirect_link": "https://example.com/open"
      }
    ]
  }
}
```

| Field                                     | Required | Notes                                                                                                         |
| ----------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `auth`                                    | yes      | Always `{}` for a UI app                                                                                      |
| `ui_app.extension_type`                   | yes      | `actionLink` for this guide, opens `redirect_link` in a new tab                                               |
| `surface_point_list[].surface_point_name` | yes      | Where the link appears, as a dotted slug (e.g. `contactDetails.header.menu`); validated by Brevo, not locally |
| `surface_point_list[].label`              | yes      | Max 48 characters                                                                                             |
| `surface_point_list[].more_info`          | no       | Max 255 characters                                                                                            |
| `surface_point_list[].redirect_link`      | yes      | `https://`, or `http://localhost` for local testing                                                           |
| `surface_point_list[].context`            | no       | Which record fields to send: `recordId`, `recordType`, `userId`, `locale`, `clientId`, `extId`                |
| `surface_point_list[].size`               | no       | `{ "width": "280px", "height": "160px" }`: `px` or `%` up to 100, shrink-only                                 |

`brevo app create` auto-seeds both `context` and `size` into `app-config.json` for you; edit the values by hand if you need something different.

Don't set `link_target` yourself. Brevo stamps it during upload, and a value already in the file shows up as drift you can't clear.

## Receiving the record's details

Brevo appends the record's context to your URL as query parameters. The path itself is never templated:

```
https://example.com/open?recordId=123&recordType=contact&userId=456
```

These values arrive through the browser, so validate them like any other user input.

## Push and install

See the CLI reference for the full flag list on [`brevo app upload`](/docs/cli-reference#brevo-app-upload) and [`brevo app install`](/docs/cli-reference#brevo-app-install):

```bash
brevo app upload    # push app-config.json, shows a diff before confirming
brevo app install    # make it available in your account
```

Pass an account ID to either command to target a specific sub-account: `brevo app install <account-id>`. There's no separate publish step. A successful upload changes what every account the app is installed in renders immediately, with no reinstall needed. Remove it with [`brevo app uninstall`](/docs/cli-reference#brevo-app-uninstall) (same optional account ID).

A UI app has no local server to run. `brevo app scaffold` and `brevo app start` don't apply here.

## Adding more placements

`brevo app create` authors exactly one placement. To add another, append a `surface_point_list` entry with its own `label` and `redirect_link`, then run `brevo app upload` again. Slot names are validated by Brevo, not locally; an unregistered `surface_point_name` fails at upload.

On a Contact record, three placements are currently supported:

| Surface point                     | Appears as                                   |
| --------------------------------- | -------------------------------------------- |
| `contactDetails.overview.main`    | An inline card on the record's overview page |
| `contactDetails.overview.sidebar` | A card in the record's sidebar               |
| `contactDetails.header.menu`      | A dropdown item in the record's header menu  |

The same three placements exist for other record types: replace `contactDetails` with `companyDetails` or `dealDetails`.

Here's `ui_app.surface_point_list` with two placements: a header menu item and a sidebar card:

```json
{
  "surface_point_list": [
    {
      "surface_point_name": "contactDetails.header.menu",
      "label": "View in my app",
      "more_info": "Opens this contact in Acme",
      "redirect_link": "https://example.com/open"
    },
    {
      "surface_point_name": "contactDetails.overview.sidebar",
      "label": "Acme account",
      "more_info": "Order history and account status",
      "redirect_link": "https://example.com/sidebar"
    }
  ]
}
```