Skip to content
Back to the Bootcamp
The Momentum Bubble Bootcamp, free. Part 8 of the paid course that trained hundreds of Bubble developers — released free, because the thinking it teaches is exactly what great AI-assisted development is built on.

Bootcamp

Wiring an onboarding flow to your database

A form that looks finished and saves nothing is still a prototype. The hard part of an onboarding flow isn’t the inputs; it’s the timing. Where does the data live? When does the record get created? When does each field get written? What happens when someone abandons the flow halfway and comes back tomorrow? And how do you finally flip the switch that says “this profile is live”?

Answer those questions deliberately and the interface almost wires itself. Skip them and you’ll ship a flow that silently loses people’s answers.

Saving form data is a set of timing decisions: when the record is created, when each field is written, what survives a reload, and when the result is published. In Bubble that means choosing the workflow events that create, update and finalise a row, not just binding inputs to fields.

The questions the database forces you to answer

Before building, list them out: where do these fields get stored, and on which data type? When is the row of data created? When are the fields saved into it? How are they validated? If someone leaves and returns, is their progress still there? And how is the profile published once complete?

The first question has no single right answer. You could store everything on the User: it’s lightweight and needs no extra data types. But it ties the data to one user, which hurts the moment a company account needs multiple users, and you may want profile data to be public while the User stays private. For this flow we create a dedicated Profile data type and move on; the deeper trade-offs belong to data modelling.

Build the forms

The profile step collects a photo, name, phone and location.

  1. Copy an existing input group from another page into Group Profile Form and duplicate it for each field. Use a Picture Uploader for the photo, not a File Uploader; that substitution is a mistake we’ve watched trip up plenty of builders.
  2. Name the elements with Bubble’s default names in front: Input Name, Input Phone. You’ll thank yourself as soon as workflows reference them.
  3. For location, right-click the input and replace the element type with a Search Box, set to geographic places. This powers dynamic address search through the Google Maps geolocation API. Call it SearchBox Location.
  4. Put name and phone side by side by grouping them into a row container with a 16px gap. Group the picture uploader with its label and helper text into a column, with small 4px and 8px gaps doing the visual work.
  5. Two finishing touches on the uploader: a 150px square image rounds into a circle with a corner roundness of 75, and ticking “limit the size before uploading” shrinks files before they hit your storage.
  6. Check every input’s content type. Ours were still set to email from the copied group, so validation rejected perfectly good names.

The company step wants a description, a team size and a set of industries.

  1. Copy the profile form group, paste it into Group Forms, move the visibility conditional across, and strip it back to one field. Replace the input’s element type with a Multiline Input called about.
  2. Team size is a single choice, so use radio buttons (radio buttons are exclusive; checkboxes allow multiple). Rather than hard-coding “1-10, 11-100” as static choices, create an option set Team Size OS with options 1-10, 11-50 and 51-100, and use dynamic choices: all Team Size OS, displaying the current option’s display. Storing the answer categorically beats storing free text.
  3. Industries allow multiple selections, and Bubble has no native checkbox-list element, so build one: create an option set Industry OS (finance, healthcare, information technology, real estate), then add a Repeating Group with type of content Industry OS and data source All Industry OS, rows about 48px high, containing a single checkbox whose label is Current cell's Industry OS's display.

Create the profile once, at sign-up

Give the Profile data type its fields: profile picture (image), name (text), phone (text), location (geographic address), description (text), team size (Team Size OS), industries (a list of Industry OS, because checkboxes mean multiple values), and an explicit owner field of type User. Don’t rely on Bubble’s built-in Creator: in plenty of apps an admin creates profiles for other people, and then the creator is not the owner. Add the reverse link too, a profile field of type Profile on the User, so expressions can travel both directions: Current User's Profile one way, the profile’s owner the other. A field whose type is another data type is simply Bubble’s way of writing a relationship; a database person would call it profile ID.

Now, when to create the row? You could check on page load: if Current User's Profile is empty, create one. It works, but that check runs on every single load, and every debug session makes you step through it. It’s noise you’ll be paying for later. The cleaner answer is to create the profile exactly once, at sign-up.

  1. In the sign-up workflow, after Sign the user up, add Create a new thing (Profile) with owner = Current User, then Make changes to Current User setting profile = Result of step 2. User and profile now reference each other permanently.
  2. Wrap those two steps in a custom event called initialise profile, and trigger it from the sign-up workflow. The custom event gives the steps a name and a home, and you can attach a note: “profile creation must only run once, when the user is created; one user to one profile.” That’s not overkill. We’ve seen a project where a later developer assigned data to users outside the once-only flow, and privacy rules built on those assumptions ended up exposing data.
  3. Test it and check the database. Our first attempt saved nothing at all, and the reason is worth internalising: the existing test users were created before this workflow existed, so no profile rows had ever been made. Delete stale test users, sign up fresh, and confirm in App Data that a new User and an empty Profile appear, each pointing at the other.

Save each step when Next is clicked

With the row guaranteed to exist, saving is a Make changes to a thing on the Next button, targeting Current User's Profile: profile picture = Picture Uploader's value, name = Input Name's value, phone = Input Phone's value, location = SearchBox Location's value. Each expression’s type lines up with its field: image, text, text, geographic address.

One refinement: this action would fire on every step, not just the profile step. Add an only-when condition, All Onboarding Step OS:filtered (sort by = Get page from URL (page, as number)):first item's display is profile. Filter the option set down to the step in the URL, take the first item to turn the list into a single option, and compare its name. The net effect is “only run on page two”, but written dynamically, so reordering steps later doesn’t break it.

Duplicate the action for the company step (condition: display is company) and map description and team size. Then you hit the interesting one: industries.

Checkbox lists need a custom state

A list field can’t be set with a simple equals; Bubble offers add, remove, set list, and clear. To use set list you need a list to set from, and a pile of independent checkboxes doesn’t give you one. The answer is a custom state.

  1. On the repeating group, create a custom state Selected Industries, type Industry OS, ticked as a list. It starts empty on every page load.
  2. Checkboxes can’t have workflows added from the design tab, so go to the workflow editor and add an An input’s value is changed event for the checkbox. The action: set the state to Repeating Group Industry's Selected Industries plus item Current cell's Industry OS.
  3. Ticking works; unticking does nothing yet. Create a second, opposite event using minus item. Critically, put the conditions on the events, not the actions: “when Current cell’s Industry OS is not in Selected Industries” on the add event, “when it is in” on the remove event. Two conditional actions inside one event can end up referencing and cancelling each other; two mutually exclusive events never collide.
  4. Back on the company step’s save action, set the industries list to Repeating Group Industry's Selected Industries. Run it, then validate in App Data that the row shows exactly what you selected.

Publish, track completion, and build the review screen

Publishing is just a status flip. Create a Profile Status OS option set with draft and published, add a status field to Profile with a default of draft, and on the review step’s button (the one labelled “Create profile”), set status to published. Every profile is born a draft and one click makes it live.

The review screen needs to know which steps are done. Add a steps completed field on Profile, a list of Onboarding Step OS, and in each step’s save action append the matching option with add item. Since the front end disables Next until required fields validate, a step in that list really was completed.

Then make the review screen build itself. On the option set, add a yes/no attribute show in review screen (yes for profile and company) and a text attribute label (“Profile details”, “Company details”). Add a repeating group of Onboarding Step OS whose source is All Onboarding Step OS:filtered with the constraint show in review screen; you don’t even need “is yes”, it’s implied. You could hand-build the list with get an option:converted to list:plus item, and it’s a useful trick to know, but the flag-driven filter means a future step joins the review screen by setting one attribute.

In each row, show the label and an icon. Default the icon to a cross, with a conditional: when Current User's Profile's steps completed contains Parent group's Onboarding Step OS, show the tick. Defaulting to the cross matters because the icon renders before the data arrives, and a tick flashing into a cross looks broken. Add a workflow so clicking a row goes to the current page with page = this step's sort by, jumping the user straight back to fix an incomplete step.

Finally, guard the button. On “Create profile”, add a conditional: when the current step is review and Current User's Profile's steps completed:count is not the count of All Onboarding Step OS:filtered (show in review screen), this element isn’t clickable. Two completed of two required and the button lives; delete one from the database and watch it grey out.

Survive a reload

Progressive saving is only half the promise; the forms must also read back. Set each input’s initial content from the same fields you save to: the uploader’s dynamic image is Current User's Profile's profile picture, name’s initial content is ...Profile's name, and likewise phone, location, description and team size. The checkboxes take one more move: set their preset status to dynamic, checked when Current cell's Industry OS is in Selected Industries, and add a Page is loaded action that sets Selected Industries to Current User's Profile's industries. While there, we renamed the field from industry to industries; single values get singular names, lists get plurals.

Finishing touches

Saving only on Next means edits made before pressing Back are lost. Create a custom event saveData, move the Make Changes actions into it, and trigger it from both Next and Back; either direction now saves. Group the page’s workflows into folders (navigation, profile, form, review) so sixty workflows don’t blur into one screen.

Completed users shouldn’t wander back through the wizard, so add a Page is loaded event: only when Current User's Profile's status is published, go to the current page with page set to All Onboarding Step OS:filtered (final screen):first item's sort by, using a new final screen attribute (set yes on the end step; unset attributes simply default to no). Then make all your page-load events mutually exclusive by adding “and status is not published” to the others. Only one event ever fires, there’s no redirect chasing another redirect, and the loading flicker disappears; not doing redundant steps is a performance strategy in itself.

One honest note: after publishing, clicking review-screen rows sometimes bounced to the final screen, a timing quirk between the freshly written status and the redirect condition. We disabled that navigation and flagged it for a return visit; real projects involve that call more often than anyone admits.

This skill in the AI era

Everything here is really about state and lifecycle: create once, write at defined moments, guard the transitions, survive interruption. Those are exactly the decisions an AI coding agent cannot make for you, because they’re product decisions wearing technical clothes. When we build applications with AI, the spec we hand Claude Code reads like this article’s checklist, and the code that comes back is only as good as that thinking. The Bubble-era habits of validating in the database and naming things properly transferred wholesale; here’s the full story of that transition.

If this sparked something, let's talk.

No pitch, no pressure — just a conversation about what you're working on.

Let's talk
Share: