Action links

Open your website from a Brevo record, with the record's details as query parameters
View as Markdown

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.

An action link ("View invoices") shown in a Contact record's header menu

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 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

$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

1{
2 "app_id": "e22eb778-a2a8-488a-a5e8-466b6dad9385",
3 "app_name": "my-action-link",
4 "version": "1",
5 "logo_uri": "https://example.com/logo.png",
6 "distribution_type": "private",
7 "app_type": "ui",
8 "auth": {},
9 "ui_app": {
10 "extension_type": "actionLink",
11 "surface_point_list": [
12 {
13 "surface_point_name": "contactDetails.header.menu",
14 "context": ["recordId", "recordType", "userId", "locale", "clientId", "extId"],
15 "label": "View in my app",
16 "more_info": "Opens this contact in Acme",
17 "redirect_link": "https://example.com/open"
18 }
19 ]
20 }
21}
FieldRequiredNotes
authyesAlways {} for a UI app
ui_app.extension_typeyesactionLink for this guide, opens redirect_link in a new tab
surface_point_list[].surface_point_nameyesWhere the link appears, as a dotted slug (e.g. contactDetails.header.menu); validated by Brevo, not locally
surface_point_list[].labelyesMax 48 characters
surface_point_list[].more_infonoMax 255 characters
surface_point_list[].redirect_linkyeshttps://, or http://localhost for local testing
surface_point_list[].contextnoWhich record fields to send: recordId, recordType, userId, locale, clientId, extId
surface_point_list[].sizeno{ "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 and brevo app install:

$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 (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 pointAppears as
contactDetails.overview.mainAn inline card on the record’s overview page
contactDetails.overview.sidebarA card in the record’s sidebar
contactDetails.header.menuA 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:

1{
2 "surface_point_list": [
3 {
4 "surface_point_name": "contactDetails.header.menu",
5 "label": "View in my app",
6 "more_info": "Opens this contact in Acme",
7 "redirect_link": "https://example.com/open"
8 },
9 {
10 "surface_point_name": "contactDetails.overview.sidebar",
11 "label": "Acme account",
12 "more_info": "Order history and account status",
13 "redirect_link": "https://example.com/sidebar"
14 }
15 ]
16}