A Test Drive of Bubble.is: A Visual Programming Tool

I heard about a visual programming tool named Bubble on Product Hunt. I decided to give it a try by building an approval workflow app.


A Bubble schema is built from things — basically tables. A thing has built-in fields Creator, Created Date, Modified Date. The scalar field types are text, number, numeric range, data, date range, date interval, boolean, file, image, geo address, and thing (there’s no explicit relationship notion). A scalar type can have a default value. If a default value is added, the field is not reset on existing rows. List is the only collection type. Encryption at rest isn’t supported.

The App Data tab has CRUD, views, import, export, transformation, copy dev (production) to production (dev), and PIT recovery commands. Built-in fields are not displayed — a view must be defined to show them. While defining a view, the Creator field is shown as Created By.

The Privacy tab allows thing-specific access control entries termed roles. I don’t see a distinction between read & write, or owner & not. Surely, I’m confused because it must be necessary to express that only the owner & a row-specific white list (decision makers in this app) can write.

I defined a DecisionStatus thing with 3 rows: undecided, approved, rejected. I set my Decision thing’s status field’s type to DecisionStatus. However, I couldn’t figure out how to make the UI set it. So I switched to a text field.

There’s a built-in User thing with fields email, Created, Modified, id — password is hidden. There doesn’t appear to be a field Confirm Email. I don’t see any security info such as (IP, time) history. I don’t know if there’s login throttling.

I want to store the allowed decision makers. This could be a list of Users or emails. I could not determine how to populate and enforce this list.

Judging from HTTP traces, it appears to use firebase and elasticsearch.


Most of the programming time is spent designing the UI. The widgets are:

  • visual elements: text, button, icon, link, image, shape (?), alert, video, HTML, geographic map, powered by Bubble.
  • form elements: input, multiline input, checkbox, dropdown, search box, radio buttons, slider, date/time picker, picture uploader, file uploader. The lack of a multi-select, auto-complete element is a showstopper for me.
  • containers: group, repeating group (data bound grid), popup (a group that is invisible initially), floating group (pinned despite scrolling), group focus (I didn’t study).
  • reusable elements: footer, header, new element. There’s a tab group but I couldn’t determine how to use it.
  • There is a plugin architecture, which I didn’t study.

A nice feature is the style selector shows a preview of the styles.

All app frameworks feature data binding so it is no surprise that Bubble containers and form elements can be bound to things (as a reminder, these are like tables). During execution I was warned that data couldn’t be changed without defining roles (see above). It would have been nice to be informed at compile time.

I chose the built-in, reusable element Signup/Login Popup. The UI is inconvenient because the default is Signup despite Login being a more frequent event.

There is a way to perform searches.

The page becomes cluttered with groups playing the role of modal forms. There is no way to auto-center a group when shown so there’s no choice but to pile them on top of each other in the designer. I recommend a way to stash invisible groups somewhere — similar to Bubble workflow folders perhaps.

I created these groups: decision grid (repeating group), create decision form, edit/delete decision form, search form (binds the decision grid).

With many elements, things, fields, and modifiers, menus get too long.

Localization is unsupported.

There’s a ResetContent action that clears input elements but it isn’t clear what the scope is. I defined a custom state whose value is the empty string. I create an action to set an input element’s state to it but it has no effect.


Programming is expressed through events in the workflow tab of the designer. Elements have the familiar event types like onClick and Page.onLoad. An interesting event type is reactive (Bubble doesn’t use this term), eg, when a radio group’s value is X. Another event type is a recurrence.

Each event has an action sequence such as 1) show group 2) bind data 3) set focus (this action isn’t reliable for me).

When a decision is approved or rejected, the decision’s date is set to today. When the radio group’s value is not undecided, the date is shown — otherwise the date is hidden. This requires defining two events. It would be nice if an event had an else branch.

I could not determine how to do a case-insensitive query. There are modifiers to shift an input element’s value to lower case (:lower) and to trim (:trim) but not to shift a field. The contains query seems to demand equality.

There is no textual language — not even JSON. Expressions are built by clicking. Events have conditions expressed by boolean expressions like X is clicked (in contrast to ev.type == “click” && ev.target == X) or Current User is logged in (in contrast to curUser.isLoggedIn()). Data bindings are paths like Current cell’s Decision’s Title. As a programmer, I find this harder to read than currentCell.decision.title. To avoid clutter, events can be grouped in workflow folders.

To create a formula, one must start with an operand defined in the datastore. I created a field named One whose default value is 1. Parentheses aren’t allowed — the workaround is multiple actions such as 1) hiddenElement = 1+x 2) f = hiddenElement^y.


The cookie doesn’t appear to be signed or encrypted (possibly allowing session hijacking). Sessions appear to be used — not the best choice for scale.

Cookie:decisionsdecisions_test_u2undefined=1475989593701x882298631127923700; decisionsdecisions_test_u2undefined.sig=lJ7r494Z5YboyA0KOQ1907hiEc4; decisionsdecisions_u1_testundefined=1475989593693x585895211668685000; decisionsdecisions-firebase=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NzY2NTUyMTYuODA1LCJ2IjowLCJkIjp7ImFwcF9pZCI6ImRlY2lzaW9uc2RlY2lzaW9ucyIsInVzZXJfaWQiOiIxNDc1OTg5NTkzNjkzeDU4NTg5NTIxMTY2ODY4NTAwMCIsInVpZCI6IjE0NzU5ODk1OTM2OTN4NTg1ODk1MjExNjY4Njg1MDAwIiwiYnViYmxlX3RlYW0iOmZhbHNlfSwiaWF0IjoxNDc2NjM3MjE2fQ.M2s4P5nW_eumcu0nYMYDfAyfFZLRxwnd6dcsaWozOz0; decisionsdecisions-firebase_workflow=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NzY2NTUyMTYuODA1LCJ2IjowLCJkIjp7ImFwcF9pZCI6ImRlY2lzaW9uc2RlY2lzaW9ucyIsInVzZXJfaWQiOiIxNDc1OTg5NTkzNjkzeDU4NTg5NTIxMTY2ODY4NTAwMCIsInVpZCI6IjE0NzU5ODk1OTM2OTN4NTg1ODk1MjExNjY4Njg1MDAwIiwiYnViYmxlX3RlYW0iOmZhbHNlfSwiaWF0IjoxNDc2NjM3MjE2fQ.uZnerzf8UFbHyTvUzJ1_EYANxX4B5xDbcXfBJVq-wfs; decisionsdecisions_test_page_session=1476637217715x4

POSTs with JSON request & response payloads are used. The client sends a heartbeat https://appName.bubbleapps.io/version/user/hi

Thing updates seem to be sent in the modify operation as in this update of Decision.Title:

POST https://decisionsdecisions.bubbleapps.io/version-test/elasticsearch/modify

Thing create/delete seems to be sent as workflow executions.



Happily, there is a debugger and an element tree (like the DOM) inspector. These only show the Creator’s user id — email would be helpful. The tool has video help and a manual (too terse & several omissions such as modifiers like :filtered). The tours step through using the real UI. Udemy has a free course and a paid one.

Closing Remarks

In summary, Bubble’s mission is noble and they have worked hard on this tool. Unfortunately, I could not program a simple approval workflow app. I posted a draft of this review on the Bubble forums. I was scolded for not trying hard enough to learn this tool. One poster offered to assist. One of the founders, Josh, was welcoming. Maybe I’ll take the Udemy course.