States, statuses, and option sets

How to keep lifecycle rules out of scattered page conditions and workflow branches.

You add a field called status. At first it only controls a label in the UI. Then the client asks for a button to show only when the thing is ready, a notification to send only when it is approved, and a dashboard to count what is overdue.

Status represents where the thing is in the business process, which action is allowed next, and which parts of the app should treat it differently.

Status is a rule

Most business objects have a lifecycle. An invoice can be draft, sent, paid, or void. A booking can be requested, confirmed, cancelled, or complete. An application can be submitted, reviewing, accepted, or rejected.

Those values decide how the app behaves. They control which buttons appear, which workflows are valid, which records appear in searches, which notifications send, and what dashboards count. If the status field is loose, every one of those places has to compensate.

Application
status: Application Status
submitted at
reviewer
Show Review button
Search active applications
Send approval email
Count applications in review
A status is used by more than the text element that displays it.

Text is too loose

A text field is usually the wrong place to store lifecycle status. Bubble will let you save pending, Pending,in progress, and In Progress as different values. Searches and conditions then depend on exact spelling.

status = pending
status = Pending
status = in progress
status = In Progress
status = complete
status = completed
A text status allows multiple spellings for the same lifecycle state.

A search for applications where status = Reviewing misses records saved as reviewing. A button condition has to check more than one text value. The fix is to stop using text for fixed lifecycle states.

Option sets

Use an option set when the statuses are fixed values in the app's logic. An Invoice Status option set might have Draft,Sent, Paid, and Void. Every workflow, condition, search, and reusable element now points to the same values.

Invoice Status
Draft
Sent
Paid
Void
Option sets give the app one controlled vocabulary for a fixed lifecycle.

This is the right fit when the statuses are not created by users, do not need permissions, and are not expected to change per client account. The statuses are part of how the app works.

When status needs a data type

Some statuses look fixed until a user wants to configure them. A CRM pipeline stage in a SaaS app might begin as an option set: Lead, Qualified, Proposal, Won, Lost. Then, we might want to support renaming stages, reordering them, restricting by team, or some other variant.

In that case, Pipeline Stage needs to be a data type, not an option set. Use a Pipeline Stage data type, and letDeal point to the current stage.

Deal
name
value
stage: Pipeline Stage
Pipeline Stage
name
sort order
win probability
team
automation
Use a data type when the status value needs fields of its own.

An option set works when every app uses the same values. A data type works when the values belong to an account, team, or user configuration. Once the user can edit the available stages, the stages need records in the database.

Allowed transitions

After you choose where the status lives, decide how it is allowed to change. Lifecycle transitions should happen through their own dedicated workflows. Do not set the status field directly from whatever workflow happens to be running, such as a button workflow, a save workflow, or a page condition that also changes data.

Finalize invoice
Accepts
Draft or Updated
Does
Validate required fields
Sets status
Finalized
Mark invoice paid
Accepts
Finalized or Overdue
Does
Record payment details
Sets status
Paid
Void invoice
Accepts
Draft or Finalized
Does
Store void reason
Sets status
Void
Avoid direct field updates like status = Paid
Status changes should go through workflows that own the allowed transition.

For example, a Finalize invoice workflow can take an invoice in any non-finalized state, validate the required fields, and mark it asFinalized. A Mark invoice paid workflow can require the invoice to be Finalized or Overdue, then set it to Paid. A button should call the workflow; it should not make changes to the invoice and set status = Paid itself.

This keeps the transition checks in one place. When the rule changes, you update the workflow that owns the transition instead of hunting for repeated status updates across pages.