Before starting, ensure you have:
When creating user scripts for the Waze Map Editor (WME), you must either create a browser extension or a user script that can be installed through Greasemonkey or Tampermonkey. Tampermonkey is the recommended extension due to recent changes in how Greasemonkey functions.
This guide focuses on scripts installed via Tampermonkey. Creating browser extensions is similar, but a separate topic. We’ll build a simple script to demonstrate the basics of interacting with the Waze object and the WME SDK.
Every Tampermonkey script begins with a special header block that tells the browser extension:
Here is a sample default header:
// ==UserScript==
// @name New Userscript
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match http://*/*
// @grant none
// ==/UserScript==
💡 Learn More: Greasy Fork’s rules for posted scripts!
Here you’ll assign a script name, set a namespace (often your GreasyFork profile link for auto-updates), add version, description, author, and specify what pages the script will run on.
Here’s an example for from the WME Wazebar Script:
// ==UserScript==
// @name WME Wazebar
// @namespace https://greasyfork.org/users/30701-justins83-waze
// @version 2025.05.30.01
// @description Displays a bar at the top of the editor that displays inbox, forum & wiki links
// @author JustinS83
// @include https://beta.waze.com/*
// @include https://www.waze.com/discuss/*
// @include https://webnew.waze.com/discuss/*
// @include https://www.waze.com/editor*
// @include https://www.waze.com/*/editor*
// @exclude https://www.waze.com/user/editor*
// @require https://greasyfork.org/scripts/27254-clipboard-js/code/clipboardjs.js
// @connect storage.googleapis.com
// @connect greasyfork.org
// @grant none
// ==/UserScript==
Best practice: Use @exclude for any pages you do not want your script to run on to avoid unexpected issues.
💡 Learn More: User Script Meta Keys!
For your first script, use this header:
// ==UserScript==
// @name WazeDev First Script // Display name in Tampermonkey dashboard
// @namespace http://tampermonkey.net/ // Unique identifier (use your GreasyFork profile for updates)
// @version 0.1 // Version number for update tracking
// @description Learning to script! // Brief description of functionality
// @author You // Your name or username
// @include https://beta.waze.com/* // Run on beta WME (testing environment)
// @include https://www.waze.com/editor* // Run on production WME
// @include https://www.waze.com/*/editor* // Run on localized WME URLs
// @exclude https://www.waze.com/user/editor* // Don't run on user profile pages
// @exclude https://www.waze.com/editor/sdk/* // Don't run on SDK documentation
// @grant none // No special permissions needed
// ==/UserScript==
This allows your script to run in both production and beta WME, but not on editor profile or SDK pages.
Below the header, Tampermonkey inserts a wrapper for your script:
(function() {
'use strict';
// Your code here...
})();
Your script logic goes inside this function.
A bootstrap routine ensures WME and its SDK are fully loaded before your script runs, preventing errors.
SDK Initialization and Bootstrap Example
const SCRIPT_NAME = GM_info.script.name; // Get the Script name from the @name tag of the UserScript Meta Keys
let wmeSDK; // Declare wmeSDK globally
// Initialization of the WME SDK
if (window.SDK_INITIALIZED) {
console.log(`${SCRIPT_NAME}: SDK initialized...`);
window.SDK_INITIALIZED.then(bootstrap).catch((err) => {
console.error(`${SCRIPT_NAME}: SDK initialization failed`, err);
});
} else {
console.warn(`${SCRIPT_NAME}: SDK_INITIALIZED is undefined`);
}
function bootstrap() {
try {
wmeSDK = getWmeSdk({
scriptId: SCRIPT_NAME.replaceAll(" ", ""),
scriptName: SCRIPT_NAME,
});
Promise.all([wmeReady()])
.then(() => {
console.log(`${SCRIPT_NAME}: All dependencies are ready.`);
init();
})
.catch((error) => {
console.error(`${SCRIPT_NAME}: Error during bootstrap`, error);
});
} catch (error) {
console.error(`${SCRIPT_NAME}: Failed to initialize SDK`, error);
}
}
function wmeReady() {
return new Promise((resolve) => {
if (wmeSDK.State.isReady()) {
console.log(`${SCRIPT_NAME}: WME is ready.`);
resolve();
} else {
console.log(`${SCRIPT_NAME}: Waiting for WME to be ready...`);
wmeSDK.Events.once({ eventName: "wme-ready" })
.then(() => {
console.log(`${SCRIPT_NAME}: WME is ready now.`);
resolve();
})
.catch((error) => {
console.error(`${SCRIPT_NAME}: Error while waiting for WME to be ready:`, error);
});
}
});
}
💡 Learn More: For complete SDK documentation, visit the WME SDK Reference Sight
What this achieves:
SDK Initialization Check: First, it verifies that window.SDK_INITIALIZED exists and waits for the SDK initialization promise to resolve.
WME SDK Setup: Once the SDK is initialized, the bootstrap() function creates the WME SDK instance using getWmeSdk() with your script’s name and ID.
wmeReady() function ensures the WME environment is fully loaded. This prevents errors that occur when scripts try to access WME data before it’s available. The function checks if:
Event-Based Waiting: If WME isn’t ready immediately, the script listens for the special “wme-ready” event using wmeSDK.Events.once(). This event only fires once after all initialization, login, and initial data loading is complete.
init() function is called (you can name this whatever you want). This is where you would set up your script using SDK modules like:
wmeSDK.DataModel - Access segments, venues, nodes, etc.wmeSDK.Map - Control map view and add layerswmeSDK.Sidebar - Create custom UI panelswmeSDK.Events - Register for WME eventswmeSDK.Settings - Manage user preferencesYou might wonder why we need this seemingly complex initialization process instead of just writing our script code directly. The bootstrap pattern is essential for WME scripts because of how web applications load:
When a web page loads, many things happen simultaneously:
Without a bootstrap pattern, your script might try to access WME data or create UI elements before they exist, causing errors like:
// ❌ This fails if WME isn't ready yet
const segments = wmeSDK.DataModel.Segments.getAll(); // Error: Cannot read property 'Segments' of undefined
The bootstrap pattern ensures everything your script needs is available before it runs:
Real-World Analogy Think of it like cooking a meal:
Your script should look like this so far:
// ==UserScript==
// @name WazeDev First Script // Display name in Tampermonkey dashboard
// @namespace http://tampermonkey.net/ // Unique identifier (use your GreasyFork profile for updates)
// @version 0.1 // Version number for update tracking
// @description Learning to script! // Brief description of functionality
// @author You // Your name or username
// @include https://beta.waze.com/* // Run on beta WME (testing environment)
// @include https://www.waze.com/editor* // Run on production WME
// @include https://www.waze.com/*/editor* // Run on localized WME URLs
// @exclude https://www.waze.com/user/editor* // Don't run on user profile pages
// @exclude https://www.waze.com/editor/sdk/* // Don't run on SDK documentation
// @grant none // No special permissions needed
// ==/UserScript==
(function() {
'use strict';
const SCRIPT_NAME = GM_info.script.name; // Get the Script name from the @name tag of the UserScript Meta Keys
let wmeSDK; // Declare wmeSDK globally
if (window.SDK_INITIALIZED) {
console.log(`${SCRIPT_NAME}: SDK initialized...`);
window.SDK_INITIALIZED.then(bootstrap).catch((err) => {
console.error(`${SCRIPT_NAME}: SDK initialization failed`, err);
});
} else {
console.warn(`${SCRIPT_NAME}: SDK_INITIALIZED is undefined`);
}
function bootstrap() {
try {
wmeSDK = getWmeSdk({
scriptId: SCRIPT_NAME.replaceAll(' ', ''),
scriptName: SCRIPT_NAME,
});
Promise.all([wmeReady()])
.then(() => {
console.log(`${SCRIPT_NAME}: All dependencies are ready.`);
init();
})
.catch((error) => {
console.error(`${SCRIPT_NAME}: Error during bootstrap`, error);
});
} catch (error) {
console.error(`${SCRIPT_NAME}: Failed to initialize SDK`, error);
}
}
function wmeReady() {
return new Promise((resolve) => {
if (wmeSDK.State.isReady()) {
console.log(`${SCRIPT_NAME}: WME is ready.`);
resolve();
} else {
console.log(`${SCRIPT_NAME}: Waiting for WME to be ready...`);
wmeSDK.Events.once({ eventName: 'wme-ready' })
.then(() => {
console.log(`${SCRIPT_NAME}: WME is ready now.`);
resolve();
})
.catch((error) => {
console.error(`${SCRIPT_NAME}: Error while waiting for WME to be ready:`, error);
});
}
});
}
function init() {
// Your code here
console.log(`${SCRIPT_NAME}: Script initialized successfully!`);
}
})();
Expected console output:
WazeDev First Script: SDK initialized...
WazeDev First Script: WME is ready now.
WazeDev First Script: All dependencies are ready.
WazeDev First Script: Script initialized successfully!
This covers the very basics for getting started on a script. In the next section we will look at setting up a tab in the side panel where our script can create controls to toggle settings.