# Mural

- Tool: Bubble
- Difficulty: Intermediate
- Time required: 2–3 hours
- Last updated: July 2026

## TL;DR

Connect Bubble to Mural using the API Connector plugin with a Private Bearer personal access token. Mural's widget endpoint returns sticky note content as htmlContent (HTML markup) — display it in a Bubble HTML element, not a text element. Use Mural's votes array to build a voting results dashboard where workshop participants see top ideas without needing a Mural account. Requires Mural Team plan or above for API access.

## Build a post-workshop voting results dashboard in Bubble

Mural is where design thinking workshops happen — teams generate sticky notes, group ideas into clusters, and vote on the best solutions. The problem: when the workshop ends, the insights are locked inside Mural. Only people with Mural accounts can see the results, and the raw board view is noisy with all the facilitation artifacts.

Building a Bubble app on Mural's API solves this. You can create a clean results dashboard that shows workshop participants the top-voted ideas, their vote counts, and any approved action items — without them needing a Mural account or access to the raw board.

Three Mural-specific details that affect Bubble setup:

First, Mural's widget endpoint returns its data in a `value` array field rather than directly at the response root. In Bubble API Connector, you need to configure the 'Use response field' setting to extract the `value` array as your list.

Second, sticky note content comes back as HTML markup in the `htmlContent` field — including tags like `<p>`, `<strong>`, and `<br>`. A standard Bubble text element displays this as raw text with visible tags. You must use a Bubble HTML element to render it correctly.

Third, Mural's `votes` field on each widget is an array of vote objects. Bubble can count this array with the 'count' operator to get total votes per sticky note.

Note: Mural personal access tokens for API use require a Team plan or above. The free Personal plan does not include Developer Settings access.

## Before you start

- A Mural account on the Team plan or above — personal access token generation requires Developer Settings which is only available on paid plans (Team from approximately $9.99/user/month)
- Your Mural workspace ID — visible in the URL of your Mural workspace dashboard
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble)
- Basic familiarity with Bubble's workflow editor, repeating groups, and HTML elements

## Step-by-step guide

### 1. Generate a Mural personal access token with correct scopes

Log into Mural and click your profile avatar in the top-right corner. Select 'Developer Settings' from the profile menu. If you do not see 'Developer Settings', your Mural plan does not include API access — this option is only available on Team plan and above. Verify your Mural subscription before proceeding.

In Developer Settings, navigate to the 'Personal Access Tokens' section. Click 'Create a token'. Give it a name like 'Bubble Integration' and set the expiration to your preference (90 days is the default; you can set longer periods). For scopes, select:
- `murals:read` — required to list murals and read widget content
- `murals:write` — optional, add only if you need to create or update content
- `identity:read` — optional, for accessing workspace and user information

Click 'Create token' and copy it immediately. Mural personal access tokens expire based on your configured expiration period — unlike many other API keys, they are not permanent. Set a calendar reminder to rotate the token before it expires, and add a fallback error state in your Bubble app to handle 401 responses gracefully when the token expires.

**Expected result:** You have a Mural personal access token with murals:read scope. The Developer Settings page shows the token name and expiration date. A calendar reminder is set for before the expiration date.

### 2. Configure the Bubble API Connector with Mural's base URL

In your Bubble app, open the Plugins tab and confirm the API Connector plugin is installed. Click 'Add another API' and name it 'Mural'.

Set the Authentication to 'Private key in header'. Enter `Authorization` as the key name and `Bearer YOUR_TOKEN` as the value — replace YOUR_TOKEN with your Mural personal access token. Tick the 'Private' checkbox.

Set the base URL to `https://app.mural.co/api/public/v1`.

Add a shared Content-Type header: `Content-Type: application/json`.

Note: Mural's API rate limit is approximately 100 requests per minute per token. For Bubble apps with auto-refreshing dashboards, implement a minimum 30-second interval between refresh cycles. Polling more frequently than this risks hitting the rate limit and returning 429 errors, which will blank out your dashboard mid-session.

```
{
  "api_name": "Mural",
  "base_url": "https://app.mural.co/api/public/v1",
  "headers": [
    {
      "key": "Authorization",
      "value": "Bearer <private>",
      "private": true
    },
    {
      "key": "Content-Type",
      "value": "application/json",
      "private": false
    }
  ]
}
```

**Expected result:** The Mural API group appears in Bubble API Connector with the Authorization header showing a Private lock icon and the correct base URL.

### 3. Add and initialize the murals list call

Inside the Mural API group, click 'Add another call'. Name it 'Get Murals'. Set method to GET and path to `/workspaces/{workspaceId}/murals`.

Add a path parameter: workspaceId (text). Enter your Mural workspace ID as the default value. Your workspace ID is visible in the Mural dashboard URL: `app.mural.co/t/{workspaceId}/`.

Add an optional query parameter: limit (number, default 50).

Click 'Initialize call'. Mural returns a JSON object with a `value` array containing mural objects, and a `next` field for pagination.

IMPORTANT: Mural wraps list responses in a `value` field. In Bubble's initialization dialog, look for a setting to specify the response field or root path. Set it to use `value` as the response array. If this option isn't directly available, set 'Use as' to 'Data' and configure your repeating groups to reference 'Result of step 1's value' to access the array.

Each mural object contains: id, title, updatedOn (Unix timestamp in milliseconds), createdBy (user object), and thumbnailUrl.

```
GET https://app.mural.co/api/public/v1/workspaces/{workspaceId}/murals?limit=50
Authorization: Bearer <private>

// Response structure:
// {
//   "value": [
//     {
//       "id": "teamId.muralId",
//       "title": "Q3 Strategy Session",
//       "updatedOn": 1752134400000,
//       "thumbnailUrl": "https://..."
//     }
//   ],
//   "next": "cursor_token_for_pagination"
// }
```

**Expected result:** The 'Get Murals' call is initialized and Bubble detects the mural list fields including id, title, updatedOn, and thumbnailUrl. The response structure shows the value array containing mural objects.

### 4. Add the widget call and configure for sticky note display

Add another call: 'Get Widgets'. Set method to GET and path to `/murals/{muralId}/widgets`. Add path parameter: muralId (text, dynamic). Add query parameter: limit (number, default 200).

Click 'Initialize call' using a real mural ID from your workspace. Mural returns a response wrapped in a `value` array containing widget objects of various types (sticky_note, image, text, shape, connector, etc.).

Each sticky note widget contains:
- `id`: unique widget identifier
- `type`: 'sticky_note'
- `htmlContent`: the sticky note text as HTML markup (e.g., `<p><strong>Idea title</strong></p>`)
- `votes`: array of vote objects (each vote is an object with userId and timestamp)
- `backgroundColor`: hex color of the sticky note
- `x`, `y`: position on the mural canvas

For the vote count: in Bubble, you can get the count of the `votes` array using the 'count' list operator. In your repeating group expression: 'Current cell's widget's votes count'.

Set 'Use as' to 'Data' and configure to reference the `value` array. In your repeating group filter, you can filter by type = 'sticky_note' to show only sticky notes, excluding images, text blocks, and connector lines.

```
GET https://app.mural.co/api/public/v1/murals/{muralId}/widgets?limit=200
Authorization: Bearer <private>

// Sticky note widget structure:
// {
//   "id": "widget-uuid",
//   "type": "sticky_note",
//   "htmlContent": "<p>This is the sticky note text</p>",
//   "votes": [
//     {"userId": "user-id", "timestamp": 1752134400000}
//   ],
//   "backgroundColor": "#FFD700",
//   "x": 100,
//   "y": 200
// }
```

**Expected result:** The 'Get Widgets' call is initialized and Bubble detects widget fields including id, type, htmlContent, votes, backgroundColor, x, and y. The votes field appears as a list data type.

### 5. Build the voting results repeating group with HTML element display

On your Bubble page, add a Dropdown for mural selection. Set its choices to 'Get data from an external API → Mural - Get Murals'. Display: current option's title. Value: current option's id.

Add a Repeating Group for widgets. Set its data source to 'Get data from an external API → Mural - Get Widgets' with muralId = Mural dropdown's value.

In the Repeating Group settings, add a filter: type = 'sticky_note'. Sort by: votes count, descending (highest vote count first). This surfaces the most-voted ideas at the top.

INSIDE THE REPEATING GROUP CELL — this is critical:

Do NOT add a standard Bubble text element for the sticky note content. Instead, add an HTML element (from the Visual elements panel, it is listed as 'HTML'). Set its content to: 'Current cell's widget's htmlContent'. The HTML element renders the Mural content with proper formatting including bold text, line breaks, and lists.

If you use a standard text element instead, your users will see raw HTML markup like `<p><strong>Customer journey mapping</strong></p>` — completely unreadable.

Add alongside the HTML element:
- A Group container styled with the widget's backgroundColor field for a matching sticky-note color
- A text badge showing 'Current cell's widget's votes count' + ' votes'
- A medal icon or text ('🥇', '🥈', '🥉') using a conditional on the row index for the top 3

```
// Repeating group configuration summary:
// Data source: Mural - Get Widgets (muralId = Mural dropdown value)
// Filter: type = 'sticky_note'
// Sort: votes count, Descending

// Inside cell:
// - HTML element: content = Current cell's widget's htmlContent
// - Vote badge text: Current cell's widget's votes count + ' votes'
// - Background color Group: style fill = Current cell's widget's backgroundColor

// Conditional for top 3 highlighting:
// When Current cell's index = 1 → show gold background
// When Current cell's index = 2 → show silver background
// When Current cell's index = 3 → show bronze background
```

**Expected result:** The repeating group shows Mural sticky notes from the selected mural, sorted by vote count (most voted first). Each row displays the sticky note content rendered as HTML (not raw markup), a vote count badge, and uses the sticky note's original background color.

### 6. Add auto-refresh and handle token expiration gracefully

For a live workshop results dashboard, you want the sticky note votes to update without manual refreshing. Implement a 30-second auto-refresh using Bubble's 'Schedule API workflow' on page load:

1. Create a Backend Workflow (paid Bubble plan required) named 'Refresh Mural Data'. This workflow calls Get Widgets with the current muralId and schedules itself to run again in 30 seconds.
2. Alternatively, use a Bubble 'Reusable element' with a recurring event pattern to re-fetch the widgets call every 30 seconds.

Caution: Auto-refreshing faster than 30 seconds risks hitting Mural's rate limit of approximately 100 requests per minute. If multiple users have the dashboard open simultaneously, each user generates their own API calls — 10 users with 10-second refresh = 60 calls/minute, which hits the limit.

For token expiration handling, add a workflow condition to any API call's On error event: 'When API error status is 401, show Alert element: Mural connection expired — please contact your administrator.' Store the last successful refresh time in a Bubble custom state and display it on the page ('Last updated: X minutes ago') so users know when data may be stale.

RapidDev's team has built post-workshop dashboards combining Mural results with Bubble voting overlays and Airtable action tracking — if you need a more complex facilitation app, book a free scoping call at rapidevelopers.com/contact.

```
// Auto-refresh pattern (Bubble workflow):
// Page load event:
//   Step 1: Display data (Get Widgets, current muralId)
//   Step 2: Schedule API workflow 'Refresh Mural Data' in 30 seconds
//
// Backend Workflow 'Refresh Mural Data':
//   Step 1: Display data (Get Widgets)
//   Step 2: Schedule this same workflow in 30 seconds
//   (creates a recurring loop until page is closed)

// Rate limit awareness:
// 100 req/min / 30s interval = 2 req/min per user
// Safe for up to ~50 concurrent users
```

**Expected result:** The dashboard automatically refreshes vote counts every 30 seconds. If the Mural token expires and the API returns 401, an error banner appears rather than a silent failure. The 'Last updated' timestamp keeps users informed of data freshness.

## Best practices

- Always mark the Mural Bearer token as Private in Bubble API Connector. Tokens grant access to your workspace's murals and must never appear in browser network requests.
- Use Bubble HTML elements (not text elements) for Mural sticky note content. The htmlContent field contains HTML markup that only renders correctly in an HTML element.
- Implement a minimum 30-second auto-refresh interval for live dashboards. The Mural API rate limit is approximately 100 requests per minute per token — faster polling risks 429 errors that blank out the dashboard.
- Set a calendar reminder to rotate the Mural personal access token before its expiration date. Add a 401 error handler in your Bubble app that shows a user-friendly 'connection expired' message rather than a silent failure.
- Verify the client's Mural plan includes Developer Settings access before starting development. The Personal (free) plan does not include API access — this is a hard gate that cannot be worked around.
- Filter the widgets repeating group to type = 'sticky_note' to exclude images, connector lines, and shape objects from your results dashboard.
- Add Privacy Rules (Data tab → Privacy) for any Mural content you store in Bubble's database, restricting read access to authenticated users with appropriate roles — workshop content may be sensitive.

## Use cases

### Post-workshop voting results dashboard

Build a Bubble page that displays workshop sticky notes sorted by vote count. Participants see the top-ranked ideas from their session without needing a Mural account. The dashboard shows each idea's content, vote count badge, and color category — giving a clean, shareable view of the workshop outcomes.

Prompt example:

```
Create a Bubble page with a mural ID input (or hardcoded for a specific session). Call GET https://app.mural.co/api/public/v1/murals/{muralId}/widgets with limit=200. Filter to type='sticky_note'. Sort by votes array count descending. Display each sticky note in an HTML element showing htmlContent, with a vote count badge. Highlight the top 3 in gold/silver/bronze colors.
```

### Multi-mural workspace browser with idea filtering

Build an internal tool that lists all murals in a Mural workspace and lets team members browse sticky notes from any mural without opening Mural. A mural dropdown drives the widget loading, and a type filter shows only sticky notes or only images. This gives product teams a searchable idea library across all their workshop boards.

Prompt example:

```
Create a Bubble page with two dropdowns: workspace selector (GET /workspaces) and mural selector (GET /workspaces/{workspaceId}/murals, filtered by selected workspace). When a mural is selected, load GET /murals/{muralId}/widgets filtered to type='sticky_note'. Show content in HTML elements. Add a search input that filters displayed widgets by htmlContent text content.
```

### Action items approval tracker

After a Mural workshop, facilitators mark certain sticky notes as approved action items in a Bubble app. The Bubble app reads Mural widget data and lets facilitators assign owners, set deadlines, and track completion status alongside the original Mural content — bridging the gap between the whiteboard session and project execution.

Prompt example:

```
Create a Bubble page that loads Mural sticky notes via the API. Add a 'Mark as action item' button to each note. On click, create a Bubble ActionItem data type record storing the muralId, widgetId, htmlContent, assigned_to (User), and due_date. Build a separate action items tracking view showing all approved items with their completion status.
```

## Troubleshooting

### Developer Settings is not visible in the Mural profile menu

Cause: Mural Developer Settings, including personal access token generation, is only available on Team plan and above. The free Personal plan does not include API access.

Solution: Upgrade to Mural Team plan to access Developer Settings. Verify the client's Mural subscription plan before starting development. If the client is on a Personal plan, API integration is not possible without upgrading.

### All API calls return 401 Unauthorized, but the token was valid during setup

Cause: Mural personal access tokens have a configurable expiration period (default 90 days). When a token expires, all API calls fail with 401 silently — there is no advance warning from the API.

Solution: Generate a new personal access token in Mural Developer Settings and update the Authorization header value in Bubble API Connector. Set a calendar reminder to rotate the token before the next expiration. Add an error state in your Bubble app that shows a user-friendly message when 401 errors occur, so users are not left with a blank dashboard.

### Sticky note content shows raw HTML tags like <p> and <strong> in the repeating group

Cause: A standard Bubble text element displays htmlContent as literal text including HTML markup. Mural's sticky note content field is HTML-formatted, not plain text.

Solution: Replace the text element with a Bubble HTML element (from the Visual elements panel). Set the HTML element's content to the widget's htmlContent field. The HTML element renders markup properly with bold text, line breaks, and formatting.

### The widget repeating group shows all widget types (images, connectors, text blocks) not just sticky notes

Cause: No type filter has been applied to the repeating group. Mural's widget endpoint returns all widget types including sticky notes, images, shapes, text blocks, and connector lines.

Solution: Add a repeating group constraint: 'type = sticky_note'. In Bubble's repeating group data source settings, add a filter condition: 'Current cell's widget's type = sticky_note'. This displays only sticky notes while excluding all other widget types.

### 'There was an issue setting up your call' when initializing the widgets call

Cause: Bubble requires a real successful response during initialization. This error occurs if the muralId parameter is empty, the mural ID is incorrect (wrong format — Mural IDs combine teamId and muralId with a dot), or the token lacks murals:read scope.

Solution: Use a complete valid mural ID during initialization — copy it from a real mural's URL in the format 'teamId.muralId'. Verify the personal access token has the murals:read scope in Mural Developer Settings. Confirm the token has not expired.

### Vote count shows as blank or 0 for all sticky notes even though votes exist in Mural

Cause: The votes field is an array of vote objects. Bubble may not automatically count array fields — you need to use the 'count' operator on the list.

Solution: In your repeating group, reference the vote count as 'Current cell's widget's votes:count' (using Bubble's list count operator). If the field is detected as a single object rather than a list, re-initialize the call and confirm that votes is detected as a List data type in the API Connector field mapping.

## Frequently asked questions

### Does Mural's free plan include API access?

No. Mural Developer Settings (required to generate personal access tokens) is only available on Team plan and above. The free Personal plan does not include API access. Verify the client's Mural subscription before starting development — this is a hard gate. The Team plan starts at approximately $9.99 per user per month.

### Why do my sticky note contents show HTML tags instead of formatted text?

Mural's htmlContent field contains HTML markup (e.g., <p><strong>Idea</strong></p>) because sticky notes support basic formatting. A standard Bubble text element displays this as literal text including the angle brackets. Use a Bubble HTML element instead — it renders the markup as formatted content. The HTML element is available in the Visual elements panel.

### How do I get the vote count for each sticky note?

The votes field on each Mural widget is an array of vote objects (one per person who voted). To display the vote count in Bubble, reference it as 'Current cell's widget's votes:count' using Bubble's list count operator. Sort your repeating group by this count in descending order to surface the most-voted ideas at the top.

### My Mural personal access token stopped working after some weeks — why?

Mural personal access tokens have a configurable expiration period with a default of 90 days. When they expire, all API calls fail with 401 Unauthorized. Generate a new token in Mural Developer Settings and update the Authorization header value in Bubble API Connector. Set a calendar reminder before the next expiration to avoid unexpected outages.

### Can I use Mural OAuth 2.0 instead of a personal access token in Bubble?

Mural supports OAuth 2.0 for multi-user scenarios where each user authenticates with their own Mural account. OAuth 2.0 in Bubble requires additional workflow setup: redirect to Mural's authorization URL, handle the callback with a Bubble Backend Workflow, exchange the code for an access token, and store it per user. For a single-facilitator dashboard, a personal access token is far simpler. Consider OAuth only if you need per-user access control where each Bubble user should see only their own Mural boards.

### The widget endpoint returns images and shapes, not just sticky notes — how do I filter?

Mural's /widgets endpoint returns all widget types on the board: sticky notes, images, text blocks, shapes, connector lines, and more. In Bubble's repeating group, add a constraint filtering to type = 'sticky_note'. This excludes all non-sticky-note widgets from the results display. You can also filter to other types (type = 'text' for text blocks, type = 'image' for images) for specific display needs.

---

Source: https://www.rapidevelopers.com/bubble-integrations/mural
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/mural
