Multi-tenant · multi-vertical · India

One system.
Many organisations.
Hard walls.

Orchestrix runs schools and property firms on a single deployment — and the thing that makes that safe is not a permission check in a screen. It is a policy in the database that every query, from every client, has to pass.

one deployment · 24 organisations · no query crosses a wall

45modules shipped
2verticals live
1deployment
0cross-tenant reads

The problem

Software tends to fail quietly.

Not with an error. With a payroll run that used a month of unmarked attendance. With a flat sold twice because two people pressed the button in the same second. With one school’s report card rendering another school’s name.

None of those raise an error. They tend to surface weeks later, usually in front of somebody who matters.

So we try to make those states simply not possible — caught by a constraint, a lock or a policy, rather than relying on everyone to remember.

What we run

Different industries.
The same spine.

Each vertical has its own database, its own vocabulary and its own screens. What they share is the part that must never be wrong.

SCHlive

Schools

K-12 schools running admissions through to alumni on one system.

The whole institution — the classroom, the office, the bus and the hostel — with a parent portal that is a different product on the same data.

28 modules

Explore
RLTlive

Real estate

Developers, plotted-land firms and interior studios.

Enquiry to registration: the pipeline, the inventory, the price, the booking, the schedule and the money that arrives against it.

17 modules

Explore
HSPplanned

Hospitals

Clinics and small hospitals.

The third vertical. Same spine, same isolation model, a different operational vocabulary.

Still in design

Tenant isolation

The boundary is the product.

Every table carries the organisation it belongs to. Every policy keys on the organisation the caller is in. Row-level security is switched on automatically when a table is created, so a table nobody wrote policies for is invisible to everyone rather than open to anyone.

When a row references two other rows, both must belong to the same organisation — enforced by a composite key, not by the code that inserts it.

-- a booking cannot reference another firm’s unit create table booking ( id uuid, org_id uuid not null, unit_id uuid not null, -- both columns, one key foreign key (unit_id, org_id) references unit (id, org_id) ); -- and the row is only visible to its own tenant create policy booking_read on booking for select using (org_id = current_org_id());

One deployment, many organisations

Every table carries an organisation. Every policy keys on it.

No cross-tenant reference is possible

Composite keys make the wrong row a constraint violation.

Vendor access is recorded

Entering a customer's data is logged, and the session expires.

No biometric template ever leaves a device

A punch is an enrolment number and a time. A fingerprint cannot be reissued.

Pickup codes are unreadable

The code that collects a child has no select policy at all.

Deletion is archival first

Nothing is destroyed in one step, and the second step is narrow.

How we build

Six principles we build to.

Each of these came out of something we learned the slower way, and each is now enforced by the system rather than by memory.

01

The database is the boundary

Row-level security, not application checks

Every query runs in the browser under the signed-in user's own token. There is no server-side API layer to trust, and that is deliberate: the only thing standing between one organisation's data and another's is a Postgres policy, which is the same code path for every client, every screen and every mistake.

A permission check written into a screen protects that screen. The next one has to remember.

02

Two references, one tenant

Composite foreign keys, not two independent ones

When a row points at two other rows, both must belong to the same organisation — enforced by a composite key on (id, org_id), not by the code that inserts it. Independent foreign keys permit a booking that references one firm's unit and another firm's customer.

It is better for the database to reject a cross-tenant row than for every future feature to remember not to write one.

03

Anything that races is a function

SECURITY DEFINER for contention and authority

Two salespeople holding the same flat, two librarians issuing the same copy, a fine being waived, a child being handed to an adult — none of these are client-side writes. They are database functions that take the lock, check the rule and record who used them.

A check and then a write, from a browser, leaves a gap in between — and that gap is where two people get the same flat.

04

Features do not depend on memory

Enforced in the schema, not in a screen

If one module's correctness depends on another writing a column, a trigger does it. Student class is mirrored between two tables in both directions, so the students screen and the promotion wizard each work without knowing the other exists.

A contract that lives only in someone's head is easy to lose track of as the codebase grows.

05

Verified against the running database

Not against what the code implies

Reading an application's types tells you what it uses, never what the database actually has. Migrations written from inference have produced wrong helper names and indexes that were quietly missed.

A migration written from assumption can report success while quietly leaving the problem in place.

06

Frozen means frozen

A price agreed is a price recorded

A booking copies its unit price, its charges and its discount at the moment it is made. Repricing the tower next month does not reach backwards. The figures a buyer signed are stored, not derived.

A total that recalculates will eventually disagree with a document somebody has already signed.

Read the engineering notes

Technology

Chosen, not accumulated.

Every layer below is here for a stated reason. Anything that could not justify itself is not in the list.

LayerChoiceWhy this one
ApplicationNext.js · React · TypeScriptClient-rendered throughout. There is no server tier to compromise because there is no server tier.
DataPostgreSQL via SupabasePolicies, triggers, constraints and functions carry the rules. One database per vertical — separate projects, no shared rows.
AuthorisationRow-level securityEnabled by an event trigger on table creation, so a table without policies is invisible rather than open.
Server workDeno edge functionsOnly where a service-role key is unavoidable: creating logins, signing uploads, ingesting from devices. Each re-derives the caller from their token before acting.
FilesCloudflare R2Private bucket behind short-lived signed URLs for records; a separate public bucket for anything meant to be seen.
PaymentsPluggable adaptersRazorpay, Cashfree, PayU, Stripe. Provider-agnostic on the application side; per-tenant secrets encrypted at rest.
MobilePWA · CapacitorInstallable on any phone, and a native Android build from the same static export.

Scope

45 modules, all in production.

Everything listed is in production use. If something is still being built, you will not find it here yet.

Schools

28 modules
AdmissionsStudentsStaffAcademicsAttendanceStaff attendanceFeesAccountingExamsTimetableHomeworkHR & payrollLibraryHostelTransportBus trackingBus trip registerBiometric devicesFront deskNoticesEventsCalendarGalleryMessagesAt-riskAudit logRoles & permissionsParent portal

Real estate

17 modules
Leads & pipelineQuotationsCustomersProjectsPhasesInventoryRate bandsChargesBookingsPayment plansDemandsReceiptsCollectionsCatalogueRate cardsWebsitesEnquiry capture

The hard parts

The parts that took longest.

Feature lists are easy. These are the two problems that took the most care to get right.

SCH

Payroll that refuses to guess

Leave accrues on an academic year, April to March, pro-rated for mid-year joiners, and never for a period before the school was onboarded. Payroll blocks on unmarked attendance inside the employment period. Neither rule is a preference. Both are there because a salary is a number someone has to stand behind when a teacher asks about it.

RLT

Money that adds up

A receipt does not pay a demand — it is allocated against demands, in amounts, as rows. That one shape handles a part payment, a single cheque across three demands, and an overpayment with nowhere to go, without a special case for any of them. TDS is carried separately, so that above the threshold the buyer is credited in full even though the developer banked one percent less.

Tell us what you are working with.

We would rather understand how your organisation actually runs than walk you through a feature list. Start wherever it hurts.

Start a conversation hello@orchestrix.in