Building AI workflows in Bubble without losing control
Build AI workflows like normal Bubble workflows: store the state, run private logic in backend workflows, and keep each step small enough to debug.
The model can decide what it wants to do next, but Bubble should still control the workflow. That means Bubble owns the data types, permissions, tool routing, database writes, retries, and final state shown to the user.
This matters even for fairly simple AI features. A "summarise this text" button can probably be one backend workflow. A support assistant that searches a knowledge base, writes messages, creates tickets, or calls external APIs needs a clearer structure.
Store the AI run in the database
Do not rely on one long workflow to hold the state of a multi-step AI run.
For chat-style AI, I would usually start with data types like:
ConversationMessageTool Call
Message can hold fields like content, role, isGenerating, error, and an optional Tool Call. Tool Call can hold the tool name, the tool call id, and the arguments returned by the model.
The point is debugging. If the model gives a weird answer, you want to see the user message, the assistant message, the requested tool, the arguments, the tool result, and the final answer. If those only existed inside a workflow run, you have made the app harder to fix.
You do not need to store every temporary value. Store the values another developer would need when the workflow fails.
Put the orchestration in backend workflows
AI workflows often involve API keys, private prompts, database writes, and external API calls. Put that logic in backend workflows or backend custom events where possible.
A normal pattern is:
Page workflow creates the user's
Messageand setsisGeneratingto yes.Backend workflow takes the
Conversation.Backend workflow builds the message history and calls the AI API.
Backend workflow stores the assistant
Message.If the model requested a tool, a custom event routes that tool call.
The tool result is stored, then the backend workflow runs the model again.
The page should mostly show state from the database: pending, complete, or errored. It should not contain the whole AI process.
One exception is a small UI-only feature. If the AI result does not touch private data, write records, send emails, or call other APIs, keeping the workflow simple is fine.
Treat tool calls as explicit steps
A tool call should have a name, inputs, validation, and a result.
For example, query_knowledgebase might take:
querycategorymax_results
That is workable. Bubble can check the tool name, parse the arguments, run the knowledge-base search, and return the result to the model.
Do not create a generic "do anything" tool unless you have a very good reason. It is harder to validate, harder to permission, and harder to debug.
For multi-tool workflows, use a router custom event. The router receives the Tool Call, checks the tool name, then runs the matching custom event. Each tool should do one thing: search the knowledge base, fetch an invoice, get availability, create a draft, or whatever the app actually needs.
Keep tool definitions consistent
If you copy tool schemas, model ids, prompt fragments, or API URLs into several workflows, they will drift.
Option sets can work well for stable AI config:
AI Roleforsystem,user,assistant, andtoolAI Modelfor model ids and pricing attributesAI Toolfor tool names and function schemas
This is especially useful for tool calling because the function schema and the Bubble router need to agree. If you rename a tool in one place and forget the other, the model can request a tool that your workflow does not recognise.
One thing to flag: option sets are visible on the client. That may be fine for tool schemas, because the schema is normally just the shape of the request. If the tool list or schema is sensitive, return the allowed tools from a backend custom event instead.
Use global expressions for shared expressions
Global expressions are useful for values and calculations you use in more than one place.
For AI workflows, that might be:
Get AI API Base URLFormat Message As JSONGet Allowed AI ToolsUser Has AI PermissionGet Default AI Model
The reason to use a global expression is simple: you change the expression once. That is better than finding the same :format as text or model id copied through five workflows.
Do not put secrets in a global expression just because it is convenient. API keys, private system prompts, and sensitive permission checks should usually stay in backend workflows or backend custom events.
Reuse small pieces, not one giant AI engine
Reusable logic is useful when it keeps one thing in one place.
Good reusable pieces might be:
format a
Messageas JSONreturn the allowed tools for this feature
check whether the current user can run an AI action
run the
query_knowledgebasetoolmark a message as errored
Bad reusable logic is a custom event with twelve parameters and twenty conditions because every AI feature got pushed into it.
Use the right Bubble feature for the job:
global expressions for shared expression logic
custom events for repeated page or reusable element logic
backend custom events for reusable server-side logic that returns data
backend workflows for scheduled work, external endpoints, and longer AI runs
option sets for stable product constants where client visibility is fine
There is overlap between these features. That is fine. The test is whether the next developer can tell what the piece does without opening half the app.
Add basic failure handling
AI workflows fail in boring ways.
The model returns invalid JSON. The tool name does not match. The API times out. The user sends another message before the first one finishes. The model asks for a tool the user should not be able to use.
Add the basic checks:
validate tool names before running custom events
validate tool arguments before using them in searches or API calls
check permissions inside the backend workflow before sensitive actions
store
isGeneratingor a status fieldstore an
errorfield and useful error textadd a maximum number of model/tool-call loops
The loop limit is important. If the workflow can call the model, run a tool, then call the model again, set a hard maximum. A bad prompt or bad tool schema should not create an endless backend workflow.
Start smaller than you think
Most apps do not need a full agent on day one.
If the feature is "write a title", "summarise this note", or "classify this enquiry", use one backend workflow and store the result. That is probably enough.
If the feature needs tools, private prompts, multiple model calls, or database writes, give it a proper structure:
store the AI request and result
store tool calls and tool results
run orchestration in backend workflows
keep each custom event narrow
check permissions before the tool does anything sensitive
That is the main rule. The AI part can be clever. The Bubble workflow around it should be boring enough to debug.