Style guide

Reusable logic without turning the app into spaghetti

Reuse logic only when it makes the app easier to change

Duplicated workflow steps are bad, but hiding everything inside custom events, reusable elements, and backend workflows can be just as bad.

The useful test is simple: can another Bubble developer open the workflow and understand what happens next?

Use reusable logic when it removes repeated work or makes order clearer. Do not use it to hide messy logic. A custom event called Run Checkout Logic with 30 actions inside is still a messy workflow.

Use custom events when step 2 depends on step 1

Custom events are useful when one group of actions must finish before the next thing happens.

For example, a checkout workflow might need to:

  1. Create or update the Customer.

  2. Create an Invoice for that Customer.

  3. Show the confirmation popup.

If the invoice action depends on the Customer being ready, move the customer actions into a custom event called Prepare Customer For Checkout.

The parent workflow can then be:

  1. Trigger Prepare Customer For Checkout.

  2. Create Invoice using the result.

  3. Show confirmation popup.

That is easier to debug than ten mixed actions where step 7 only works because step 3 changed the right thing.

One thing to flag: do not use custom events as a way to avoid naming the actual job. If a custom event has several unrelated branches, split it.

Use reusable elements for repeated frontend calculations

If the same frontend calculation appears on multiple pages, put it in one place.

A reusable element can work well for this. Give it reusable element properties for the inputs, then run a custom event inside the reusable to calculate or return the value.

This might be a pricing calculator used on a marketing page and a logged-in dashboard. The reusable element takes properties like seats, plan, billing interval, and add-ons. Its custom event returns the calculated price. When pricing changes, you update the reusable once.

This is especially useful for:

  • pricing calculations

  • tax estimates

  • permission display checks

  • formatted labels

  • repeated API URL or environment values

Do not scatter hidden helper reusables around the app without clear names. If a page needs reusable - pricing calculator to work, name it that way and keep it somewhere obvious on the page.

Use backend custom events when the frontend needs a returned server value

Returning data from backend custom events now exists. Treat that as the modern option when the page needs a value back from server-side logic.

The old pattern was to call your own backend workflow through the API Connector or App Connector, then use the API result in later frontend steps. That was useful at the time, but it was clunky. You had to keep the API call and backend workflow parameters in sync, refresh metadata, and deal with awkward parameter issues.

Now, if the page needs a narrow result from server-side logic, use a backend custom event where it fits.

Good examples:

  • return whether the Current User can perform an action

  • return a generated sequence number

  • return a calculated price, score, or eligibility result

  • call a private API and return only the value the page needs

  • return a true/false answer instead of exposing a searchable data source

Return the smallest useful value. If the backend custom event searches private records and returns the records, you have still exposed the records.

Do not replace every backend workflow with returned data

Backend custom event return data is useful, but it is not the right tool for every backend job.

Use API-style backend workflows when:

  • Stripe, Postmark, Make, Zapier, or another external service needs to call your app

  • the work should continue if the user closes the tab

  • the work might take minutes

  • the same endpoint is deliberately used by another system

  • you need scheduled, recursive, or list-based processing

If the next frontend step needs the returned value, a backend custom event is probably the cleaner option.

If the user starts a bulk import, PDF generation, AI run, or invoice batch, do not keep the page waiting for returned data. Start the backend workflow and track progress separately.

Use a backend status data type for long-running work

If backend work takes time and the page needs to react when it finishes, create a status thing.

The pattern:

  1. Create a Backend Status thing before scheduling the backend workflow.

  2. Link it to the User, Company, Account, or other thing needed for privacy rules.

  3. Add a Status option set field, such as queued, running, complete, and failed.

  4. Pass the Backend Status thing into the backend workflow.

  5. Update the Backend Status from the backend workflow.

  6. On the page, use Trigger custom event when data changes to react when the status changes.

For example, an admin clicks Generate invoices for 500 customers. The page creates a Backend Status, schedules the backend workflow, and shows progress from that status. The backend workflow marks it as running, then complete, or failed with an error message.

That is better than counting records in a repeating group and hoping the number eventually matches.

Do not use this for tiny actions. If the workflow creates one thing and the next step can use Result of step 1, a Backend Status thing is probably overkill.

Keep page inputs out of permission checks

Reusable properties, custom states, group data, inputs, URL parameters, and frontend searches are page data. Treat them as values the user can influence.

Frontend workflows are not automatically insecure. If a workflow action hits the server and the condition checks Current User's Role is Admin, Bubble can evaluate that server-side.

The problem is using page-provided values as permission checks.

Bad:

  • archive a Project because a reusable property says canArchive is yes

  • create an admin-only record because a custom state says selectedRole is Admin

  • show a hidden button only to admins, then leave the workflow action itself loose

Better:

  • archive the Project only when Current User is allowed to archive that Project

  • set roles only in a workflow with a server-side admin condition

  • use privacy rules to restrict what the page can read

  • return a narrow value from a backend custom event when the page only needs an answer

Secure the workflow action itself.

Use the simplest pattern that matches the job

For repeated frontend calculations, use a reusable element, reusable element properties, custom events, or global expressions.

For ordered workflow steps, use custom events.

For a returned server-side value, use a backend custom event.

For webhooks, scheduled jobs, recursive jobs, and work that must continue without the page, use backend workflows.

For long-running backend work that the page needs to observe, use a Backend Status thing.

For permission checks, use server-side workflow conditions and privacy rules. Do not rely on hidden elements, reusable properties, group variables, or frontend searches.

Was this helpful?