08 · HR Tech
HR Management Portal: a multi-tenant Bubble.io SaaS for an agency client.
A multi-tenant HR portal on Bubble.io with a 120k-plus-row reporting dataset, built at Infex Biztech as a junior developer on Dixit Patel's team. Hierarchy-based: every survey, every response, every role permission, and every reporting query had to be tenant-scoped at the database layer. Most of the engineering lives in the schema and the backend workflows underneath the UI.
A multi-tenant HR SaaS, built where the engineering lives
The HR Management Portal is a hierarchy-based multi-tenant SaaS for HR teams. An admin composes the surveys (dropdowns, free text, numeric ranges, suggested responses). A separate employee fills them in through a guided one-question-at-a-time wizard. The responses pour into a reporting surface that has to stay usable when a single tenant’s response table climbs past 120,000 rows. Every survey, every response, every role permission, and every reporting query is tenant-scoped at the database layer so a user signing in to one client’s instance never sees another client’s data.
I worked on the Bubble.io build as a junior developer at Infex Biztech, on the team led by Dixit Patel. The project came to us as an agency engagement: build the HR Management Portal as a multi-tenant SaaS the client could roll out to their organisations. The work was database-heavy and backend-workflow-heavy from week one. Most of the architectural decisions that mattered were never visible on screen.
What got scoped to me
The product had several distinct surfaces, and the work split across the team. The parts I owned or co-owned in the Bubble.io build:
- Authentication and user account management. Sign-in flow scoped per tenant organisation so a user signing in to one client's instance never sees another client's data. Account fields, profile maintenance, password recovery, and the role hooks the rest of the product reads from.
- Data tables with role-based permissions. Tenant-scoped tables for surveys, questions, and responses, with row-level Privacy Rules in Bubble.io so the read access rules ran at the database layer, not just the UI. Conditional Visibility on the cards and pages handled the client-side state, but the database was the source of truth.
- Multi-organisation access management. A user may belong to one tenant or several; a tenant has its own administrator, its own survey library, its own reporting view. The data model under that was the part that decided whether the rest of the product felt fast or felt brittle.
- Reporting with filters and custom data display. Tables that read the tenant-scoped response store and stay performant as the row count climbs. The 120k-row dataset was the stress test that exposed every lazy query I had written and forced me to learn how to read Bubble's database performance honestly.
- Usage monitoring and customisation of common data. An admin view onto activity across the tenant, plus a customisation layer so a tenant could tune the question library, the suggested responses, and the role matrix to match their own HR policy without a developer involved.
The marketing site, the brand, and the original UI direction predated my time on the project. The work named in this case study is the multi-tenant engineering pass that made all of the above hold together at the same time.
The multi-tenant data model came first, before any UI.
The cheapest mistake on a multi-tenant Bubble.io build is to start with the UI and bolt the tenancy on later. Every read query, every workflow, every Privacy Rule downstream is shaped by the schema you commit to in week one. Get it right and the rest is straightforward. Get it wrong and you will be running a quiet rewrite for the rest of the engagement.
We spent the first weeks on the schema alone. A tenant has organisations. An organisation has users. A user has roles, scoped per organisation. A survey belongs to a tenant. A survey has parts, parts have sections, sections have questions. A question has a type, and a type carries its own option set or range bounds. A response belongs to a respondent, to a question, and to a specific version of the survey so historical answers do not silently shift meaning when the admin edits the question text six months later.
Once the schema held together on paper, every other decision got cheaper. The backend workflows had one shape to read from. The Privacy Rules had one set of relations to check against. The reporting queries had one place to add a server-side constraint.
A survey detail page. Parts and sections on the left; a per-question Risk panel on the right that lets the admin flag questions as the structure is composed.
Question types as data, not code.
The customisable question modal was the piece that made the product feel like a platform instead of a hardcoded form. An admin opens the modal, picks the question type, and configures it inline. A dropdown gets its options typed in. A numeric question gets its lower and upper bound. A free-text question gets its placeholder. The configuration is saved as fields on the question record itself; the fill-survey wizard reads those fields at render time and produces the right input.
This is a small architectural choice with a large consequence on a multi-tenant product. If question types had been hardcoded in Bubble.io workflows, every new type would have meant a deploy that affected every tenant. By keeping the type definition as data, the product team could add question shapes by editing rows, and the user-facing wizard never had to change.
The customisable question composer. Type chosen on the left, configuration on the right; the saved record drives what the end user sees inside the wizard.
System Questions, with the Suggested Response modal open. Admin-authored response language that the wizard can offer as a default, with the user free to override.
Backend workflows: where multi-tenant actually lives.
The backend API workflow layer is where multi-tenant gets enforced or quietly violated on a Bubble.io product. Every workflow that writes a row has to write the right tenant ID. Every workflow that reads a list has to filter by tenant before doing anything else. Every workflow triggered by an external event has to validate the tenant context before honouring the request.
The change that mattered most was wiring Privacy Rules at the data type level rather than relying on UI-side filters. A tenant boundary that lives in the database holds whether the request comes from the UI, the API Connector, or a scheduled workflow. A tenant boundary that lives only in a Conditional Visibility rule does not. The combination of Privacy Rules at the data layer plus tenant-aware backend workflows is what made the multi-tenant model actually multi-tenant rather than just looking that way.
The fill-survey wizard. One question per screen, progress visible, partial state preserved across sessions, idempotent writes so a flaky network does not double-record an answer.
Reporting at 120,000+ rows on Bubble.io.
The reporting surface was the part of the product that taught me Bubble.io’s performance characteristics the honest way. A single tenant’s response table grew past 120,000 rows during the engagement, and the first version of the reporting table loaded as if every cell were being computed from scratch on every render. It mostly was.
The fixes were the usual suspects, learned the hard way: server-side constraints instead of client-side filters, paginated repeating groups over “all responses” lists, denormalised lookup fields on response rows so a report did not have to walk three relations to render a column, and the Bubble search-index feature applied where it actually helped instead of everywhere by reflex.
What I would not have appreciated as a junior is how much of the work on a multi-tenant SaaS lives below the visible UI. The reporting tables look the same before and after the optimisation pass. They just stop locking up for the customer with the biggest response table.
The reporting surface. Tens of thousands of response rows behind a table that has to stay usable for an HR lead skimming for patterns.
The user and role management surface. A multi-tenant product is essentially a permissions product, and this is where the matrix lives.
The user profile and auth surface. Sign-in once, see only what your role and tenant permit across surveys, responses, and reporting.
The backend workflows. Most of what makes a multi-tenant Bubble.io app feel reliable lives in this canvas, not in the visible UI.
The Bubble.io data tab, with the 120k-row response table visible. The reporting performance work is mostly a reaction to what this view looks like in production.
What the project taught me
This is the project where several patterns I now use by default on multi-tenant Bubble.io builds got their first real workout. The data model came first because it had to: a multi-tenant survey engine with versioned responses has nowhere to hide a sloppy schema. The customisable question composer taught me to prefer configuration over deployment whenever a product team needs to extend a shape. Privacy Rules at the data type level taught me that tenant isolation only counts if it lives at the database. And the reporting surface at 120k rows taught me Bubble.io’s performance characteristics in the only way that sticks, which is by hitting them.
Infex Biztech put me on the build, Dixit Patel led the engineering carefully enough that the parts I broke got fixed before they shipped, and the agency client got a multi-tenant HR Management Portal running on Bubble.io that absorbed real customer load.
What changed
The product shipped as a multi-tenant HR Management Portal on Bubble.io, with the schema, the backend workflows, the Privacy Rules, and the reporting surface all holding together under real customer load. The pattern of doing the work this way, schema first and tenant isolation at the database layer, is the part I would carry into any multi-tenant Bubble.io build after this one.