PROJECTS / #PROJ-004 / BIZPH
LIVE · PRE-LAUNCH·SAAS PLATFORM·2026 — PRESENT

Building a Multi-Tenant Operations Suite for Philippine Small Businesses

Twelve modular products under one workspace and one login: peso invoicing with VAT, Asia/Manila time, and a private workspace per organization. Built solo with AI assistance.

PLATFORM
Multi-tenant SaaS (web)
STACK
Next.js, GraphQL, PostgreSQL, Supabase
ROLE
Solo developer · AI-assisted
URL
STATUS
Live since 2026-09-20 · Pre-launch, no paying customers yet
REPO
Private
manila-coffee.bizph.app/dashboard
SAMPLE DATA · ILLUSTRATIVE UI
BIZPH
DASHBOARD
INVOICE
CASHFLOW
CRM
HR
SCHEDULE
INVENTORY
POS
Manila Coffee Co. — Workspace
ASIA/MANILA · ₱ PHP
REVENUE (MTD)
₱248,300
INVOICES
132
STAFF ON SHIFT
14
BOOKINGS TODAY
38
CASHFLOW · LAST 12 WEEKS
RECENT INVOICES
INV-0132Dela Cruz TradingPAID
INV-0131Santos BakeryPENDING
INV-0130Reyes Auto PartsPAID
INV-0129Villanueva ClinicOVERDUE
01

The Idea.

Most small businesses in the Philippines already run on several tools that don't talk to each other: a spreadsheet for customers, a separate POS that has no idea what's in stock, a notebook for inventory, staff records in a folder, delivery addresses in a chat thread. Nobody re-enters that on purpose — it just happens, one tool at a time, until the same customer exists in four places with four slightly different spellings of their name.

Bizph starts from a specific belief: these businesses don't need another all-in-one ERP that tries to do everything and does most of it half-well. They need a handful of tools that actually work together, and the option to add one at a time as the business grows. So Bizph isn't one application with a feature list — it's built as independent products. A business can run on a single one, or combine several, and the products it does enable share the same customers, staff and records instead of each keeping a private copy.

02

The Product.

Twelve products, grouped the same way the live product groups them. Every one below exists in the application today — Procurement isn't counted separately; it ships as part of Inventory.

CUSTOMERS & SALES
CRM
Leads, customers and deals, with every follow-up in one place.
POS
Counter sales, GCash and Maya, cashier sessions and refunds.
Invoice
Quotes, invoices and collections in ₱, with VAT handled.
Cashflow
Cash on hand today, and where it is heading in 30, 60 and 90 days.
OPERATIONS & STOCK
Inventory
Stock, suppliers and purchase orders, per location — Procurement is part of this product.
Tasks
Assign work, track progress, and link it to the rest of the business.
Dispatch
Online orders, delivery runs, drivers and proof of delivery.
TEAM, BOOKINGS & DOCUMENTS
Bizph HR
Employee records, attendance, leave and payroll in one place.
Schedule
Online booking, staff calendars and a walk-in queue.
Reservations
Rooms, rates, deposits and check-in for places people stay.
Events
Plan events: guests, vendors, budget, agenda and checklist.
Files
Business documents, organized, versioned and signed.

Every product also reads and writes a small set of shared records — organizations, users, customers and files — instead of keeping its own copy. That shared layer is what turns twelve separate tools into one workspace rather than twelve unrelated apps that happen to share a login screen.

The live workspace, shown with example data — a café and resto account with Dashboard, CRM, POS, Inventory, Scheduling, Dispatch, Invoices and Cashflow enabled.
EXAMPLE COMBINATIONS
Water station, Dasmariñas
POSInventoryDispatchCashflow
Salon, Dasmariñas
SchedulePOS
Hardware store, Imus
POSInventoryCashflow
Beach resort, Nasugbu
ReservationsPOSTasks
03

One Business Workflow.

Take a water-refilling station in Dasmariñas, Cavite — one of Bizph's own example accounts — running POS, Inventory, Dispatch and Cashflow. One order moves through all four without anyone re-entering it.

1
Customer places an order
Through the online order page, or a message to the counter.
ORDER
2
Sale is recorded
Item, quantity and payment method — e.g. four 5-gallon refills, cash on delivery.
POS
3
Stock is deducted
The filled containers on hand drop by four, automatically.
INVENTORY
4
A delivery job is created
Added to the day’s run with the saved address, in one click.
DISPATCH
5
The driver receives the job
Opened from a phone browser and marked out for delivery.
DISPATCH
6
Delivery is completed
Proof of delivery is captured and the cash received is recorded.
DISPATCH · CASHFLOW

Building four screens for that isn't the hard part. The hard part is letting POS, Inventory, Dispatch and Cashflow react to one order without being wired directly to each other — so a business that only has POS and Inventory still works on its own, and Dispatch can be switched on later without anyone touching the other three. That's what the transactional outbox in the next section is for.

04

The Architecture.

Bizph is sold as separate products, not one subscription, and that business decision shapes the architecture as much as any technical requirement does:

BUSINESS_MODEL.TO_ARCHITECTURE
Products are sold independently
↓
Every product must work alone
↓
Cross-product links must be optional
↓
Products communicate through events, not direct calls
↓
Shared organizational context (customers, staff, files)

The platform treats tenant identity and authorization as server-side concerns, connects products through events rather than direct dependencies, and documents its security-sensitive decisions.

01 / SERVER-SIDE TENANCY

Tenant Resolved on the Server

The tenant is resolved server-side from the subdomain, and every tenant query is scoped by organization. The browser is never trusted for tenant identity. One login is shared across subdomains.

TENANT_RESOLUTION.FLOW
Request to {name}.bizph.app
↓
Server resolves organization from subdomain
↓
Every query scoped by organization
↓
Only that tenant's data returned
02 / DATABASE-DRIVEN AUTHZ

Permissions and Entitlements from the Database

Authorization comes from the database as permissions plus per-product entitlements, never from hardcoded role names. The frontend only hides things for UX. The server decides.

In practice: organization → enabled product → user permission. A user doesn't gain access just by existing inside an organization.

AUTHORIZATION.DECISION
Request for a product action
↓
Server checks the organization's product entitlement
↓
Server checks the user's permissions
↓
Allow or deny, decided on the server
03 / TRANSACTIONAL OUTBOX

Products Connected Through Events

Product data connects through events using a transactional outbox, processed by a pg-boss background worker. Each cross-product link is optional, so a module works alone and links up when its neighbour is present.

OUTBOX.PIPELINE
Product writes data and an outbox event together
↓
Worker (pg-boss) picks up the event
↓
Linked product reacts, if the customer has it
↓
Scheduled jobs: expiry, reminders, cleanup
04 / HARDENED UPLOADS

Logo Uploads Treated as Untrusted

Organization logo upload uses magic-byte sniffing, a server-side re-encode, a private quarantine bucket, rate limits and operator takedown. The design is recorded in an architecture decision record.

UPLOAD.PIPELINE
Rate-limited upload
↓
Magic-byte sniffing
↓
Server-side re-encode
↓
Private quarantine bucket, operator takedown available
05

The Problems I Had to Solve.

Six questions shaped most of the engineering work. Each one turned into a specific decision described below.

01_TENANCY

How do I guarantee one business can never see another’s data?

Each customer organization gets its own workspace at {name}.bizph.app. No query may ever cross that boundary.

02_AUTHORIZATION

How do permissions work when every business can buy different products?

Access depends on what an organization has bought and what a person is allowed to do, not on a fixed list of role names.

03_MODULARITY

How do twelve independent products stay independent?

CRM, Invoice, Cashflow, HR, Schedule, Tasks, Inventory, Events, Files, Dispatch, Reservations and POS are sold separately, yet they should still connect when a customer has several.

04_UNTRUSTED_INPUT

How do I safely handle a file a stranger’s browser hands me?

Features such as organization logos accept files from the browser, which cannot be trusted by default.

05_LOCALE

How do I make this genuinely useful for a Philippine business?

Peso (₱) invoicing with VAT, Asia/Manila time, and GCash/Maya as accepted payment methods are core behaviour, not settings added later.

06_SCALE_OF_CODE

How do I keep a large, multi-app codebase maintainable alone?

About 184k lines of TypeScript across 5 apps and 29 packages had to stay consistent and reviewable.

06

HOW I BUILT IT

Bizph is a pnpm and Turborepo monorepo of 5 apps and 29 shared packages, deployed across Vercel, Railway and Supabase. It was built solo with AI assistance.

SYSTEM_01

Marketing Site

The public site at bizph.app introduces the product family and the workspace model.

NEXT.JSVERCEL
SYSTEM_02

Tenant Web App

Each organization works at {name}.bizph.app. A custom "operations ledger" design system, built on Ant Design, re-themes the workspace with the organization's brand colour.

NEXT.JS 15REACT 19ANT DESIGNAPOLLO CLIENT
SYSTEM_03

Operator Admin Console

A separate internal console for the operator, used for actions such as takedowns.

NEXT.JSVERCEL
SYSTEM_04

GraphQL API

A GraphQL Yoga API over PostgreSQL through Drizzle ORM (39 migrations), with Supabase for auth, database and storage.

GRAPHQL YOGAPOSTGRESQLDRIZZLESUPABASERAILWAY
api.bizph.app/graphqlSAMPLE DATA
query WorkspaceInvoices($status: [InvoiceDocumentStatus!]) {
invoices(filter: { status: $status }, limit: 1) {
nodes { invoiceNumber status total currency }
}
}
→ 200 OK · org resolved from session (santos-bakery)
{ "invoices": { "nodes": [
{ "invoiceNumber": "INV-2026-0151", "status": "issued", "total": "4200.00", "currency": "PHP" }
] } }
SYSTEM_05

Background Worker

A pg-boss worker processes the transactional outbox for cross-product integration and runs scheduled jobs for expiry, reminders and cleanup.

PG-BOSSOUTBOXRAILWAY
railway · bizph-worker · logsSAMPLE DATA
pg-boss started
outbox picked invoice.payment.recorded
cashflow reacted entry created
outbox picked pos.sale.completed
inventory reacted stock-out posted
cashflow skipped module not enabled for org
cron run bizph.tasks.process-reminders
cron run bizph.org-logos.sweep
SYSTEM_06

Testing & Release Gate

About 376 test files (254 unit and 122 integration, the integration ones against a real PostgreSQL) and 40 end-to-end specs. A release-gate Playwright suite includes security checks. 12 ADRs and a master architecture document record the decisions.

PLAYWRIGHTINTEGRATION TESTS12 ADRS
pnpm test:release-gateSAMPLE DATA
pnpm test && pnpm test:integration
vitest · unit + integration (real PostgreSQL)
✓ magic-bytes upload sniffer — fake PNG rejected
✓ invoice money math — VAT computed in PHP
✓ outbox consumer — event delivered exactly once
✓ tenant-isolation.spec.ts (Playwright) — cross-org query denied
✓ product-entitlement-gate.spec.ts (Playwright) — locked UI for unentitled product
376 test files · 254 unit / 122 integration · 40 Playwright e2e specs
12 ADRs + master architecture blueprint
07

KEY DESIGN DECISIONS

SERVER DECIDES

Tenant identity and authorization are resolved on the server. The frontend only hides things for UX.

DATA OVER ROLE NAMES

Permissions and per-product entitlements live in the database, so access follows what a customer has bought.

OPTIONAL BY DESIGN

Cross-product links are events, and each one is optional, so any module can be sold and used alone.

WRITTEN DOWN

Decisions are recorded as 12 ADRs, including the logo-upload security design.

08

AI-Assisted Solo Development.

Bizph is a solo engineering project. I designed the system, wrote and reviewed the code, and I'm the only person who has worked in the codebase.

AI tools were a development partner throughout — for implementation, exploring approaches, documentation, debugging and iterating quickly on the systems described above. What they didn't do was decide the tenant model, choose what stays isolated versus shared, set the testing strategy, or sign off on what shipped. Those decisions, and the final code that implements them, stayed mine.

09

THE CURRENT STATE

Bizph went live at bizph.app on 2026-09-20 and is pre-launch. It has no paying customers yet, so there are no user, revenue or retention figures to report. What exists today is a deployed, tested platform: 12 products, 5 apps and 29 packages behind one login, with wildcard tenant subdomains across Vercel, Railway and Supabase. The repository is private.

→Live at bizph.app since 2026-09-20 (pre-launch, no paying customers yet)
→One login and one workspace across 12 modular products
→Server-side tenancy and database-driven authorization
→Optional cross-product integration through a transactional outbox
→Security decisions documented in 12 ADRs
→Automated release-gate suite checks tenant isolation, authorization and upload safety before every deploy
10

What Remains Unproven.

The platform side of this is built and tested. The market side hasn't been tested at all — and those are two different kinds of validation. What's still open:

→Which product combinations businesses actually choose, versus the bundles I assumed they’d want
→Which industries — water stations and delivery, retail, salons, inns — have the strongest real demand
→Which workflows need deeper, industry-specific features rather than a general product
→How much hands-on onboarding a small business needs before it adopts a new system on its own
→Which integrations — payment providers, government reporting, accounting — turn out to be essential once real usage starts
→Which of the twelve products should stay general-purpose, and which should specialize over time
REFLECTION

Building this solo meant the architecture decisions had nowhere to hide — whatever shortcut I took, I'd be the one debugging it later. That pushed me toward writing decisions down as ADRs, testing the boundaries that matter most first (tenant isolation, authorization), and keeping products genuinely optional instead of assuming I'd get every dependency right on the first try. The next real test isn't a code review. It's whether a small business picks two products, keeps using them, and asks for a third.