What are the best practices for structuring workflows in Bubble to ensure maintainability and performance?

How do you design your workflows in Bubble so they are organized, efficient, and easy to manage?

1 Like

Reusables wherever possible

We have a rule at our agency to build applications where every reusable or page does not have more than 20 workflow events (edge cases exist, but this is what we follow after building more than 100 Bubble applications that are maintainable and have gone on to scale to millions in users and $).

We don’t use colour or folders for that matter (the latter only comes into play when >20 workflow events on a single page is inevitable)

2 Likes

You might appreciate this template which is designed to be an example of how to build a maintainable, easy to understand app: Better-boilerplate | Bubble Editor

For structuring workflows, the concept of reusables everywhere is not necessarily sage advice. Overuse of reusables is a significant issue it seems and a lot of developers are making mistakes by insisting on using reusable elements in a way that doesn’t make for a maintainable app. Having five different reusables, such as one for popup, one for button, one for tab navigation, one for repeating group display etc. when they are all being brought together into one reusable for the purposes of controlling one data type is bad practice and makes it harder to maintain and expand an application.

But, specific to just workflows, use folders. Use naming conventions and folders that makes sense and allows you to to take advantage of quick type features in bubble to find things quickly and easily. For example, I have a folder for ‘delete thing’ and it has actions linked to the press of delete icon, the cancel and close of the popup and the actual delete button from popup to delete the actual thing as well as the popup is closed event to reset the popup. I have another folder for ‘create thing’ etc. This helps me quick type or scan quickly to see the folder and events of concern.

When setting up Custom Events, now, after the recent ‘upgrades’ to the workflow tab, it had become insanely difficult to squint at small icons to determine what are custom events and what are not, so I now, add to the name of all Custom Events the words Custom Event, so it is a custom event with name of ‘Custom Event - Close Popup’…this allows me to clearly see what are Custom Events, and to quick type and find them.

I personally do not use colors as a sea of color does nothing other than confuse me.

Just as an avoidance of reusable element overuse, I try to avoid as much as possible lots of chained custom events. Sometimes you can not really get away from it, but I try to avoid them as it causes me to have to click to often to find the exact action I’m looking to modify.

I also sometimes will add the name of the reusable element or at least the data type to help when debugging because the debugger doesn’t always show us where the action or event running is from (such as which reusable or page).

Always remember, the app search tool is your friend. Depend on it. Learn to Love it.

Basically, folders, good naming conventions, ovoid over use of reusables and chained custom events, and learn where and how to quick type to find quickly and how to search app wide using app search tool…other than that, there really is not much to it.

4 Likes

People literally pay us to come in and do this on their app that has become unmaintainable because it makes it cleaner, easier to edit, and less buggy.

If it were just self-indulgent refactoring for the sake of vanity and didn’t actually deliver results for their end users and development speed (the things that actually matter to these businesses), we really wouldn’t have any clients because they’d just be throwing money into a pit.

When I say that using reusable elements and custom events well helps your app, it isn’t just being pulled out my ass, it’s based on our experience of working with plenty of apps that thought they needed to migrate off of Bubble, that we’ve been able to turn around by implementing these.

So, you can certainly decide to limit your own use of them, - but I’m speaking from experience, as it is my team that ends up as the one brought into these apps to clean them up when the app’s size has made the app’s build practices untenable, haha.

Although my friends above have already contributed a lot, here’s my small addition:

Before you start building, stop and think.

Using reusable elements, custom events, and well-organized API workflows creates a highly scalable structure — especially when you can reuse the same workflow in multiple places.

For example, let’s say you have a simple popup whose purpose is to delete an item. Instead of having multiple popups across your app that delete different things, build one reusable popup and use it everywhere. The dynamic texts can be populated externally through the element’s properties. As for the main function — when the user clicks “Delete” — you can have a single API workflow that handles every type of deletion. Just set up the necessary parameters and conditions so it knows what to delete in each case.

The idea is to have just one element with the same style and structure that you can reuse throughout your entire platform.

That’s just one example, but it illustrates a mindset: using reusable components whenever possible is incredibly powerful. The same goes for API workflows — imagine you have the same CRUD operation used in several parts of your app. If you repeat that logic everywhere, you’ll end up duplicating code. But with an API workflow, you just call it whenever you need it.

For front-end-specific logic, reusables also give you freedom — you can work both inside and outside them. You have access to their properties, custom states, and custom events, which can all be triggered externally.

Additionally, with plugins like Toolbox, you can interact across different reusables using JavaScript actions. And through JavaScript to Bubble, you can send events back with any data or type you want.

So, the secret is: before jumping in to build, pause and think — can I make this a reusable? Can I move this to an API workflow? Where else might I reuse this later?

That’s how you create a structure that’s reusable, scalable, and performant.

For organization, always keep your folders, naming conventions, and comments clear — especially if other people will work on the project. Make use of folders and don’t underestimate comments. Many people forget to use them, but imagine opening a workflow months later — a short comment there can save a lot of confusion.

Finally, as @georgecollier mentioned, I highly recommend checking out :backhand_index_pointing_right: Better-boilerplate | Bubble Editor It’s full of great examples and best practices for building clean, scalable, and high-performance structures.

2 Likes

So people come to you and say, I have an app that has one single data set controlled by one single reusable element. I want you to break it out into different components, do not make them function for more than just the one data type though, so that we have a bunch of disconnected reusable elements that all work on the same data type and no others? Or did you not read the part of the quote that says ‘purposes of controlling one data type’?

I mean if a developer is charging their clients per hour, I can see how a client would benefit from the developer ‘refactoring’ for development speed, since the client pays less once ‘refactoring’ is complete. But if the developer charges a subscription fee, surely such ‘refactoring’ for development speed is a benefit to the developer earning a higher effective hourly rate?

That is the KEY, used in multiple places

Yes, we can do that too for the Create and the Modify. My plugin Data Jedi has these built in for purpose.

With that setup, I have one single reusable that is my Global Data Center and it controls every single one of my data types in the app and allows me to perform all CRUD operations on them from anywhere in the app and make bulk create/relate with that Step 4 to return IDs. Makes for a very, very easily maintained and expanded app.

I might have a different interpretation of ‘whenever possible’ as I would say it is possible to use a reusable all the time. The idea is to use them whenever practical, such as when they handle the deletion of every data type and not just one. So must be that using reusable components whenever it is practical so as to be used in multiple places to control multiple things, not just in one place to control one thing unless the control of one thing requires lots of workflows and elements so then it is wise to just build the entire feature set for that one thing as a reusable (I’ve been teaching that since 2020 to avoid editor crash).

You could do that. I use my plugin as in the example image above. This same type of thing could be done with Toolbox plugin and some javascript as well.

Bonus points, saving 0.1 WU on each ‘schedule’ of backend workflow when all you really want is to trigger a backend workflow, use the API connector. Makes the process of setting up parameters on those backend workflows a bit faster too. Data Jedi has this built in as well, for purposes of not only saving 0.1 WU but also for benefits of utilizing backend workflows wherever and whenever it is needed.

Properties can not be triggered externally, other than by linking them to a URL parameter or custom state, although we can set them externally, but the setting is ‘static’ in nature. I’ve seen a lot of confusion on the forum still about how to use properties, custom states and custom events of reusable elements appropriately.

This is a hugely powerful concept. Learning javascript to complete it can be tedious, or can use a plugin like Data Jedi to do it in a NoCode way.

I would change this. Question 1: Where else might I reuse this later. Question 2: would moving this series of actions to a backend workflow improve my user experience by offloading processing to server so the user can continue to navigate the app as data is processed.

The question of can I make this a reusable never needs to be asked because it is always known that everything can be made into a reusable.

Excellent advice. The comments are huge, and the full set of comments feature is great as clicking onto a comment will bring you to the element or workflow the comment is on.

I disagree. It has some good things. Doesn’t exemplify a global data center for the delete anything. I believe it has separated components as reusables that should be one reusable together.

From that app, the reusables of ViewAccounts and TableAccounts, first should be renamed to View Accounts and Table Accounts, but more importantly should be combined into a single reusable. No reason whatsoever for TableAccount to not live in the same reusable as the ViewAccounts does. The ViewAccount reusable has zero workflows on it, and has the TableAccount reusable on it plus only text as a title and group as container.

@georgecollier Please explain exactly which purposes this serves? Why did you not just put the TableAccount as regular elements inside of ViewAccount since ViewAccount only has a title and a container element. It seems all of those Table Reusables should have been in the View reusables so that the developer doesn’t need to click around from the View to find the Table, since on the Dashboard it is just the View resuable anyway.

That is kind of my point when I talk about overuse of reusables. For me, unless there is a valid reason not evident by my review of that boilerplate, for the table to be a separate reusable from the view (ie: title and container) than it seems like an overuse of reusables and will slow a developer down when trying to make changes, or even to repurpose them.

From my experience, repurposing a single reusable that has all CRUD operations for a single data type on it is easier and faster than having to repurpose two reusables, one for the view and one just to change the title.

Furthermore, if I were to use the same concepts as applied by @georgecollier the Dashboard page would have all ‘tables’ and one single view and one single pagination reusable. There is no need to have the pagination reusable on each table since all the pagination reusable needs is numbers, and the ‘view’ is just a title and container, both of which can be tied into the URL parameter that denotes which data type is being viewed.

I also have no idea what purpose at all the property of Current View on the Global reusable even serves since it’s property value is used nowhere on the reusable to begin with.

2 Likes

For example, that TableInvoices can be used in both the admin’s ManageAccount view (to show Invoices filtered for this account) and also the user’s invoices tab to show the users invoices.

:face_with_raised_eyebrow: Pagination properties are a property of the table you’re looking at. If you have one pagination reusable for all tables, how are you gonna have two tables shown at once?!

An easier way to access the current page from the URL as an option value. More useful when using page routes for navigation as it could give you a list of the current path which lets you stop hard coding magic values as texts.

It’s used outside the reusable, not inside. I recommend trying the use of global reusable elements as reusable expressions and data sources.

The main thing is not mentioned here; maybe I missed that part. It is Database design, because it impacts every aspect of ur app. If it is not well designed, u will end up with a lot of static FE elements and BE workflows, plus high computation.

Once I saw how people develop FE/BE in traditional apps, I completely changed my approach on how I do that in Bubble. I’m lucky enough to have mentors with a decade of experience, and we’re combining now No Code + Code.

One more time, we always start from designing data flow based on business logic, then we lay down a blueprint for FE/BE solutions with well-documented trade-offs analysis (sometimes it’s just notes, quick conversations when there are short timelines )

3 Likes

That, I’m sure we can all agree on!

Yes, that is fine. My point was you do not need and should not have a second reusable of View that simply has a text as header, the container group to hold the Table reusable.

That is correct. My mention of this was based on how your example is setup. I didn’t see any views in the dashboard that showed more than one table at any one time. If you are doing that, then yes, keep the reusable paginator onto the Table reusable…but do not have a second reusable to just put a title over the Table.

Okay, that makes sense. I just didn’t see anything in the example editor that made use of it in that way.

Yes, that is something interesting, to use a Global Data Center for data sources.

How do you use them for expressions? Is there a way to make dynamic expressions dynamic? Or are you just getting the evaluated result of the dynamic expression so as to feed in all the values for the dynamic expression to use and evaluate to then expose the result as a property on the reusable element?

You can set a default optional property in the reusable element. The default expression should be the expression you want to be accessible from anywhere. Place the reusable element where you want it, and access the data from the property. Bubble caches identical searches so it’ll only fetch the identical searches once.

4 Likes

Custom workflows with actions that only use the workflow’s parameters, and no reference to page variables. Then you can copy/paste custom workflows to other pages, reusables, and back-end, with no errors.

1 Like