Style guide

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:

  • Conversation

  • Message

  • Tool 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:

  1. Page workflow creates the user's Message and sets isGenerating to yes.

  2. Backend workflow takes the Conversation.

  3. Backend workflow builds the message history and calls the AI API.

  4. Backend workflow stores the assistant Message.

  5. If the model requested a tool, a custom event routes that tool call.

  6. 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:

  • query

  • category

  • max_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 Role for system, user, assistant, and tool

  • AI Model for model ids and pricing attributes

  • AI Tool for 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 URL

  • Format Message As JSON

  • Get Allowed AI Tools

  • User Has AI Permission

  • Get 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 Message as JSON

  • return the allowed tools for this feature

  • check whether the current user can run an AI action

  • run the query_knowledgebase tool

  • mark 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 isGenerating or a status field

  • store an error field and useful error text

  • add 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.

Was this helpful?