Board UI

Methods

canOpenPanel(...)

() => Promise<boolean>

Checks if there are blocking elements before attempting to open a panel in the UI.

Panels that are already open from other sources can block these requests. It's recommended to check using miro.board.ui.canOpenPanel before using miro.board.ui.openPanel.

Example:


if (await miro.board.ui.canOpenPanel()) {
  await miro.board.ui.openPanel({
    url: 'show-this-page-in-the-panel.html',
    height: 400,
  });
}

openPanel(...)

(
  options: Identity<{ height?: number; title="url">url: string } & conditional>
) => Promise<{ waitForClose: () => Promise<Result | undefined> }>

Opens a panel on the current board.
The content displayed in the panel is fetched from the specified URL.

  • The panel height corresponds to the current viewport height.
  • Panel width: currently it's set to 368 dp, fixed, padding included.
    You should implement your app to adapt to content area width changes between 292 dp and 320 dp, and to accommodate a fixed panel width between 340 dp and 368 dp, padding included.
  • Left padding: 24 dp
  • Right padding: 24 dp

When opening a panel, you can pass in data that can be accessed within the panel itself. Use the data property within the options object to pass the necessary information.

Note: To get the data from inside the panel, use getPanelData() method.

The method for opening the panel returns a Promise. This Promise resolves to an object that includes a waitForClose() method:

Promise<{ waitForClose: () => Promise<Result | undefined> }>;

The waitForClose() method returns a Promise that resolves to the result of the panel when it's closed, or undefined if there is no result.
The result is a value that is passed as a parameter into closePanel() method.

Note: When a user manually closes a panel, the promise returned by the waitForClose() method always resolves with an undefined value.

Example:

// Example 1: Basic usage of openPanel
await miro.board.ui.openPanel({
  url: 'show-this-page-in-the-panel.html',
});
// The panel is displayed on the board

// Example 2: Passing data into the panel
const dataForModal = "hello world";
await miro.board.ui.openPanel<string>({
  data: dataForModal,
  url: 'show-this-page-in-the-panel.html',
});

// Example 3: Waiting for the panel to close
const someData = { message: "my message" };
const { waitForClose } = await miro.board.ui.openPanel<{message: string}, string>({
  data: someData,
  url: 'show-this-page-in-the-panel.html',
});

// Inside the panel, you can call the following to close the panel and resolve the Promise:
//     await miro.board.ui.closePanel('result');
console.log(await waitForClose()); // This will log: "result"

The content that apps display on modals and panels opens inside iframes.
iframes can request access to the following permissions:

  • microphone:listen: access a user's microphone to record audio in an iframe.
  • screen:record: access a user's screen to record it in an iframe.
  • webcam:record: access a user's camera to record video.

See also:

NameTypeDescription
optionsobject-
options.urlstringAbsolute or relative URL pointing to the content that you want to display in the panel. If you specify a relative URL, the URL path resolves relative to the app URL. The transport protocol must be HTTPS.
options.data?anyAny data that you want to pass directly to the panel and use it inside the panel itself. Check the getPanelData() method.

getPanelData(...)

() => Promise<undefined | Data>

Retrieves the data associated with the currently open panel in the Miro board. This data is initially passed to the panel when it is opened using the miro.board.ui.openPanel({ data, ... }) method.

This method is typically called from within the panel itself to access any data that it needs to function correctly. If no data was passed when the panel was opened, calling getPanelData will return a promise that resolves to undefined.

Note: getPanelData will always return promise that resolves or to undefined or to the actual passed data.

Example:

Consider the following scenario where a panel is opened with some data:

// Define some data to pass to the panel
const someData = { message: "my message" };

// Open a panel and pass the data to it
const { waitForClose } = await miro.board.ui.openPanel<{message: string}, string>({
  data: someData,
  url: 'show-this-page-in-the-panel.html',
  height: 400,
});

Now, from within the panel, you can retrieve this data using getPanelData():

// Retrieve the data from within the panel
const data = await miro.board.ui.getPanelData<{message: string}>()

console.log(data) // Outputs: "{ message: "my message" }"

closePanel(...)

(result?: Result) => Promise<void>

Closes an open panel on the current board.

This method accepts an optional result parameter. Providing this parameter results in the panel being resolved with the provided value. If you use the waitForClose() method—returned by the miro.board.ui.openPanel(...)—the promise returned by waitForClose() will resolve, returning the value of result.

TLDR; If you've passed a result value and close the panel, this value will be returned by the waitForClose() promise. This is useful if you want to perform some action based on the result after the panel is closed.

ℹ️ Note:

  • To close an open panel, closePanel must be executed after openPanel.
  • The app that closes the panel with closePanel must be the same that opened it with openPanel.

Example:

// Open a panel
await miro.board.ui.openPanel({
  url: 'show-this-page-in-the-panel.html',
  height: 400,
});

// Close the open panel
await miro.board.ui.closePanel();

// Open a panel and wait for the result
const {waitForClose} = await miro.board.ui.openPanel({
  url: 'show-this-page-in-the-panel.html',
  height: 400,
});

async function logResult() {
    const result = await waitForClose();
    console.log(result) // This will log: "Hello world!"
}

logResult()

// Close the open panel with some result
await miro.board.ui.closePanel("Hello world!");

The content that apps display on modals and panels opens inside iframes.
iframes can request access to the following permissions:

  • microphone:listen: access a user's microphone to record audio in an iframe.
  • screen:record: access a user's screen to record it in an iframe.
  • webcam:record: access a user's camera to record video.

See also:


canOpenModal(...)

() => Promise<boolean>

Checks if there are blocking elements before attempting to open a model in the UI.
Modals are blocking elements, meaning there can be only one modal displayed at a time for a user. If your operation happens at the same time as another Miro modal is being shown, your modal won't be displayed to the user. It's recommended to check using miro.board.ui.canOpenModal before using miro.board.ui.openModal.

Example:


if (await miro.board.ui.canOpenModal()) {
  await miro.board.ui.openModal({
    url: 'show-this-page-in-the-modal.html',
    width: 600,
    height: 400,
    fullscreen: false,
  });
}

openModal(...)

(
  options: Identity<{ fullscreen?: boolean; height?: number; title="url">url: string; width?: number } & conditional>
) => Promise<{ waitForClose: () => Promise<Result | undefined> }>

Opens a modal on the current board.
The content displayed in the modal is fetched from the specified URL.

The max. width and height of the modal correspond to the width and height of the current viewport.
The load timeout for the modal is 10 seconds.

You have the ability to pass data into a modal that you open. This data can then be accessed from within the modal itself. To pass data, use the data property in the options object when opening it.

Note: to get the data from inside the modal you have to use getModalData() method.

The method for opening the modal returns a Promise. This Promise resolves to an object that includes a waitForClose() method:

Promise<{ waitForClose: () => Promise<Result | undefined> }>;

The waitForClose() method returns a Promise that resolves to the result of the modal when it's closed, or undefined if there is no result.
The result is a value that is passed as a parameter into closeModal() method.

Note: promise returned by waitForClose() method will always be resolved with undefined value in case of manual closing of modal by user.

Example:

// Example 1: Basic usage of openModal
await miro.board.ui.openModal({
  url: "show-this-page-in-the-modal.html",
  width: 600,
  height: 400,
  fullscreen: false,
});
// This will open a modal with the specified URL, width, and height. The modal will not be displayed in fullscreen mode.

// Example 2: Passing data into the modal
const dataForModal = "hello world";
await miro.board.ui.openModal<string>({
  data: dataForModal,
  url: "show-this-page-in-the-modal.html",
  width: 600,
  height: 400,
  fullscreen: false,
});

// Example 3: Waiting for the modal to close
const someData = { message: "my message" };
const { waitForClose } = await miro.board.ui.openModal<{message: string}, string>({
  data: someData,
  url: "show-this-page-in-the-modal.html",
  width: 600,
  height: 400,
  fullscreen: false,
});

// Inside the modal, you can call the following to close the modal and resolve the Promise:
//     await miro.board.ui.closeModal('result');
console.log(await waitForClose()); // This will log: "result"
NameTypeDescription
optionsobject-
options.urlstringAbsolute or relative URL pointing to the content that you want to display in the panel. If you specify a relative URL, the URL path resolves relative to the app URL. The transport protocol must be HTTPS.
options.height?numberSets the height of the modal, in dp.
The max. height of the modal corresponds to the height of the current viewport.
Default: 600
options.width?numberSets the width of the modal, in dp.
The max. width of the modal corresponds to the width of the current viewport.
Default: 800
options.fullScreen?booleanSet it to:
  • false to disallow full screen display for the modal.
  • true to override height and width, and to display the modal in full screen.
Default: false
options.data?anyAny data that you want to pass directly to the modal and use it inside the modal itself. Check the getModalData() method.

getModalData(...)

() => Promise<undefined | Data>

Retrieves the data associated with the currently open modal in the Miro board. This data is initially passed to the modal when it is opened using the miro.board.ui.openModal({ data, ... }) method.

This method is typically called from within the modal itself to access any data that it needs to function correctly. If no data was passed when the modal was opened, calling getModalData will return a promise that resolves to undefined.

Note: getModalData will always return promise that resolves or to undefined or to the actual passed data.

Example:

Consider the following scenario where a modal is opened with some data:

// Define some data to pass to the modal
const someData = { message: "my message" };

// Open a modal and pass the data to it
const { waitForClose } = await miro.board.ui.openModal<{message: string}, string>({
  data: someData,
  url: "show-this-page-in-the-modal.html",
  width: 600,
  height: 400,
  fullscreen: false,
});

Now, from within the modal, you can retrieve this data using getModalData():

// Retrieve the data from within the modal
const data = await miro.board.ui.getModalData<{message: string}>()

console.log(data) // Outputs: "{ message: "my message" }"

closeModal(...)

(result?: Result) => Promise<void>

Closes an open modal on the current board.

This method accepts an optional result parameter. Providing this parameter results in the modal being resolved with the provided value. If you use the waitForClose() method—returned by miro.board.ui.openModal(...)—the promise returned by waitForClose() will resolve, returning the value of result.

TLDR; If you've passed a result value and close the modal, this value will be returned by the waitForClose() promise. This is useful if you want to perform some action based on the result after the modal is closed.

ℹ️ Note:

  • To close an open modal, closeModal must be executed after openModal.
  • The app that closes the modal with closeModal must be the same app that opened it with openModal.

Example:

// Open a modal
await miro.board.ui.openModal({
  url: 'show-this-page-in-the-modal.html',
  height: 400,
  width: 600,
  fullscreen: false,
});

// Close the open modal
await miro.board.ui.closeModal();

// Open a modal and wait for the result
const {waitForClose} = await miro.board.ui.openModal({
  url: 'show-this-page-in-the-modal.html',
  height: 400,
  width: 600,
  fullscreen: false,
});

async function logResult() {
    const result = await waitForClose();
    console.log(result) // This will log: "Hello world!"
}

logResult()

// Close the open modal with some result
await miro.board.ui.closeModal("Hello world!");

on(...)

(event: 'drop', handler: (event: DropEvent) => void) => void

on property

miro.board.ui.on(event, handler);

If you want your app to react to an event by executing a function, you can use the on property to subscribe to events.
The on property subscribes the app to listen to an event. When the event fires, the event handler executes a function to perform an action.

To subscribe to an event and its handler, pass to the on property:

  • The event that your app should listen to.
  • The event handler that the app needs to call when the event fires.

💡 To unsubscribe from an event and its handler, use the off property.

Supported events
Event typeEvent handlerEvent fires when...
drop(event: DropEvent)An HTML element is dropped from an open panel to the board. See also Add drag and drop to your app.
icon:click()An app icon is clicked. See also Add clicking on an icon to your app.
app_card:open(event: AppCardOpenEvent)The app card status icon is connected, the icon is clicked, and it enables opening a modal with the app card detail view.
app_card:connect(event: AppCardConnectEvent)The app card status icon is disconnected, the icon is clicked, and it enables connecting an app card with a corresponding data source.
selection:update(event: SelectionUpdateEvent)An area on the board is selected.
online_users:update(event: OnlineUsersUpdateEvent)The number of currently online board users changes.
items:create(event: ItemsCreateEvent)New items are created on the board.
experimental:items:update(event: ItemsUpdateEvent)Existing items are updated on the board.
items:delete(event: ItemsDeleteEvent)Existing items are deleted from the board.
custom:${string}(event: CustomEvent)A custom action on a board item is initiated.
Dispatching events

In general, when an app subscribes to an event, the event is dispatched to all iframes:

This behavior makes it easy to subscribe to an event from any of these iframes, without worrying about which iframe the event is dispatched to.

ℹ️ Note:

  • icon:click is dispatched only to the headless/main iframe.
    Typically, icon:click is used to open a panel or a modal when a user clicks the app icon on the app toolbar or the app toolbar panel.

drop event

on(event: 'drop', handler: (event: DropEvent) => void): void

When an app subscribes to this event, it's dispatched to all iframes.

drop registers the event associated with dropping one or more selected items on the board.

The event handler has the following properties:

PropertyTypeDescription
xNumberCoordinate that defines the horizontal position of the HTML element to drop on the board.
yNumberCoordinate that defines the vertical position of the HTML element to drop on the board.
targetElement interfaceThe HTML element that is the recipient of the drop event.

Example:

/** When the selected HTML element is dropped on the board:
 *  1. A sticky note is created.
 *  2. The text of the HTML element is assigned to the
 *     'content' property of sticky note.
 */
miro.board.ui.on('drop', async ({x, y, target}) => {
  await miro.board.createStickyNote({
    x,
    y,
    content: target.innerText,
  });
});

You can provide a custom preview image for drag & drop using data attributes on elements with class="miro-draggable":

  • data-drag-preview - Specify the URL of the preview image to use during dragging.
  • data-drag-preview-width - (Optional) Set the width of the preview image in pixels.
  • data-drag-preview-height - (Optional) Set the height of the preview image in pixels.

Preview image size behavior:

  • If no width or height is set, the original image size will be used.
  • If only width or height is set, that dimension will be used and the other dimension will be calculated to maintain aspect ratio.
  • If both width and height are set, those dimensions will be applied and aspect ratio will be ignored.

A valid URL pointing to or representing the image. Possible formats:

  • A publicly available, absolute URL to an image.
  • A base64-encoded data URL: data:image/<image-file-format>;base64,<base64-encoded-image>.

Example:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>My panel with draggable items</title>
    <!-- Loads the Miro Web SDK -->
    <script src="https://miro.com/app/static/sdk/v2/miro.js"></script>
    <!-- Loads the companion JS file with the logic -->
    <script src="app.js"></script>
  </head>
  <body>
    <!-- The 'miro-draggable' class makes HTML items draggable -->

    <!-- The `data-drag-preview` attribute changes preview image during dragging of element -->
    <div class="miro-draggable" data-drag-preview="https://amazing-images.com/categories/cats/cute-fluffy-kitten.png">
      Kitten
    </div>

    <!-- The `data-drag-preview-width` attribute changes preview image width. The `data-drag-preview-height` will work in the same way -->
    <div
      class="miro-draggable"
      data-drag-preview-width="100"
      data-drag-preview="https://amazing-images.com/categories/dogs/beautiful-dog.jpg"
    >
      Dog
    </div>

    <!-- The `data-drag-preview-width` + `data-drag-preview-height` force apply size and ignore aspect ratios of image -->
    <div
      class="miro-draggable"
      data-drag-preview-width="100"
      data-drag-preview-height="100"
      data-drag-preview="https://amazing-images.com/categories/dragons/scary-dragon.svg"
    >
      Dragon
    </div>
  </body>
</html>

icon:click event

on(event: 'icon:click', handler: () => void) => void

When an app subscribes to this event, it's dispatched only to the headless/main iframe.

icon:click registers click events on icons.
When a user clicks an icon, the registered event handler for icon:click is called.
Typically, icon:click is used to open a panel, or to display a modal.

Example:

/** When a user clicks the icon:
 *  1. The openPanel method is called
 *  2. The method opens the HTML page: `panel.html`
 */
miro.board.ui.on('icon:click', async () => {
  await miro.board.ui.openPanel({
    url: 'panel.html',
  });
});

app_card:open event

on(event: 'app_card:open', handler: (event: AppCardOpenEvent) => void): void;

When an app subscribes to this event, it's dispatched to all iframes.

app_card:open registers click events to open an app card from compact to detail view.

When a user clicks the icon to expand an app card to view it in detail, the registered event handler for app_card:open is called.
Typically, app_card:open is used to open a modal displaying the custom fields of the app card and their content.

Example:

/** When a user clicks the icon that expands an app card to view it in detail:
 *  1. The 'openModal' method is called
 *  2. The method opens a modal to display the specific app card content fetched from the URL
 */
// Listen to the 'app_card:open' event
miro.board.ui.on('app_card:open', (event) => {
  console.log('Subscribed to app card open event', event);
  const {appCard} = event;

  // Build a URL containing the app card ID.
  // You pass this URL with the 'openModal()' method below.
  // The code in the modal uses the 'appCardId' URL query parameter
  // to identify which app card was opened.
  const url = `https://my.app.example.com/modal.html?appCardId=${appCard.id}`;

  // Open the modal to display the content of the fetched app card
  miro.board.ui.openModal({
    url,
  });
});

app_card:connect event

on(event: 'app_card:connect', handler: (event: AppCardConnectEvent) => void): void;

When an app subscribes to this event, it's dispatched to all iframes.

By default, newly created app cards have a disconnected status, unless the app card constructor sets a different value.
To connect an app card to a corresponding data source in an external application, an app must listen to the app_card:connect event.

When an app listens to the app_card:connect event:

  • The disconnected icon App card status: disconnected is clickable.
  • On hovering over the icon, a tooltip is displayed to notify users that they can click the icon to connect the app card to a data source.
  • When users click the icon, the app_card:connect event fires.
  • The event handler needs to include at least the logic to:
    • Retrieve the data source that the app card maps to.
    • Sync data to populate the app card fields with any updated information.
    • Update the app card status from disconnected to connected.
    • sync() the app card to propagate the changes to the board.

If the app listens also to the app_card:open event, it can react to it; typically, by opening a modal to display the app card detail view.

Example:

/** In a typical flow:
 *  1. The app creates an app card.
 *     The 'disconnected' status icon is not clickable, yet.
 *  2. The app listens to the 'app_card:connect' event.
 *     The 'disconnected' status icon is clickable.
 *  3. The app listens to the 'app_card:open' event.
 *  4. When users click the 'disconnected' status icon, a tooltip prompts them to connect the app card to its data source.
 *  5. When the app card is connected, its status icon changes to 'connected'.
 *  6. Now the app can open a modal to display the app card detail view.
 */

// Create an app card
const appCard = await miro.board.createAppCard({
  title: 'This is the title of the app card',
  // Default status of new app cards
  status: 'disconnected',
});

// Listen to the 'app_card:connect' event
miro.board.ui.on('app_card:connect', (event) => {
  console.log('Connect the app card to its data source');
  const appcard = event.appCard;
  // Update the app card status to 'connected'
  appcard.status = 'connected';
  // Propagate the app card updates to the board
  appcard.sync();
});

// Listen to the 'app_card:open' event
miro.board.ui.on('app_card:open', (event) => {
  console.log('Subscribed to app card open event', event);
  // URL containing the app card ID.
  // The content is displayed inside the modal
  const url = `https://my.app.example.com/modal.html?appCardId=${appCard.id}`;
  // Open the modal to display the content of the fetched app card
  miro.board.ui.openModal({
    url,
  });
});


Figure 1. Newly created card, or duplicate app card through manual copying and pasting on the board UI. The app card status is disconnected. The tooltip notifies about the missing connection. The status icon isn't clickable.


Figure 2. The app card status is disconnected. The tooltip prompts to connect it to its data source. The status icon is clickable.


Figure 3. The app card status is connected. The app listens to the app_card:open event. When the icon is clicked, the event fires. Typically, it opens a modal to display the app card detail view.

selection:update event

on(event: 'selection:update', handler: (event: SelectionUpdateEvent) => void): void;

When an app subscribes to this event, it's dispatched to all iframes.

selection:update registers the event associated with updating the content of the current selection on the board.

When a user selects an area on the board, the registered event handler for selection:update is called.
The event contains an array with the selected board items.
If the selected area doesn't include any board items, the array is empty.

If the grouped items are selected, the group entity for those items will also be returned.
When individual items within a group are selected with a double-click, the group entity will not be returned as part of this method.

You can add logic to perform actions on the selection, such as filtering specific item types, and then modifying them.

Example:

/** When a user clicks and selects multiple board items on a board:
 *  1. The 'selection:update' method logs the selection to the developer console
 *  2. A filter identifies sticky note items in the selection
 *  3. The color of the sticky notes is changed to 'cyan'
 */

// Listen to the 'selection:update' event
miro.board.ui.on('selection:update', async (event) => {
  console.log('Subscribed to selection update event', event);
  console.log(event.items);
  const selectedItems = event.items;

  // Filter sticky notes from the selected items
  const stickyNotes = selectedItems.filter((item) => item.type === 'sticky_note');

  // Change the fill color of the sticky notes
  for (const stickyNote of stickyNotes) {
    stickyNote.style.fillColor = 'cyan';
    await stickyNote.sync();
  }
});

online_users:update event

on(event: 'online_users:update', handler: (event: OnlineUsersUpdateEvent) => void): void

When an app subscribes to this event, it's dispatched to all iframes.

online_users:update registers the event associated with a change in the number of users that are currently online on the board.

When a user joins or leaves the board, the registered event handler for online_users:update is called.
The event contains an array with user IDs and names of the online users.

Your app can include logic to perform follow-up actions based on the change in online users.
For example, it can greet a new user that just joined the board.

Example:

/** When the number of online users changes, identify the
 *  new online users and greet them with a notification.
 */

let currentOnlineUsers = [];
// Listen to the 'online_users:update' event.
await miro.board.ui.on('online_users:update', async (event) => {
  console.log('Subscribed to the update of online users');
  console.log('Online users: ', event.users);
  const onlineUsers = event.users;

  // Identify the new online users.
  const newUsers = onlineUsers.filter((user) => !currentOnlineUsers.find((u) => u.id === user.id));
  console.log('New users:', newUsers);

  // Greet the new online users.
  for (const newUser of newUsers) {
    await miro.board.notifications.showInfo(`Hello, ${newUser.name}!`);
  }

  currentOnlineUsers = onlineUsers;
});

items:create event

on(event: 'items:create', handler: (event: ItemsCreateEvent) => void): void;

When an app subscribes to this event, it's dispatched to all iframes.

items:create registers the event associated with the creation of a new item on the board.

When a user creates a new item on a board, the registered event handler for items:create is called.
The event contains an array with the created board items.

Your app can include logic to perform follow-up actions on the created items. For example, it can filter specific item types, and then process them by fetching or setting their properties.

ℹ️ Note:

  • When creating a new item by copy-pasting or by duplicating an existing one, items:create isn't triggered.

Example:

/** When a user creates a new item on a board:
 *  1. 'items:create' logs the created items to the developer console.
 *  2. In the group of created items, a filter identifies sticky notes.
 *  3. The color of the sticky notes is set to 'cyan'.
 */

// Listen to the 'items:create' event.
miro.board.ui.on('items:create', async (event) => {
  console.log('Subscribed to the creation of new board items', event);
  console.log(event.items);
  const createdItems = event.items;

  // Filter sticky notes from the created items.
  const stickyNotes = createdItems.filter((item) => item.type === 'sticky_note');

  // Change the fill color of the sticky notes.
  for (const stickyNote of stickyNotes) {
    stickyNote.style.fillColor = 'cyan';
    await stickyNote.sync();
  }
});

Experimental feature Experimental

experimental:items:update event

on(event: 'experimental:items:update', handler: (event: ItemsUpdateEvent) => void): void;

When an app subscribes to this event, it's dispatched to all iframes.

experimental:items:update registers the event associated with updating an item on the board.

When a user updates one or more items on the board, the registered event handler for experimental:items:update is called.
The event contains an array with the updated board items.

Your app can include logic to perform follow-up actions on the updated items. For example, it can log a list of the updated board items.

ℹ️ Note:

  • Currently, experimental:items:update fires only when items are moved on the board, when an item dimensions are resized, when an item is rotated on the board, and when updating the scale of an item.
    In the future, the event will also fire when updating other item data.

Example:

/**
 * When a user updates one or more items on the board:
 * 'experimental:items:update' logs all the updated board items to the developer console.
 */

// Listen to the 'experimental:items:update' event.
miro.board.ui.on('experimental:items:update', async (event) => {
  console.log('Subscribed to updates of board items', event);
  console.log('Updated items: ', event.items);
});

items:delete event

on(event: 'items:delete', handler: (event: ItemsDeleteEvent) => void): void;

When an app subscribes to this event, it's dispatched to all iframes.

items:delete registers the event associated with deleting an item from the board.

When a user deletes one or more items from the board, the registered event handler for items:delete is called.
The event contains an array with the deleted board items.

Your app can include logic to perform follow-up actions on the deleted items. For example, it can log a list of the deleted board items.

Example:

/**
 * When a user deletes one or more items from the board:
 * 'items:delete' logs all deleted board items to the developer console.
 */

// Listen to the 'items:delete' event.
miro.board.ui.on('items:delete', async (event) => {
  console.log('Subscribed the deletion of board items', event);
  console.log('Deleted items: ', event.items);
});

custom:${string} event

on(event: `custom:${string}`, handler: (event: CustomEvent) => void): void;

When an app subscribes to this event, it's dispatched to all iframes.

custom:${string} events implement custom actions in an app.
Web SDK custom actions behave like standard Web SDK events:

  • The app subscribes to a custom action event with the on property.
  • When the custom action event is no longer necessary, the app unsubscribes from it with the off property.
Custom event naming

Custom event names always start with the custom: prefix: custom:${string}

${string} must match the value assigned to the event property when the app registers the custom action with the miro.board.experimental.action.register method.

  • The event name can contain only lowercase alphabetic characters and hyphens (^[a-z]+(-[a-z]+)\*$).
  • It cannot contain spaces.
  • It cannot be longer than 30 characters.

off(...)

(event: 'drop', handler: (event: DropEvent) => void) => void

off property

miro.board.ui.off(event, handler);

When an app no longer needs to listen to an event to trigger an event handler, it can use the off property to unsubscribe from it.
To unsubscribe from an event and its handler, pass to the off property:

  • The event whose handler you want your app to unsubscribe from.
  • The event handler that you previously registered with the on property, and that your app no longer needs to listen to.

💡 To subscribe to an event and its handler, use the on property.

Supported events

See the supported events for the on property.

drop unsubscribe

off(event: 'drop', handler: (event: DropEvent) => void): void

Enables unsubscribing from a drop event handler.
To unsubscribe, pass to the off property:

  • The drop event.
  • The event handler that you previously registered with the on property, and that your app no longer needs to listen to.

Example:

// Add a 'drop' event handler to drag and drop images.
const drop = async (event: DropEvent) => {
  const {x, y, target} = event;
  if (target instanceof HTMLImageElement) {
    const image = await miro.board.createImage({
      x,
      y,
      url: target.src,
    });
  }
};

// Register the 'drop' event so that the app listens to it.
miro.board.ui.on('drop', drop);

// Unsubscribe from the 'drop' handler.
// The app no longer creates image items on drag and drop.
miro.board.ui.off('drop', drop);

icon:click unsubscribe

off(event: 'icon:click', handler: () => void) => void

Enables unsubscribing from an icon:click event handler.
To unsubscribe, pass to the off property:

  • The icon:click event.
  • The event handler that you previously registered with the on property, and that your app no longer needs to listen to.

Example:

// Add an 'iconClick' event handler to open a panel upon clicking an icon.
const iconClick = async () => {
  await miro.board.ui.openPanel({
    url: 'panel.html',
  });
};

// Register the 'icon:click' event so that the app listens to it.
miro.board.ui.on('icon:click', iconClick);

// Unsubscribe from the 'icon:click' event handler.
// The app no longer enables opening a panel when clicking an icon.
miro.board.ui.off('icon:click', iconClick);

app_card:open unsubscribe

off(event: 'app_card:open', handler: (event: AppCardOpenEvent) => void): void;

Enables unsubscribing from an app_card:open event handler.
To unsubscribe, pass to the off property:

  • The app_card:open event.
  • The event handler that you previously registered with the on property, and that your app no longer needs to listen to.

Example:

// Create an app card.
const appCard = await miro.board.createAppCard({
  title: 'This is the title of the app card',
  status: 'disconnected',
});

// Add an 'appCardOpen' event handler for the 'app_card:open' event.
const appCardOpen = async (event: AppCardOpenEvent) => {
  const appcard = event.appCard;
  const url = `https://my.app.example.com/modal.html?appCardId=${appCard.id}`;
  miro.board.ui.openModal({
    url,
  });
};

// Register the 'app_card:open' event so that the app listens to it.
miro.board.ui.on('app_card:open', appCardOpen);

// Unsubscribe from the 'app_card:open' event handler.
// The app no longer enables opening a modal with the detail view of an app card.
miro.board.ui.off('app_card:open', appCardOpen);

app_card:connect unsubscribe

off(event: 'app_card:connect', handler: (event: AppCardConnectEvent) => void): void;

Enables unsubscribing from the app_card:connect event and its handler.
To unsubscribe, pass to the off property:

  • The app_card:connect event.
  • The event handler that you previously registered with the on property, and that your app no longer needs to listen to.

Example:

// Create an app card.
const appCard = await miro.board.createAppCard({
  title: 'This is the title of the app card',
  status: 'disconnected',
});

// Add an 'appCardConnect' event handler for the 'app_card:connect' event.
const appCardConnect = async (event: AppCardConnectEvent) => {
  const appcard = event.appCard;
  appcard.status = 'connected';
  appcard.sync();
};

// Register the 'app_card:connect' event so that the app listens to it.
miro.board.ui.on('app_card:connect', appCardConnect);

// Unsubscribe from the 'app_card:connect' event handler.
// The app no longer enables connecting an app card to a data source.
miro.board.ui.off('app_card:connect', appCardConnect);

selection:update unsubscribe

off(event: 'selection:update', handler: (event: SelectionUpdateEvent) => void): void;

Enables unsubscribing from a selection:update event handler.
To unsubscribe, pass to the off property:

  • The selection:update event.
  • The event handler that you previously registered with the on property, and that your app no longer needs to listen to.

Example:

// Add a 'selectionUpdate' event handler to update the color of sticky notes included in a selection.
const selectionUpdate = async (event: SelectionUpdateEvent) => {
  const selectedItems = event.items;
  const stickyNotes = selectedItems.filter((item) => item.type === 'sticky_note');
  for (const stickyNote of stickyNotes) {
    stickyNote.style.fillColor = 'cyan';
    await stickyNote.sync();
  }
};

// Register the 'selection:update' event so that the app listens to it.
miro.board.ui.on('selection:update', selectionUpdate);

// Unsubscribe from the 'selection:update' event handler.
// The app no longer enables updating the color of the sticky notes included in a selection to cyan.
miro.board.ui.off('selection:update', selectionUpdate);

online_users:update unsubscribe

off(event: 'online_users:update', handler: (event: OnlineUsersUpdateEvent) => void): void

Enables unsubscribing from an online_users:update event handler.
To unsubscribe, pass to the off property:

  • The online_users:update event.
  • The event handler that you previously registered with the on property, and that your app no longer needs to listen to.

Example:

// Add an 'online_users:update' event handler to greet new online users joining the board.
let currentOnlineUsers = [];
const onlineUsersUpdate = async (event) => {
  const onlineUsers = event.users;
  const newUsers = onlineUsers.filter((user) => !currentOnlineUsers.find((u) => u.id === user.id));

  for (const newUser of newUsers) {
    await miro.board.notifications.showInfo(`Hello, ${newUser.name}!`);
  }

  currentOnlineUsers = onlineUsers;
};

// Register the 'online_users:update' event so that the app listens to it.
await miro.board.ui.on('online_users:update', onlineUsersUpdate);

// Unsubscribe from the 'online_users:update' event handler.
// The app no longer greets new online users.
await miro.board.ui.off('online_users:update', onlineUsersUpdate);

items:create unsubscribe

off(event: 'items:create', handler: (event: ItemsCreateEvent) => void): void;

Enables unsubscribing from an items:create event handler.
To unsubscribe, pass to the off property:

  • The items:create event.
  • The event handler that you previously registered with the on property, and that your app no longer needs to listen to.

Example:

// Add an 'itemsCreate' event handler to update the color of user-created sticky notes.
const itemsCreate = async (event: ItemsCreateEvent) => {
  const createdItems = event.items;
  const stickyNotes = createdItems.filter((item) => item.type === 'sticky_note');
  for (const stickyNote of stickyNotes) {
    stickyNote.style.fillColor = 'cyan';
    await stickyNote.sync();
  }
};

// Register the 'items:create' event so that the app listens to it.
miro.board.ui.on('items:create', itemsCreate);

// Unsubscribe from the 'items:create' event handler.
// The app no longer updates the color of user-created sticky notes.
miro.board.ui.off('items:create', itemsCreate);

Experimental feature Experimental

experimental:items:update unsubscribe

off(event: 'experimental:items:update', handler: (event: ItemsUpdateEvent) => void): void;

Enables unsubscribing from an experimental:items:update event handler.
To unsubscribe, pass to the off property:

  • The experimental:items:update event.
  • The event handler that you previously registered with the on property, and that your app no longer needs to listen to.

Example:

// Add an 'itemsUpdate' event handler to log user-updated board items to the developer console.
const itemsUpdate = async (event: ItemsUpdateEvent) => {
  console.log(event.items);
};

// Register the 'experimental:items:update' event so that the app listens to it.
miro.board.ui.on('experimental:items:update', itemsUpdate);

// Unsubscribe from the 'experimental:items:update' event handler.
// The app no longer enables logging user-updated board items.
miro.board.ui.off('experimental:items:update', itemsUpdate);

items:delete unsubscribe

off(event: 'items:delete', handler: (event: ItemsDeleteEvent) => void): void;

Enables unsubscribing from an items:delete event handler.
To unsubscribe, pass to the off property:

  • The items:delete event.
  • The event handler that you previously registered with the on property, and that your app no longer needs to listen to.

Example:

// Add an 'itemsDelete' event handler to log user-deleted board items to the developer console.
const itemsDelete = async (event: ItemsDeleteEvent) => {
  console.log(event.items);
};

// Register the 'items:delete' event so that the app listens to it.
miro.board.ui.on('items:delete', itemsDelete);

// Unsubscribe from the 'items:delete' event handler.
// The app no longer enables logging user-deleted board items.
miro.board.ui.off('items:delete', itemsDelete);

custom:${string} event

off(event: `custom:${string}`, handler: (event: CustomEvent) => void): void;

Enables unsubscribing from a custom event handler.
To unsubscribe, pass to the off property:

  • The custom:${event} event that you want your app to unsubscribe from.
  • The event handler that you previously registered with the on property, and that your app no longer needs to listen to.

All properties

PropertyType
canOpenModal(...)
() => Promise<boolean>
canOpenPanel(...)
() => Promise<boolean>
closeModal(...)
(result?: Result) => Promise<void>
closePanel(...)
(result?: Result) => Promise<void>
getModalData(...)
() => Promise<undefined | Data>
getPanelData(...)
() => Promise<undefined | Data>
off(...)
(event: 'drop', handler: (event: DropEvent) => void) => void
on(...)
(event: 'drop', handler: (event: DropEvent) => void) => void
openModal(...)
(
  options: Identity<{ fullscreen?: boolean; height?: number; title="url">url: string; width?: number } & conditional>
) => Promise<{ waitForClose: () => Promise<Result | undefined> }>
openPanel(...)
(
  options: Identity<{ height?: number; title="url">url: string } & conditional>
) => Promise<{ waitForClose: () => Promise<Result | undefined> }>