Sidebar Chatbot Promo
sidebar_chatbot_promo renders a promotional card in the genai chat sidebar,
above the summarize button in the sidebar footer.
The chatbot-promo custom element only renders and reports interactions. All
messaging-system behavior — content resolution, impressions, button actions and
teardown — lives in
SidebarChatBotPromo.sys.mjs,
which ASRouter dispatches to based on the message template.
Content
Field |
Purpose |
|---|---|
|
Visual style, passed through to |
|
Card heading |
|
Card body text |
|
Label plus the action to run when clicked |
|
Label plus the action to run when dismissed |
Use additional_button, not secondary_button. The onboarding provider
pre-translates secondary_button labels via Fluent, which breaks on labels that
aren’t { string_id }.
Text fields use the localizableText shared definition: either a plain string
or { "string_id": "..." }. The { "raw": "..." } form used by multistage
screen content is not valid here.
Messages deployed from a Nimbus experiment usually use
experiment localization:
an $l10n object carrying id, text, and comment. The schema doesn’t need
to account for this — Nimbus substitutes the localized string before the message
is validated.
A message whose content resolves to nothing at all is a no-op — the surface is wired up, but nothing renders.
Dismissal and blocking
This surface does not block messages on its own. Both buttons run whatever action the message specifies and then hide the card; the message remains eligible until its frequency cap is exhausted.
If the promo should never return once the user has interacted with it, the
message must say so with
BLOCK_MESSAGE.
Use MULTI_ACTION on the primary button to both run the real action and block:
"primary_button": {
"label": { "string_id": "some-fluent-id" },
"action": {
"type": "MULTI_ACTION",
"data": {
"actions": [
{ "type": "FXA_AIWINDOW_SIGNIN_FLOW" },
{ "type": "BLOCK_MESSAGE", "data": { "id": "MY_PROMO_ID" } }
]
}
}
},
"additional_button": {
"label": { "string_id": "some-other-fluent-id" },
"action": { "type": "BLOCK_MESSAGE", "data": { "id": "MY_PROMO_ID" } }
}
Targeting
The message is routed by the
sidebarToolOpened
trigger, which fires for every sidebar tool. Targeting must narrow it to the
chat sidebar:
view == 'viewGenaiChatSidebar' && 'browser.ml.chat.provider'|preferenceValue != ''
The browser.ml.chat.provider clause keeps the promo from showing during
onboarding, before a chatbot has been selected — otherwise it would render over
the onboarding overlay and count impressions the user never really saw.
SidebarChatBotPromo.showPromo also returns early when that pref is empty, so
test providers that override targeting stay gated too.
sidebarToolOpened is fired from sidebar-main.mjs when the user opens a tool.
The promo appears the next time the chat sidebar view is opened.
Targeting is also rewritten automatically: an expression that doesn’t mention
isAIWindow becomes ((<your expression>) && !isAIWindow), so a message is
Classic-window-only unless it opts in explicitly.
Example JSON
{
"id": "EXAMPLE_SIDEBAR_CHATBOT_PROMO",
"template": "sidebar_chatbot_promo",
"groups": [],
"targeting": "view == 'viewGenaiChatSidebar' && 'browser.ml.chat.provider'|preferenceValue != ''",
"trigger": { "id": "sidebarToolOpened" },
"frequency": {
"custom": [{ "period": 604800000, "cap": 3 }]
},
"content": {
"type": "default",
"heading": { "string_id": "example-promo-heading" },
"message": { "string_id": "example-promo-message" },
"primary_button": {
"label": { "string_id": "example-promo-primary-button" },
"action": { "type": "FXA_AIWINDOW_SIGNIN_FLOW" }
},
"additional_button": {
"label": { "string_id": "example-promo-dismiss-button" },
"action": { "type": "BLOCK_MESSAGE", "data": { "id": "EXAMPLE_SIDEBAR_CHATBOT_PROMO" } }
}
}
}
Schema
Testing
No sidebar_chatbot_promo message ships enabled.
Development
Set browser.newtabpage.activity-stream.asrouter.devtoolsEnabled to true,
open about:asrouter, find (or edit) a sidebar_chatbot_promo message, and
click Show. “Show” passes force, so it opens the chat sidebar to the right
tool for you. The Share button copies an about:messagepreview URL that
renders the promo for anyone who also has the devtools pref enabled.
QA verification
A test message is available through PanelTestProvider:
Set
browser.newtabpage.activity-stream.asrouter.devtoolsEnabledtotrue.Set
browser.newtabpage.activity-stream.asrouter.providers.panel_local_testingto:{"id":"panel_local_testing","type":"local","localProvider":"PanelTestProvider","enabled":true,"cohort":"SHOW_TEST"}
Select a chatbot provider — the promo is gated on
browser.ml.chat.providerand won’t show during onboarding.Open the sidebar and click the chatbot icon. The promo appears the next time the chat sidebar view is opened.
Supply a real message via a Nimbus experiment for production.