To integrate Replit with Mural, register an OAuth 2.0 application in the Mural developer portal, store your credentials in Replit Secrets (lock icon π), complete the authorization flow, and use the Mural REST API from an Express or Flask server to create murals, add sticky notes and widgets, manage rooms, and automate workshop facilitation content.
Why Integrate Mural with Replit?
Mural is built for facilitated workshops β design sprints, retrospectives, brainstorming sessions, customer journey mapping, and strategic planning exercises. When you connect Replit to the Mural API, you can automate the preparation of workshop materials: pre-populate murals with data from your project management tools, generate retrospective boards from completed sprint data, or create customer journey maps pre-loaded with research findings from your CRM or analytics tools.
One of the most valuable use cases is template-driven mural generation. Many teams run the same type of workshop repeatedly (weekly retrospectives, quarterly OKR planning, monthly customer discovery sessions). Instead of manually recreating the mural structure and importing data for each session, a Replit backend can generate a fully prepared mural β with the correct framework, pre-loaded content, and participant labels β from a single API call or a scheduled job that runs before each session.
Mural's API covers the core building blocks: murals (the whiteboard canvas), sticky notes (the most common content type), shapes, connectors, images, and text labels. Rooms are the organizational containers for murals, and the API lets you create, move, and list murals within rooms. Understanding the coordinate system is important β Mural uses an x/y coordinate plane where objects are placed by their center coordinates, which you need to calculate when arranging groups of sticky notes in a grid or zone layout.
Integration method
Replit connects to Mural via the Mural REST API using OAuth 2.0 credentials stored in Replit Secrets. Your Express or Flask server can create and manage murals, add sticky notes, shapes, connectors, and text widgets, and organize murals within rooms. The Mural API uses Bearer token authentication with OAuth 2.0, requiring an initial user authorization flow followed by automatic token refresh for ongoing operation.
Prerequisites
- A Mural account with developer app access (available on paid plans)
- A registered application in the Mural developer portal (app.mural.co/developers)
- A Replit account with a Node.js or Python Repl created
- Basic understanding of OAuth 2.0 authorization code flow
- Familiarity with coordinate systems (x/y positioning for widget placement)
Step-by-step guide
Register a Mural application and configure OAuth
Register a Mural application and configure OAuth
Go to app.mural.co and navigate to your account settings, then to the Developer section (or visit the Mural developer portal at developers.mural.co). Create a new application by providing a name, description, and redirect URI. For the redirect URI, use your Replit preview URL followed by /oauth/callback β this is visible in the Replit webview URL bar when your Repl is running. Mural will provision a Client ID and Client Secret for your application. The OAuth scopes you need depend on your use case: murals:read (to list and read murals), murals:write (to create and modify murals), rooms:read (to list rooms and their murals), and identity:read (to get the current user's workspace ID). Request all scopes you will use β you can always re-authorize with different scopes later. Store MURAL_CLIENT_ID and MURAL_CLIENT_SECRET in Replit Secrets immediately after creating the app. Also note your Mural workspace ID, which is visible in the URL when you are logged into your Mural workspace (e.g., app.mural.co/t/YOUR_WORKSPACE_ID). Store it as MURAL_WORKSPACE_ID in Replit Secrets.
1// Node.js β install dependencies2// Run in Replit Shell:3// npm install express axios45// Verify secrets are configured6const required = ['MURAL_CLIENT_ID', 'MURAL_CLIENT_SECRET', 'MURAL_WORKSPACE_ID'];7for (const key of required) {8 const val = process.env[key];9 console.log(val ? `OK: ${key}` : `MISSING: ${key} β add to Replit Secrets (lock icon π)`);10}Pro tip: Mural API access is available on Business and Enterprise plans. If you receive 403 errors on API calls, verify that your Mural plan includes developer API access.
Expected result: A Mural application is registered with Client ID, Client Secret, and a redirect URI pointing to your Replit URL. MURAL_CLIENT_ID, MURAL_CLIENT_SECRET, and MURAL_WORKSPACE_ID are stored in Replit Secrets.
Complete OAuth 2.0 authorization and obtain tokens
Complete OAuth 2.0 authorization and obtain tokens
Mural uses the standard OAuth 2.0 authorization code flow. Build two routes in your server: /oauth/start redirects the user to Mural's authorization URL, and /oauth/callback exchanges the returned code for access and refresh tokens. The authorization URL requires your client ID, redirect URI, response type (code), and the space-separated scope string. After successful authorization, Mural redirects to your callback URL with a code parameter. Exchange this code for tokens by POSTing to Mural's token endpoint at https://app.mural.co/api/public/v1/authorization/oauth2/token with a Basic Auth header (base64 of clientId:clientSecret), the grant_type, code, and redirect_uri. The token response includes an access_token (short-lived, typically 1 hour), refresh_token (long-lived), and expires_in. Print both tokens to the console after the callback so you can copy them into Replit Secrets. In your main application, implement a token refresh function that calls the token endpoint with grant_type=refresh_token before token expiry to maintain uninterrupted API access.
1# Python β Mural OAuth 2.0 flow (mural_oauth.py)2from flask import Flask, request, redirect3import requests4import os5import base6467app = Flask(__name__)89CLIENT_ID = os.environ['MURAL_CLIENT_ID']10CLIENT_SECRET = os.environ['MURAL_CLIENT_SECRET']11REDIRECT_URI = os.environ.get('REPLIT_URL', 'http://localhost:3000') + '/oauth/callback'12SCOPES = 'murals:read murals:write rooms:read identity:read'1314def get_basic_auth():15 creds = base64.b64encode(f'{CLIENT_ID}:{CLIENT_SECRET}'.encode()).decode()16 return f'Basic {creds}'1718@app.route('/oauth/start')19def oauth_start():20 auth_url = (21 f'https://app.mural.co/api/public/v1/authorization/oauth2/'22 f'?client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}'23 f'&response_type=code&scope={SCOPES}'24 )25 return redirect(auth_url)2627@app.route('/oauth/callback')28def oauth_callback():29 code = request.args.get('code')30 resp = requests.post(31 'https://app.mural.co/api/public/v1/authorization/oauth2/token',32 data={33 'grant_type': 'authorization_code',34 'code': code,35 'redirect_uri': REDIRECT_URI36 },37 headers={'Authorization': get_basic_auth(), 'Content-Type': 'application/x-www-form-urlencoded'}38 )39 tokens = resp.json()40 print(f"ACCESS TOKEN: {tokens.get('access_token')}")41 print(f"REFRESH TOKEN: {tokens.get('refresh_token')}")42 return f"Authorization complete. Copy tokens to Replit Secrets. Expires in: {tokens.get('expires_in')}s"4344if __name__ == '__main__':45 app.run(host='0.0.0.0', port=3000)Pro tip: The Mural OAuth flow must be completed from a browser β you cannot automate the user consent step. Run the OAuth setup server once, complete the flow, copy the tokens to Replit Secrets, then replace the OAuth routes with your main application logic.
Expected result: After authorizing in Mural, the callback page displays your access and refresh tokens. Both are stored in Replit Secrets as MURAL_ACCESS_TOKEN and MURAL_REFRESH_TOKEN.
Create murals and list rooms
Create murals and list rooms
With a valid access token in Replit Secrets, you can now create murals and interact with your workspace. The Mural API base URL is https://app.mural.co/api/public/v1. To list all rooms in your workspace, call GET /workspaces/{workspaceId}/rooms. Each room object includes its ID and title. To create a mural, POST to /rooms/{roomId}/murals with a JSON body containing the title, width, and height of the canvas. Mural's canvas uses pixel units β a standard workshop mural is typically 6000x4000 pixels or larger. The response includes the mural ID and a direct share URL. Store the room ID where you want to create murals as MURAL_ROOM_ID in Replit Secrets so it is configurable without code changes. Build your main application server around a clean API client class or module that handles authentication headers and token refresh automatically, so all downstream functions simply call the API without managing auth themselves.
1// Node.js β Mural API client (mural-client.js)2const axios = require('axios');34const BASE_URL = 'https://app.mural.co/api/public/v1';5const ACCESS_TOKEN = process.env.MURAL_ACCESS_TOKEN;6const WORKSPACE_ID = process.env.MURAL_WORKSPACE_ID;7const ROOM_ID = process.env.MURAL_ROOM_ID;89const api = axios.create({10 baseURL: BASE_URL,11 headers: { Authorization: `Bearer ${ACCESS_TOKEN}`, 'Accept': 'application/json' }12});1314async function listRooms() {15 const res = await api.get(`/workspaces/${WORKSPACE_ID}/rooms`);16 return res.data.value || [];17}1819async function createMural(title, width = 6000, height = 4000) {20 const res = await api.post(`/rooms/${ROOM_ID}/murals`, {21 title,22 width,23 height24 });25 return res.data.value;26}2728(async () => {29 const rooms = await listRooms();30 console.log('Rooms:');31 rooms.forEach(r => console.log(` ${r.name} (ID: ${r.id})`));3233 const mural = await createMural('Replit Test Workshop');34 console.log(`Created mural: ${mural.title}`);35 console.log(`URL: ${mural.sharingLink}`);36})();Pro tip: Keep a reference to the created mural's ID in your application state β you will need it to add widgets and sticky notes in subsequent API calls. The mural ID is returned in the createMural response.
Expected result: The script lists your Mural rooms with their IDs and creates a new test mural. The mural appears in your Mural workspace and the shareable URL is logged to the console.
Add sticky notes and widgets to murals
Add sticky notes and widgets to murals
The primary building block of any Mural is the sticky note widget. To add sticky notes to a mural, POST to /murals/{muralId}/widgets/sticky-note with a JSON body specifying the text content, x and y coordinates (center of the widget), width, height, and optional background color. Colors are specified as hex strings or using Mural's named color values. When placing multiple sticky notes in a grid layout, calculate positions systematically: start at an offset from the top-left, add a fixed increment for each column, and wrap to a new row after reaching your column count. The Mural API also supports text labels (POST /widgets/text), shapes (POST /widgets/shape), and connectors (POST /widgets/connector) for connecting elements visually. For rich workshop setups, create frames first (POST /widgets/frame) to divide the canvas into zones, then place sticky notes within each frame's coordinate space. The frame widget acts as a visual container and helps participants navigate a complex board. Build a utility function that accepts an array of note objects and creates them all with calculated grid positions.
1# Python β add sticky notes in grid layout (add_stickies.py)2import requests3import os45ACCESS_TOKEN = os.environ['MURAL_ACCESS_TOKEN']6BASE_URL = 'https://app.mural.co/api/public/v1'78headers = {9 'Authorization': f'Bearer {ACCESS_TOKEN}',10 'Content-Type': 'application/json',11 'Accept': 'application/json'12}1314def add_sticky_note(mural_id, text, x, y, color='#FFF176', width=200, height=200):15 payload = {16 'text': text,17 'x': x,18 'y': y,19 'width': width,20 'height': height,21 'backgroundColor': color22 }23 resp = requests.post(24 f'{BASE_URL}/murals/{mural_id}/widgets/sticky-note',25 json=payload,26 headers=headers27 )28 if resp.status_code in (200, 201):29 return resp.json().get('value', {})30 else:31 print(f'Error adding sticky: {resp.status_code} - {resp.text}')32 return None3334def add_notes_grid(mural_id, notes, start_x=200, start_y=200, cols=4, gap=250):35 """Place notes in a grid layout."""36 created = []37 for idx, note in enumerate(notes):38 col = idx % cols39 row = idx // cols40 x = start_x + col * gap41 y = start_y + row * gap42 result = add_sticky_note(mural_id, note['text'], x, y, note.get('color', '#FFF176'))43 if result:44 created.append(result)45 print(f'Added {len(created)} sticky notes')46 return created4748# Example usage49MURAL_ID = 'your-mural-id-here' # Replace with actual ID from createMural response50notes = [51 {'text': 'What went well this sprint?', 'color': '#A5D6A7'},52 {'text': 'What could be improved?', 'color': '#EF9A9A'},53 {'text': 'Action items for next sprint', 'color': '#90CAF9'},54 {'text': 'Team shoutouts', 'color': '#FFF176'}55]56add_notes_grid(MURAL_ID, notes)Pro tip: Mural's coordinate system has (0,0) at the top-left of the canvas. When placing many sticky notes, leave generous padding between them (at least 20-30px gap around each 200px note) to avoid overlapping, which makes the board hard to read in the workshop.
Expected result: Sticky notes appear on the specified mural at the calculated grid positions. Refreshing the mural in the Mural app shows all newly added notes in the correct locations.
Common use cases
Automated Sprint Retrospective Board
At the end of each sprint, your Replit backend creates a new Mural from a retrospective template, adds the sprint number and team name as a title sticky note, and pre-populates the 'What we shipped' zone with task titles pulled from your project management tool. The team opens a fully prepared board without manual setup.
Build an Express endpoint that accepts a sprint number and team name, creates a new Mural in the 'Retrospectives' room, adds a title text label, creates three zone sticky notes labeled 'What went well', 'What to improve', and 'Action items', and returns the mural URL. Store MURAL_ACCESS_TOKEN and MURAL_WORKSPACE_ID in Replit Secrets.
Copy this prompt to try it in Replit
Research Data Visualization on Mural
After completing user research interviews, your Replit backend reads the interview notes from a Google Sheet or database, creates a new Mural, and auto-places each insight as a color-coded sticky note in the appropriate cluster zone based on theme tags. Researchers can then physically arrange and refine the affinity map rather than transcribing from documents.
Build a Flask API that accepts a JSON array of research insights (each with text, theme, and priority), creates a new Mural, and places each insight as a sticky note color-coded by theme (red for pain points, green for delights, yellow for needs), arranged in a grid layout. Return the mural URL.
Copy this prompt to try it in Replit
Workshop Template Generator
Your team runs a standardized customer discovery workshop every month. A Replit job triggered by a form submission generates a complete workshop mural from a parameter set (customer segment, date, facilitator name), including all standard zones, instructions, and participant names as sticky notes on a 'parking lot' frame. The facilitator opens a ready-to-use board.
Build a Flask endpoint at /generate-workshop that accepts customer segment name and date, creates a Mural with three frames (Intro, Discovery, Synthesis), adds instruction sticky notes to each frame, places the date and segment name in a title area, and returns the mural's share link.
Copy this prompt to try it in Replit
Troubleshooting
API returns 401 Unauthorized after the integration was working previously
Cause: The Mural access token has expired (typically after 1 hour). Without an automatic token refresh implementation, the stored MURAL_ACCESS_TOKEN becomes invalid.
Solution: Implement a token refresh function that uses MURAL_REFRESH_TOKEN to obtain a new access token via POST to the token endpoint with grant_type=refresh_token. Update MURAL_ACCESS_TOKEN in your application state. For persistent storage, write the refreshed token to a local JSON file in the Repl's filesystem.
1import requests, os, base642def refresh_mural_token():3 creds = base64.b64encode(4 f"{os.environ['MURAL_CLIENT_ID']}:{os.environ['MURAL_CLIENT_SECRET']}".encode()5 ).decode()6 resp = requests.post(7 'https://app.mural.co/api/public/v1/authorization/oauth2/token',8 data={'grant_type': 'refresh_token', 'refresh_token': os.environ['MURAL_REFRESH_TOKEN']},9 headers={'Authorization': f'Basic {creds}'}10 )11 return resp.json().get('access_token')POST to /murals returns 404 Not Found for a room that exists in the UI
Cause: The MURAL_ROOM_ID may be incorrect, or it belongs to a different workspace than MURAL_WORKSPACE_ID.
Solution: Call GET /workspaces/{workspaceId}/rooms to list rooms and verify the room ID. Room IDs are case-sensitive. Also confirm you are using the correct workspace ID β visible in the URL when logged into Mural.
Sticky notes are created but all appear stacked at the same position
Cause: The grid position calculation is producing the same x/y values for all notes, likely because the index variable is not incrementing or the position math has an error.
Solution: Add logging to print each calculated x/y position before the API call. Verify that start_x + col * gap and start_y + row * gap produce different values for each note. Check that cols is set to a reasonable number (3-5) and that gap is larger than the note width/height.
1# Debug: print positions before creating2for idx, note in enumerate(notes):3 col = idx % cols4 row = idx // cols5 x = start_x + col * gap6 y = start_y + row * gap7 print(f'Note {idx}: ({x}, {y}) β {note["text"][:30]}')OAuth callback returns 'redirect_uri_mismatch' error
Cause: The redirect URI used in the authorization request does not exactly match the one registered in the Mural app settings.
Solution: Copy the exact redirect URI from your Mural app settings and ensure your server uses the same string (including trailing slashes, http vs https, and port numbers). Update REPLIT_URL in your environment to match the current Replit preview URL, which changes when you create a new Repl.
Best practices
- Store MURAL_ACCESS_TOKEN, MURAL_REFRESH_TOKEN, MURAL_CLIENT_ID, MURAL_CLIENT_SECRET, MURAL_WORKSPACE_ID, and MURAL_ROOM_ID in Replit Secrets (lock icon π)
- Implement automatic token refresh before every API call since Mural access tokens expire after approximately 1 hour
- Calculate sticky note positions programmatically with clear spacing logic rather than hardcoding coordinates β workshops need to be scalable to variable amounts of content
- Create frames or zones on the mural canvas first, then place sticky notes within their coordinate bounds so the board has clear visual structure from the start
- Use color coding consistently (green for positives, red for pain points, blue for ideas) to make auto-generated murals immediately readable without manual legend setup
- Deploy as Autoscale if your Mural generation is triggered by external events; use Scheduled deployments for recurring workshop preparation jobs
- Add a short delay (200-500ms) between rapid API calls when creating many widgets β the Mural API can be sensitive to burst traffic
- Store created mural IDs in your database or a local JSON file so you can add content to them later without needing to search for them via the API
Alternatives
Miro has a broader template library and more mature API with better SDK support, making it a better choice for teams that want extensive pre-built workshop frameworks.
Notion is better suited for structured documentation and databases rather than visual workshop facilitation, with a simpler OAuth setup and more developer-friendly API.
Trello provides simpler kanban-style project boards with a well-documented REST API, suitable if your team needs task tracking rather than freeform visual collaboration.
Asana is better suited for structured project and task management with timelines, making it a better choice for recurring sprint planning where tasks need to flow from planning boards into project tracking.
Frequently asked questions
How do I connect Replit to Mural?
Register an app in the Mural developer portal, complete the OAuth 2.0 authorization code flow to get access and refresh tokens, store them in Replit Secrets (lock icon π), and make authenticated requests to the Mural REST API from your Express or Flask server using Bearer token authentication.
Does Replit work with Mural for free?
Mural API access requires a paid Mural plan (Business or Enterprise). Replit's free tier is sufficient for developing and testing the integration. You will need a paid Replit Autoscale deployment if your Mural automation needs to respond to external triggers reliably.
How do I store the Mural API key in Replit?
Mural uses OAuth 2.0 rather than a simple API key. After completing the authorization flow, store the MURAL_ACCESS_TOKEN and MURAL_REFRESH_TOKEN in Replit Secrets via the lock icon π in the sidebar. Also store MURAL_CLIENT_ID, MURAL_CLIENT_SECRET, and MURAL_WORKSPACE_ID for token refresh and API calls.
Can I add sticky notes to an existing Mural from Replit?
Yes. Use the POST /murals/{muralId}/widgets/sticky-note endpoint with the mural's ID. You need the mural ID (returned when the mural was created, or retrieved from GET /workspaces/{workspaceId}/murals) and a valid access token. Specify the text, x/y position, and optional color for each sticky note.
How do I automatically generate a Mural for each sprint retrospective?
Build a Replit server endpoint or scheduled job that calls createMural with a sprint-numbered title, then adds frame widgets to define the retrospective zones (What went well, What to improve, Action items), and optionally pre-populates sticky notes from your sprint data. Store the generated mural URL and share it with the team via Slack or email.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation