Feature: Custo-manifest (Rainbow Extensions) — GUI extensions
Licence: Business licence minimum
You can use this article to create Rainbow GUI extensions that add buttons, widgets and embedded web applications to Rainbow. You will see which extension points are available and how to choose the right placement for your workflow.
Overview
GUI extensions let you add custom buttons, display widgets, and embedded web applications to the Rainbow interface. These extensions require a Business licence minimum.
What you can do with GUI extensions:
- Add clickable action buttons in contact lists, call log, communication card, and bubbles
- Display custom content (CRM data, tickets, reports) automatically in call pop-ups and contact panels
- Embed a third-party web application directly in the Rainbow main panel
Extension point types
An extension point (EP) is a named slot in the Rainbow interface where a custom action can be attached. Each EP has a unique key (e.g. on-contact-list-actions), a fixed placement in the UI, and rules governing which fields and variables are available. The custo-manifest declares one or more actions under each EP key.
Buttons — clickable buttons
An icon-button is inserted at a specific location of the Rainbow UI. On click, an action is executed and, optionally, its result is displayed in a popup (Markdown or Adaptive Card). Up to 3 buttons per extension point.
| Extension point | Button placement |
|---|---|
| on-incoming-pbx-call-button-actions | Incoming-call pop-up |
| on-call-log-list-actions | Each row of the call log |
| on-contact-list-actions | Each row of the contact list |
| on-contact-detail-actions | Contact detail panel |
| on-communication-card-actions | Call manager bar |
| on-bubble-actions | Bubble property menu |
| on-bubble-conference-actions | Telephone conference menu |
| on-user-menu-actions | User menu (BETA, Desktop / Web — not Teams) |
Displays — automatic information display
Content (Markdown or Adaptive Card) is automatically inserted at a specific location of the UI — with no user interaction. The content is fetched when the placement becomes visible.
| Extension point | Display placement |
|---|---|
| on-incoming-pbx-call-display-actions | In the incoming-call pop-up |
| on-contact-card-display-actions | In the contact detail panel |
| on-main-widget-display-actions | Widget in the main Rainbow panel |
| on-main-display-actions | Message in the Teams web app |
Frame display — third-party web application
Embeds a third-party web application inside a Rainbow UI frame. With Popup: true, the application opens in a browser window instead of being embedded.
| Extension point | Placement |
|---|---|
| on-main-window-actions | Frame in the main Rainbow window |
Configuring a GUI extension
The custo-manifest is a JSON object with the following structure:
{
"version": "2",
"jsonType": "Rainbow-custo-manifest",
"extension-points": {
"<EXTENSION-POINT-KEY>": [
{
"name": "...",
"icon": "...",
"command": {
"url": "...",
"params": "...",
"commandType": "rest",
"method": "GET"
},
"responseType": "markdown"
}
]
}
}Global rules:
- version must be "2".
- jsonType must be "Rainbow-custo-manifest". Inside a ZIP, this field identifies which JSON file is the manifest.
- Each key under extension-points is an array — up to 3 actions maximum per extension point.
- Each action must contain a valid command object. name and icon are mandatory depending on the extension point (see §3.4).
Action fields
| Field | Position | Description |
|---|---|---|
| name | root | Text shown (button label, widget title, menu label). Mandatory depending on the EP. |
| icon / iconId | root | Button icon (URL, ${custo.path}\... or built-in sprite). Mandatory for icon buttons. |
| tooltip | root | Tooltip text. Optional. |
| command | root | Command object (see §3.2). Always mandatory. |
| responseType | root | "markdown" (default), "json" or "adaptiveCard". Required for display EPs; optional for buttons. |
| displayJsonField | root | If responseType: "json", name of the JSON field containing the markdown to display. |
| adaptiveCardTemplate | root | Inline JSON Adaptive Card template. |
| adaptiveCardTemplateJsonField | root | Name of the REST response field containing the Adaptive Card template. |
| adaptiveCardContentJsonField | root | Name of the REST response field containing the card data. |
| adaptiveCardSubmitUrl | root | REST URL called as POST when an Action.Submit is invoked in the card. |
The command object
The command object is always mandatory and carries at least a url. Depending on the chosen commandType, additional sub-fields are valid.
"command": {
"url": "{{server}}/your-endpoint",
"params": "user=${localUser.email}",
"commandType": "rest",
"method": "GET",
"header": ["Authorization: Bearer TOKEN"],
"bodyParameters": { "key": "value" }
}| Field | When to use it |
|---|---|
| url | Mandatory. Application path, Web URL, file or REST API. |
| params | Optional. Appended after ? for GET, or as URL parameters. Variables allowed. |
| commandType | Optional. Default "rest". Values: "rest", "file" (depending on EP). |
| method | Optional (only if commandType: "rest"). Values: "GET", "POST". |
| header | Optional (if REST). Array of "Header-Name: value" strings inserted as HTTP headers. |
| bodyParameters | Mandatory if method: "POST". JSON object sent as body. Variables allowed. |
Rainbow Variables
$Variables are replaced at runtime with their actual value. They can appear in command.url, command.params, command.bodyParameters, and in content returned to the extension for display.
Syntax
${object.attribute}
${object.attribute.regex(pattern)}
${object.attribute.regex(pattern, replacement)}Available objects and attributes
Rainbow user (ME)
${localUser.firstName} ${localUser.lastName}
${localUser.email} ${localUser.canonicalNumber}
${localUser.phoneNumber} ${localUser.internalNumber}
${localUser.phonePbx} ${localUser.nickname}
${localUser.loginEmail} ${localUser.id}Contact (if identified)
${contact.firstName} ${contact.lastName}
${contact.phoneNumber} ${contact.email}
${contact.company} ${contact.workNumber}
${contact.mobileNumber} ${contact.personalNumber}
${contact.personalMobile}
${contact.tags} (concatenated, comma-separated)Caller (call button EPs only)
${caller.firstName} ${caller.lastName} ${caller.phoneNumber}Called (call button EPs only)
${called.phoneNumber}Call context (call button EPs only)
${call.callId}
${call.globalCallId} (OXE only)
${call.correlator}Bubble (bubble EPs only)
${bubble.ownerEmail} ${bubble.name} ${bubble.shareLink}
${bubble.id}
${bubble.members} (JSON — only with commandType: "rest")Compatibility per extension point
| Extension point | Allowed variable objects |
|---|---|
| on-incoming-pbx-call-button-actions | localUser, caller, called, call, contact |
| on-call-log-list-actions | localUser, contact |
| on-contact-list-actions | localUser, contact |
| on-contact-detail-actions | localUser, contact |
| on-communication-card-actions | localUser, caller, called, call, contact |
| on-bubble-actions | localUser, bubble |
| on-bubble-conference-actions | localUser, bubble |
| on-user-menu-actions | localUser |
| on-incoming-pbx-call-display-actions | localUser, caller, called, call, contact |
| on-contact-card-display-actions | localUser, contact |
| on-main-widget-display-actions | localUser |
| on-main-display-actions | localUser |
| on-main-window-actions | localUser |
regex modifier
Applies a regex to the variable value before substitution:
// Normalize a number — replace +33 or +1 by 0
"params": "pbx=${localUser.phonePbx.regex(^\+33|^\+1, 0)}"
// Replace email domain
"params": "byEmail=${localUser.email.regex(alcatel.com, al-enterprise.com)}"
// Extract last 9 digits (no replacement = extraction)
"params": "${contact.phoneNumber.regex(.{0,9}$)}"${time} variable
Available on all extension points — substituted at the moment the action is executed.
| Variable | Value |
|---|---|
| ${time} | Full date-time (ISO 8601, e.g. 2026-05-18T15:30:45.123Z) |
| ${time.year} | Year — 4 digits |
| ${time.month} | Month — 01-12 |
| ${time.day} | Day — 01-31 |
| ${time.hour} | Hour — 00-23 |
| ${time.minute} | Minute — 00-59 |
| ${time.second} | Second — 00-59 |
| ${time.millisec} | Milliseconds — 000-999 |
Local resources — ${custo.path}
To distribute a manifest with local files (icons, static content):
- Reference each resource as ${custo.path}\\file-name.ext
- Package custo-manifest.json and the resource files into a ZIP (manifest at the root)
{
"icon": "${custo.path}\\crm.png",
"command": {
"commandType": "file",
"url": "${custo.path}\\content.txt"
},
"responseType": "markdown"
}Extension-specific fields
| Field | Affected extension points | Description |
|---|---|---|
| AnswerOnClick | on-incoming-pbx-call-button-actions | true → runs the action and answers the call in one click |
| Popup | on-main-window-actions | true → opens the WEB app in a browser window instead of the Rainbow frame |
| alsoForTeams | on-call-log-list-actions, on-main-display-actions | Also shows the extension in the Teams add-in |
| urlConfig | on-main-widget-display-actions | HTTP/HTTPS URL for widget configuration |
Including local resources (ZIP archive)
If the action references local files (icons or display files), package the custo-manifest.json with those files in a .zip archive before importing.
Required ZIP structure — the manifest must be at the root:
my-manifest.zip
├── custo-manifest.json
├── icon.png
└── display-content.txtDeploying the manifest
Once the manifest is configured, deploy it via the Rainbow administration console or locally. See Deploying a custo-manifest in Rainbow.
Examples of configuration
Buttons
Supported by Rainbow Desktop and Rainbow Web — requires CORS for REST calls.
Example 1 — on-contact-list-actions adds a button on each contact row that opens the contact in an external CRM (no popup inside Rainbow):
{
"version": "2",
"jsonType": "Rainbow-custo-manifest",
"extension-points": {
"on-contact-list-actions": [
{
"name": "Open in CRM",
"tooltip": "View this contact in our CRM",
"icon": "${custo.path}\\crm.png",
"command": {
"url": "https://crm.example.com/contact",
"params": "email=${contact.email}&phone=${contact.phoneNumber}"
}
}
]
}
}Example 2 — on-incoming-pbx-call-button-actions adds a button on the incoming call pop-up that creates a ticket and answers the call in one click:
{
"version": "2",
"jsonType": "Rainbow-custo-manifest",
"extension-points": {
"on-incoming-pbx-call-button-actions": [
{
"name": "Create ticket & answer",
"AnswerOnClick": true,
"command": {
"commandType": "rest",
"method": "POST",
"url": "https://tickets.example.com/api/tickets",
"header": [
"Authorization: Bearer TOKEN",
"Content-Type: application/json"
],
"bodyParameters": {
"caller": "${caller.phoneNumber}",
"callId": "${call.callId}",
"createdBy": "${localUser.loginEmail}"
}
}
}
]
}
}Displays
Supported by Rainbow Desktop and Rainbow Web — requires CORS for REST calls.
Example 1 — on-incoming-pbx-call-display-actions automatically shows a Markdown caller-ID card in the incoming call pop-up:
{
"version": "2",
"jsonType": "Rainbow-custo-manifest",
"extension-points": {
"on-incoming-pbx-call-display-actions": [
{
"command": {
"commandType": "rest",
"method": "GET",
"url": "https://api.example.com/lookup",
"params": "phone=${caller.phoneNumber}",
"header": ["Authorization: Bearer TOKEN"]
},
"responseType": "markdown"
}
]
}
}Example 2 — on-main-widget-display-actions shows an Adaptive Card widget in the Rainbow main panel, refreshed each time it is opened:
{
"version": "2",
"jsonType": "Rainbow-custo-manifest",
"extension-points": {
"on-main-widget-display-actions": [
{
"name": "My tickets",
"command": {
"commandType": "rest",
"method": "GET",
"url": "https://api.example.com/tickets/me",
"header": ["Authorization: Bearer TOKEN"]
},
"responseType": "adaptiveCard",
"adaptiveCardTemplateJsonField": "card",
"adaptiveCardContentJsonField": "data",
"adaptiveCardSubmitUrl": "https://api.example.com/tickets/submit"
}
]
}
}Frame display
Supported by Rainbow Desktop and Rainbow Web.
Example 1 — on-main-window-actions embeds a CRM dashboard in the Rainbow main panel:
{
"version": "2",
"jsonType": "Rainbow-custo-manifest",
"extension-points": {
"on-main-window-actions": [
{
"name": "My CRM",
"icon": "${custo.path}\\crm.png",
"command": {
"url": "https://crm.example.com/dashboard",
"params": "user=${localUser.email}"
}
}
]
}
}Example 2 — same with Popup: true to open in a browser window (useful when the app refuses iframe embedding):
{
"version": "2",
"jsonType": "Rainbow-custo-manifest",
"extension-points": {
"on-main-window-actions": [
{
"name": "External reporting",
"icon": "${custo.path}\\report.png",
"Popup": true,
"command": {
"url": "https://reporting.example.com/",
"params": "user=${localUser.loginEmail}"
}
}
]
}
}