|
| 1 | +<!-- search-meta |
| 2 | +tags: [custom-actions, custom action, code-based custom actions, code based custom action, customActions, CustomAction, CustomActionsPosition, CustomActionTarget, CustomActionPayload, EmbedEvent.CustomAction, callback, PRIMARY, MENU, CONTEXTMENU, context menu, more menu, toolbar button, LiveboardEmbed, AppEmbed, SearchEmbed, SpotterEmbed, add button to embed, custom menu item, TypeScript, Web, Visual Embed SDK] |
| 3 | +apis: [init, AuthType, LiveboardEmbed, AppEmbed, SearchEmbed, SpotterEmbed, customActions, CustomAction, CustomActionsPosition, CustomActionTarget, CustomActionPayload, MessagePayload, EmbedEvent, EmbedEvent.CustomAction, on] |
| 4 | +primary: true |
| 5 | +supersedes: custom-actions |
| 6 | +questions: |
| 7 | + - How do I add a custom action to a ThoughtSpot embed? |
| 8 | + - How do I add custom actions in ThoughtSpot? |
| 9 | + - What is a code-based custom action in ThoughtSpot? |
| 10 | + - How do I create a custom action entirely in code (no UI setup)? |
| 11 | + - How do I add a custom button to the toolbar of an embedded Liveboard? |
| 12 | + - How do I add a custom action to the "..." more menu? |
| 13 | + - How do I add a custom action to the right-click context menu? |
| 14 | + - How do I handle a custom action click in the Visual Embed SDK? |
| 15 | + - How do I read the data context (clicked point, columns) from a custom action? |
| 16 | + - What is the difference between UI custom actions and code-based custom actions? |
| 17 | + - Which should I use — code-based or UI custom actions? |
| 18 | + - How do I use customActions in init() vs per-embed view config? |
| 19 | + - What are CustomActionsPosition and CustomActionTarget? |
| 20 | + - How do I restrict a custom action to specific Liveboards, answers, groups, or orgs? |
| 21 | + - How do I add a custom action to Spotter? |
| 22 | + - How do I listen for EmbedEvent.CustomAction? |
| 23 | +--> |
| 24 | + |
| 25 | +# Code-Based Custom Actions |
| 26 | + |
| 27 | +> **Start here for anything about ThoughtSpot custom actions.** Code-based custom actions are the **recommended, default** way to add custom actions to an embed. Define them in your application code — no configuration in the ThoughtSpot UI — so they are version-controlled, portable across Orgs, and fully described by `position` + `target`. Only use **UI-created** custom actions (see the [`custom-actions`](../custom-actions) example) when you specifically need business users to manage actions from the ThoughtSpot Admin UI. |
| 28 | +
|
| 29 | +A **custom action** adds your own menu item / button to an embedded ThoughtSpot **Answer, Liveboard, visualization, or Spotter** interface. Clicking it fires an event your app handles — to call an API, open a modal, navigate to another app, push data to a CRM, and so on — with the data context of what the user clicked. |
| 30 | + |
| 31 | +There are two ways to create them: |
| 32 | + |
| 33 | +| Approach | Where defined | When to use | |
| 34 | +| --- | --- | --- | |
| 35 | +| **Code-based** (this example) ✅ default | In your app code via the `customActions` config | Almost always. Portable, version-controlled, works across Orgs, no admin setup. | |
| 36 | +| **UI-based** | ThoughtSpot Admin → Develop → Customizations → Actions | Only when non-developers must create/manage actions in the ThoughtSpot UI. | |
| 37 | + |
| 38 | +> If the **same primary-menu-bar** action is defined in both the UI and in code, only the **UI** action is shown. |
| 39 | +
|
| 40 | +## How code-based custom actions work |
| 41 | + |
| 42 | +1. Declare a `customActions` array — either globally on `init()` (applies to every embed) or per-embed in the view config (scoped to that embed). |
| 43 | +2. Each entry needs an `id`, a display `name`, a `position` (where it appears), and a `target` (what it applies to). |
| 44 | +3. Listen for `EmbedEvent.CustomAction` and branch on the action `id`. |
| 45 | + |
| 46 | +```typescript |
| 47 | +import { |
| 48 | + init, AuthType, LiveboardEmbed, EmbedEvent, |
| 49 | + CustomActionsPosition, CustomActionTarget, |
| 50 | + type MessagePayload, type CustomActionPayload, |
| 51 | +} from "@thoughtspot/visual-embed-sdk"; |
| 52 | + |
| 53 | +init({ |
| 54 | + thoughtSpotHost: import.meta.env.VITE_TS_HOST, |
| 55 | + authType: AuthType.Basic, // demo only — use trusted auth / SSO in production |
| 56 | + username: import.meta.env.VITE_TS_USERNAME, |
| 57 | + password: import.meta.env.VITE_TS_PASSWORD, |
| 58 | +}); |
| 59 | + |
| 60 | +const embed = new LiveboardEmbed(document.getElementById("ts-embed")!, { |
| 61 | + frameParams: { width: "100%", height: "100%" }, |
| 62 | + liveboardId: import.meta.env.VITE_LIVEBOARD_ID, |
| 63 | + |
| 64 | + // Define custom actions entirely in code: |
| 65 | + customActions: [ |
| 66 | + { id: "export-to-crm", name: "Export to CRM", |
| 67 | + position: CustomActionsPosition.PRIMARY, target: CustomActionTarget.LIVEBOARD }, |
| 68 | + { id: "send-to-slack", name: "Send to Slack", |
| 69 | + position: CustomActionsPosition.MENU, target: CustomActionTarget.VIZ }, |
| 70 | + { id: "open-ticket", name: "Open support ticket", |
| 71 | + position: CustomActionsPosition.CONTEXTMENU, target: CustomActionTarget.VIZ }, |
| 72 | + ], |
| 73 | +}); |
| 74 | + |
| 75 | +// Handle clicks. `payload.data` carries the action id + data context. |
| 76 | +embed.on(EmbedEvent.CustomAction, (payload: MessagePayload) => { |
| 77 | + const data = payload.data as CustomActionPayload & { id: string }; |
| 78 | + if (data.id === "export-to-crm") { |
| 79 | + console.log("Answer data:", data.embedAnswerData); |
| 80 | + console.log("Clicked point:", data.contextMenuPoints?.clickedPoint); |
| 81 | + // ...call your API / open a modal / navigate... |
| 82 | + } |
| 83 | +}); |
| 84 | + |
| 85 | +embed.render(); |
| 86 | +``` |
| 87 | + |
| 88 | +## `CustomActionsPosition` — where the action appears |
| 89 | + |
| 90 | +| Value | Location | |
| 91 | +| --- | --- | |
| 92 | +| `CustomActionsPosition.PRIMARY` | A primary button in the toolbar | |
| 93 | +| `CustomActionsPosition.MENU` | Inside the `...` (more options) menu | |
| 94 | +| `CustomActionsPosition.CONTEXTMENU` | The right-click context menu (Answer / visualization; not Liveboard-level) | |
| 95 | + |
| 96 | +## `CustomActionTarget` — what the action applies to |
| 97 | + |
| 98 | +| Value | Applies to | Supported positions | |
| 99 | +| --- | --- | --- | |
| 100 | +| `CustomActionTarget.LIVEBOARD` | Liveboard level | `PRIMARY`, `MENU` | |
| 101 | +| `CustomActionTarget.VIZ` | An individual visualization | `PRIMARY`, `MENU`, `CONTEXTMENU` | |
| 102 | +| `CustomActionTarget.ANSWER` | Answer / Search page | `PRIMARY`, `MENU`, `CONTEXTMENU` | |
| 103 | +| `CustomActionTarget.SPOTTER` | Spotter interface | `MENU`, `CONTEXTMENU` (no primary) | |
| 104 | + |
| 105 | +## Scoping where an action shows |
| 106 | + |
| 107 | +Each custom action can be restricted so it only appears in the right context: |
| 108 | + |
| 109 | +```typescript |
| 110 | +{ |
| 111 | + id: "viz-action", |
| 112 | + name: "My Viz Action", |
| 113 | + position: CustomActionsPosition.MENU, |
| 114 | + target: CustomActionTarget.VIZ, |
| 115 | + |
| 116 | + // Restrict by metadata (specific answers / liveboards / vizzes): |
| 117 | + metadataIds: { liveboardIds: ["liveboard-id-1"], vizIds: ["viz-id-1"] }, |
| 118 | + |
| 119 | + // Restrict by data model (models / columns): |
| 120 | + dataModelIds: { modelIds: ["model-id-1"], modelColumnNames: ["model-id::Revenue"] }, |
| 121 | + |
| 122 | + // Restrict by access (groups / orgs): |
| 123 | + groupIds: ["group-id-1"], |
| 124 | + orgIds: ["org-id-1"], |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +## The `EmbedEvent.CustomAction` payload |
| 129 | + |
| 130 | +The `.on()` callback receives a `MessagePayload` whose `data` (typed as `CustomActionPayload` plus the action `id`) includes: |
| 131 | + |
| 132 | +- `id` — the `id` of the custom action that was clicked (branch on this). |
| 133 | +- `embedAnswerData` — `{ name, id, columns, data, sources, ... }` of the underlying Answer/viz. |
| 134 | +- `contextMenuPoints` — `{ clickedPoint, selectedPoints }` when triggered from a data point (context menu). |
| 135 | +- `session` — the session context. |
| 136 | +- `vizId` — the visualization id, where applicable. |
| 137 | + |
| 138 | +## `customActions` in `init()` vs per-embed |
| 139 | + |
| 140 | +- **`init({ customActions: [...] })`** — global; the actions apply to every embed created after init. |
| 141 | +- **`new LiveboardEmbed(el, { customActions: [...] })`** — scoped to that one embed. Supported on `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, and `SpotterEmbed`. |
| 142 | + |
| 143 | +Use per-embed config (as this example does) when different embeds need different actions. |
| 144 | + |
| 145 | +## What this example does |
| 146 | + |
| 147 | +- Declares three code-based custom actions on a `LiveboardEmbed`: a toolbar button (`PRIMARY`/`LIVEBOARD`), a more-menu item (`MENU`/`VIZ`), and a right-click item (`CONTEXTMENU`/`VIZ`). |
| 148 | +- Listens for `EmbedEvent.CustomAction` and logs each event — showing the action `id` and the full payload — into a side panel so you can inspect the data context. |
| 149 | + |
| 150 | +## Project Structure |
| 151 | + |
| 152 | +- `index.html` — Vite entry; mounts `#app` and loads `src/main.ts`. |
| 153 | +- `src/main.ts` — SDK `init`, the `customActions` declaration, the `EmbedEvent.CustomAction` handler, and the event-log UI. |
| 154 | +- `src/style.css` — layout (embed + event log panel). |
| 155 | +- `src/vite-env.d.ts` — typed `import.meta.env` for the `VITE_*` variables. |
| 156 | +- `.env` / `.stackblitzrc` — connection + auth values for the public demo instance. |
| 157 | + |
| 158 | +## Requirements |
| 159 | + |
| 160 | +- `@thoughtspot/visual-embed-sdk` **>= 1.43.0** |
| 161 | +- ThoughtSpot **>= 10.14.0.cl** |
| 162 | + |
| 163 | +## Environment |
| 164 | + |
| 165 | +The committed `.env` targets the public ThoughtSpot training instance. To run against your own: |
| 166 | + |
| 167 | +``` |
| 168 | +VITE_TS_HOST=https://your-instance.thoughtspot.cloud |
| 169 | +VITE_TS_USERNAME=your-username |
| 170 | +VITE_TS_PASSWORD=your-password |
| 171 | +VITE_LIVEBOARD_ID=your-liveboard-id |
| 172 | +``` |
| 173 | + |
| 174 | +## Technologies Used |
| 175 | + |
| 176 | +- TypeScript |
| 177 | +- Vite |
| 178 | +- ThoughtSpot Visual Embed SDK (`@thoughtspot/visual-embed-sdk`) |
| 179 | + |
| 180 | +## Demo |
| 181 | + |
| 182 | +Open in [StackBlitz](https://stackblitz.com/github/thoughtspot/developer-examples/tree/main/visual-embed/code-based-custom-actions) |
| 183 | + |
| 184 | +## Documentation |
| 185 | + |
| 186 | +- [Code-based custom actions](https://developers.thoughtspot.com/docs/code-based-custom-action) |
| 187 | +- [Custom actions overview](https://developers.thoughtspot.com/docs/custom-action-intro) |
| 188 | +- [EmbedConfig.customActions](https://developers.thoughtspot.com/docs/Interface_EmbedConfig#_customactions) |
| 189 | +- [EmbedEvent.CustomAction](https://developers.thoughtspot.com/docs/Enumeration_EmbedEvent) |
| 190 | + |
| 191 | +## Run locally |
| 192 | + |
| 193 | +``` |
| 194 | +$ git clone https://github.com/thoughtspot/developer-examples |
| 195 | +$ cd visual-embed/code-based-custom-actions |
| 196 | +``` |
| 197 | +``` |
| 198 | +$ npm i |
| 199 | +``` |
| 200 | +``` |
| 201 | +$ npm run dev |
| 202 | +``` |
| 203 | + |
| 204 | +### Technology labels |
| 205 | + |
| 206 | +- TypeScript |
| 207 | +- Web |
0 commit comments