Bootcamp
A debugging method for visual developers
Photo by Lou Brassard on Unsplash
The mark of a good developer is not never making mistakes. It’s being able to find the error quickly and implement a reliable fix. That’s as true in Bubble as in any programming language, and Bubble actually gives you decent tools for it. Most builders just never move past the default technique of changing something and hoping.
What follows is a method: reproduce the fault, isolate the workflow responsible, step through it while inspecting real values, and check the server’s account of what happened. Then two extensions of the method that pay for themselves many times over: error handling that never fails silently, and a debug-table technique that turns opaque API failures into readable answers.
Debugging is a method, not a mood: reproduce the fault, isolate the workflow responsible, step through it while inspecting real data, and read the logs. The goal is always the same, to find the assumption that failed, quickly.
Run it in the debugger
When you preview a Bubble app, the page loads with debug_mode=true as a URL parameter, which brings up the debugger bar at the bottom of the screen. You can add that parameter to any page of your app yourself. The bar does two things: it lets you inspect elements on the page and see how their expressions are currently evaluating, and it controls workflow execution in three modes.
- Run is the default: workflows execute normally.
- Slow steps through each action in slow motion so you can watch the sequence happen.
- Step-by-step halts before every action and waits for you to press “run next”.
Step-by-step is where the real value is. Take a simple contact form: press send with the debugger in step-by-step mode and you can verify each action as it fires, checking Input Email's value, the multiline input’s message and the dropdown’s value before the form resets, then watching the toast alert, the send-email action and the page navigation each execute in turn. Instead of guessing which of five steps misbehaved, you watch the workflow’s actual state at every point. You also don’t need the old trick of putting temporary text elements on the page to display a custom state or a calculation: the inspector shows you how any expression is evaluating live.
If the fault happens deep in a session and you don’t want to step through everything before it, add a breakpoint to the workflow event in the editor. The app then runs at normal speed until that workflow fires, at which point the debugger automatically drops into step-by-step mode.
Isolate the problem
Debugging is faster when you shrink the search space, and Bubble gives you two blunt but effective tools.
First, you can disable a workflow entirely. A good habit when experimenting: copy the workflow, disable the original, and delete or modify steps freely in the copy, knowing the working version is intact. (Rename the copy immediately. Two identical-looking workflows side by side is its own bug waiting to happen.)
Second, drop a terminate this workflow action partway through a sequence to stop everything after it. It’s a quick way to answer “does the first half of this workflow behave?” without the second half muddying the water. Terminate has reliability caveats in production logic, but as a temporary debugging scalpel it earns its keep.
When the debugger changes the answer
Here’s the trap every visual developer eventually falls into: the debugger can slightly alter the timing of workflow execution, which means it can produce a different outcome from a normal run.
A demonstration. Build a workflow with two steps: create a Timing Test record, then display the text “hello” in a group, with a condition of Search for Timing Tests:count is 1. Run it at normal speed on an empty table and the record is created, but the text never appears: the search evaluates before the newly created row is visible to it. Now delete the row and run the same workflow step-by-step. This time the search returns a result and the text displays. Nothing changed except the debugger slowing things down long enough for the server to catch up.
It cuts the other way too. In a second test, a workflow first retrieves a list of Timing Test records, then creates one. Inspect the first step in the debugger afterwards and the search shows the row that was only created in the later step, as if data existed before it was created. The inspector is showing you the search’s current evaluation, not a snapshot from the moment the step ran.
Two lessons. A bug that disappears under the debugger is not fixed; it’s a timing bug wearing a disguise, and it needs a structural fix (sequencing steps via results-of-step references or custom events) rather than a shrug. And treat what the inspector shows about past steps with mild scepticism when data changed mid-workflow.
Log to a debug table
Sometimes the fastest instrument is one you build yourself in two minutes. Create a data type called Debug with a single text field. Then, at the points in a workflow you care about, add a create-a-new-thing step that writes something meaningful into it: Result of step 2's unique id, a label for which branch executed, whatever answers your current question.
This works because raw data is hard to interpret. When there’s a lot of it and the fields you care about are scattered, patterns hide. A debug table with a few deliberately chosen values makes correct and incorrect immediately distinguishable, and you can watch it fill up run by run. We use this constantly, and think of it as scaffolding: temporary structure you put up to build accurately and quickly, then take down.
Reading the server logs
For anything that happened on the server, the Logs tab holds the server logs: a long list of every workflow run, where even starting a workflow expands into sub-steps as each conditional is evaluated and reported. Useful, and genuinely hard to read. Some hard-won advice:
- Narrow the time window first. Debug what happened in the last three to five minutes rather than searching a haystack. Your plan’s retention window determines how far back logs are kept at all.
- Filter by user email on a live app with multiple users.
- Search for a string you know appears in the relevant workflow, like “stripe” or “contact”, then narrow by time from there. The advanced options let you filter by log type as well.
- Browser find (Cmd-F) works on a loaded log, with one caveat: the list loads as an endless scroll, so results only exist once you’ve scrolled them into view.
Error handling: never fail silently
Debugging after the fact is half the discipline. The other half is designing workflows that surface their own failures, which starts with considering all possibilities: the path where everything works, the path where a step fails, the path where an input isn’t available, and the path where a user finds a way around your controls. They will. Assume it.
Take a workflow where step 1 makes changes to an order, setting its status to processed, and step 2 updates all the order’s line items using Result of step 1. If step 1 fails, step 2 runs against nothing and you get orphaned line items, data you can’t link back to where it came from. The first fix is a guard: give step 2 the condition Only when Result of step 1's order is not empty. Corruption prevented.
But now ask the harder question: when that guard trips, who finds out? Nobody. The workflow fails silently, and silent failure can be far worse than a visible error. We once worked on an app that generated recurring meal orders for people on disability support in Australia; a silently failing backend loop there wouldn’t have meant a stale record, it would have meant vulnerable people missing a week of meal deliveries. The standard is simple: no workflow that matters is allowed to fail without telling someone. So alongside the guard, show the user an alert (“Error: order not found”), send the admin an email with the details so someone can take action, and log the error for later troubleshooting.
Since that’s several actions, package them as a custom event, handle error, triggered only when Result of step 1's order is empty. You get reusability (put it inside a reusable element and every page can trigger the same handler) and a single place to improve your error handling later.
The planning tool for all of this is unglamorous: draw a table of every path the workflow could take, what the impact of each is, and how you’ll handle it. Where does the order come from? The page URL? What happens when a user deletes half the path? Anticipating that is part of the job, and the payoff arrives the day your live app has a few hundred users and something breaks: instead of an unfindable mystery, you have an alert, an email and a log entry pointing at the cause.
Debugging API calls
API integrations are where errors congregate, and where the server logs most often let you down. Trigger a failing external call and the logs will dutifully show the workflow starting, the endpoint requested and a response received, yet frequently nothing useful about what actually went wrong. That’s not unusual when working with APIs.
The debug table earns its keep again here. In the workflow that makes the call, add a step that creates a Debug row and store the call’s raw body: the complete JSON object the server returned, success or failure. Now every run leaves the full response where you can read it. Paste it into a code editor to see the structure properly, since a JSON blob crammed into one database cell is legible but not pleasant.
When an API’s developers are helpful, the payoff is immediate: a response like invalid argument long_url: the value provided is invalid tells you precisely which field to fix. Many APIs return far more generic errors, but even those are more than the logs were offering. The comparison is stark: fifteen or thirty minutes spelunking in the server logs, or one glance at your debug table. Scaffolding yourself, again, and it’s exactly this habit that separates developers who fix integration problems in minutes from those who lose afternoons to them.
This skill in the AI era
Nothing in this method is Bubble-specific: reproduce, isolate, step through, inspect, read the logs, and never let anything fail silently. When Claude Code writes the software, your leverage shifts even further toward diagnosis, because directing an AI to fix a bug is only as good as your ability to locate and describe it. It’s the first skill we reach for in Bubble rescue and migration work, where someone else’s silent failures are the whole engagement, and it tops the list of Bubble habits that transfer straight into AI-assisted development.
If this sparked something, let's talk.
No pitch, no pressure — just a conversation about what you're working on.
Let's talkRelated posts
Bootcamp
A simple approach to data modelling
A four-step method for designing a database schema: identify entities, map relationships, choose fields. Taught through the CRM data model built in Bubble.
Bootcamp
Module 5 — Application architecture: pages, states and reusable pieces
Single-page vs multi-page apps, URL parameters, custom states, reusable elements — and a big practical building a main template with an expanding navigation drawer.
Bootcamp
API calls as actions vs data (and the planning checklist)
When to use Bubble API calls as actions versus data sources, a one-to-many auth pattern, and the planning checklist that keeps integrations from failing silently.