URL-Activated Debug Logging for Your ExB Custom Widgets

577
2
03-26-2026 07:02 AM
adamsimple
Occasional Contributor
3 2 577

debugger-feed-output.png

When I started building my first custom ExB widget, one of the earliest things I invested in was a structured debug logging system. I use AI heavily in my development workflow (what I call "Vector coding," where the human architects and AI executes), and that approach demands real data to inform decisions. I needed to see exactly what was happening inside the widget at every layer, and I needed that visibility to be something I could switch on and off without touching code.

So I built DebugLogger: a small utility that activates structured debug logging from the URL. Add ?debug=FETCH to your ExB URL and you get clean, tagged logs for your network calls. Add ?debug=FETCH,RENDER and you see rendering too. Remove the parameter and logging goes silent. Zero overhead when it's off, no rebuilds, works the same in dev, test, and production.

It's become one of the most valuable pieces of my toolkit. When someone on my team hits an issue, the conversation is simple: "Turn on ?debug=FETCH,SELECTION, reproduce the problem, and send me the console output." I get a complete picture of what happened. No guessing, no poking around in the dark, just the data to diagnose and fix.

I've been using this across every widget in my MapSimple open-source suite, and I pulled it out as a standalone drop-in file so anyone building ExB widgets can use it. Hopefully it saves you some time.

One File, Five Minutes

No dependencies. No build config changes. Copy it into your widget and go.

Step 1: Grab debug-logger-standalone.ts from the DebugLogger release and drop it into your widget:

your-widget/
  src/
    utils/
      debug-logger.ts    <-- right here
    runtime/
      widget.tsx

Step 2: Open the file and edit the bottom section. Replace the widget name and tags with your own:

export const debugLogger = createDebugLogger('MYWIDGET', [
  'FETCH',      // API/network calls
  'RENDER',     // UI rendering
  'CONFIG',     // Settings load
  'MAP',        // Map interactions
  'SELECTION',  // Record selection
  'LIFECYCLE'   // Widget open/close
])

Tags are just labels for the subsystems in your widget. You decide what makes sense.

Step 3: Use it:

import { debugLogger } from '../utils/debug-logger'

debugLogger.log('FETCH', { action: 'start', url })

That's it. You're done.

Activate It From the URL

This is the whole point. No code changes to toggle logging:

URL What Happens

?debug=FETCHLogs only FETCH-tagged calls
?debug=FETCH,RENDERLogs FETCH and RENDER
?debug=allLogs everything
(nothing)Silent. Zero overhead.

Tags are case-insensitive, so ?debug=fetch and ?debug=FETCH both work.

ExB Iframe Note

This tripped me up early on. ExB renders widgets inside iframes, so when you add ?debug=FETCH to your browser URL, that param lives on the parent page, not the iframe. The logger handles this automatically. It checks window.location first, then falls back to window.parent.location. You just add the param to whatever URL is in your address bar and it works.

What the Output Looks Like

Clean, structured JSON with a tagged prefix:

[MYWIDGET-FETCH] {
  "feature": "FETCH",
  "timestamp": "2026-03-25T10:30:00.000Z",
  "action": "start",
  "url": "https://services.arcgis.com/..."
}

The tag tells you the subsystem, the data is structured, and it's easy to filter in DevTools.

The BUG Level

One pattern I've found really useful: a BUG log level that always fires, even when ?debug isn't in the URL.

debugLogger.log('BUG', {
  bugId: 'BUG-007',
  category: 'SELECTION',
  description: 'Selected record ID not found in output DS',
  recordId: dataId
})

This uses console.warn so it stands out. I use it for race conditions I've identified but can't fully prevent, fallback paths that shouldn't normally execute, and data integrity issues. It's like a tripwire. If it fires in production, you know exactly what happened without needing to reproduce it with debug mode on.

Tags That Work Well for ExB

After using this across several widgets, here are the tags I keep coming back to:

Tag What I Log

CONFIGSettings load, validation, migration
RENDERMount/unmount, card display, template rendering
FETCHNetwork requests, responses, errors
SELECTIONRecord selection, highlight, deselection
MAPView interactions, layer ops, popups
DARK-MODETheme detection and switching
LIFECYCLEWidget open/close, visibility changes

Keep them short and uppercase. They show up in both the URL and the console, so brevity helps.

Performance

When debug is off (no ?debug in the URL), the cost is essentially zero. The log() call checks a boolean and returns immediately without serializing anything.

When it's on, each call runs JSON.stringify() on whatever you pass in. For normal widget work this is negligible. Just don't put it inside a scroll handler or animation loop unless you throttle it.

See It in Action

Want to see what this looks like in a real widget? Open your browser's DevTools console and hit this link:

https://exb-sample.mapsimple.org/?debug=QUERY,HASH-EXEC#pin=2223059013

That URL activates two debug tags (QUERY and HASH-EXEC) and passes in a hash-based query. Watch the console as the widget parses the hash, executes the query, and returns results.

Here's another one using FeedSimple, a live data feed widget pulling USGS earthquake data:

https://feed-quakes.mapsimple.org/?debug=FETCH,FEED-LAYER

This one shows the data fetch cycle and the feed layer creation. Same logger, different widget, same clean output. That's the kind of visibility you get.

Here are the tags you can try on the FeedSimple sample:

Tag What You'll See

FETCHFeed URL requests, response status, timing
PARSEXML parsing, field discovery, item counts
RENDERCard rendering, template processing
POLLAuto-refresh polling cycle
JOINSpatial join with map layers
FEED-LAYERMap layer creation, points, symbology, popups
TEMPLATEMarkdown template processing, token substitution
SETTINGSSettings panel activity, config changes
EXPORTCSV/data export
SEARCHRuntime search filtering
SORTRuntime sort operations
FEATURE-EFFECTFeature effects (highlight, filter on map)

And here are the tags for the QuerySimple / HelperSimple sample:

Tag What You'll See

allEvery log across both widgets (heads up, high volume)
HASHDeep link consumption and URL parameter parsing
TASKQuery execution, performance metrics, and data source status
RESULTS-MODETransitions between New, Add, and Remove selection modes
EXPAND-COLLAPSEState management for result item details
SELECTIONIdentify popup tracking and map selection sync
RESTORELogic used to rebuild the map selection after an identify event
WIDGET-STATEThe handshake between HelperSimple and QuerySimple
GRAPHICS-LAYERHighlighting logic for graphics-enabled widgets

Go Get It

The standalone file and full guide are in the DebugLogger release. One TypeScript file, no external dependencies, works with any ExB custom widget. Copy it, edit the config at the bottom, and you should be up and running in about five minutes.

If you're curious about the broader MapSimple project, it's a growing suite of open-source ExB widgets. The DebugLogger is the shared foundation that all of them use.

Questions, ideas, or bugs? Feel free to drop those here and I will work to address them. I really hope this can help ExB devs in the community. Happy coding!


Download: debug-logger-standalone.ts | Project: MapSimple

2 Comments
Contributors
About the Author
GIS Manager at King County and Founder of MapSimple. Exploring the "Architect vs. Pen" philosophy by leveraging AI to modernize legacy GIS tools. Currently reclaiming custom development in Experience Builder, one widget at a time.