Portfolio / System Ops / ghostkitchen

A ghost kitchen,
built before day one.

The client came to us before opening — planning a five-brand ghost kitchen running out of one prep line, and looking for a system that could carry it from launch. The brief was clear: every brand had to feel like its own shop to the customer, but the kitchen, the admin and the books needed to behave like one operation. We built the whole stack to that brief, ready on opening day.

ClientGhostKitchen (pre-launch)
ScopeCatalog · Kitchen · Admin · Delivery
Brands at launch2 ready · 3 templated
Timeline5 weeks · 2026
What we built

A multi-tenant order stack disguised as five different stores.

Each brand gets its own catalog page, its own colour, its own status tracker, its own Instagram-style feed. To the customer it reads as a focused shop. Behind the curtain it's one database, one kitchen dashboard and one finance log — tagged by brand at every row, so the math will be honest from the very first month-end.

Module 01

Per-brand catalog

Instagram-feed style menu per brand — carousel posts, double-tap love, add-to-cart stepper, catatan field. Each brand has its own URL, palette and tone of voice. Customer taps the brand's Instagram or TikTok bio link, lands on its storefront and sees one shop, not five.

BrandedPWASold-out hook
Module 02

Geo-aware checkout

Live geolocation calculates delivery fee against the kitchen pin before the customer commits. Pays via tunai / QRIS / transfer. Order lands in the kitchen the moment the QRIS hits.

QRISGeofenceNo CS
Module 03

Kitchen dashboard

One screen on the prep line. Every order in queue, colour-bordered by brand. Stations swipe through queued → cooking → ready. Live counts at the top. No paper tickets, no lost orders.

Brand-tagged3 phasesLive
Module 04

Live status tracker

Five phases — placed, paid, cooking, out for delivery, arrived. Customer sees the brand they ordered from, not "ghost kitchen." Auto-updated from kitchen dashboard taps. The status URL stays in their browser so they can re-open it instead of calling.

5 phasesRe-openable link
Module 05

Brand-aware admin

Owner's dashboard. Filter by brand, by phase, by date. See active orders, today's count, history, brand-by-brand revenue split. One view to manage all five brands — no switching apps.

Brand filterHistory
Module 06

Shared-cost finance

Per-brand revenue tracking with shared-cost allocation (rent, staff, gas, packaging). Monthly P&L per brand auto-computed. Marketing budget tracker so we know cost-per-customer per brand — the only number that decides what we launch next.

Per-brand P&LCAC

Five brands, one prep line. Two live on opening day; three pre-wired in the database.

● The ask

"Each brand has to feel like its own shop."

The client wanted to launch with two brands and have three more pre-wired in the database for later. Each brand had its own positioning, palette and audience — a customer ordering a Korean rice bowl should never know the same kitchen also ships geprek. The customer surface had to read as a single, focused brand from the first tap.

But repeating the build five times was off the table. We needed one shell that wore five faces — clean to launch a new brand in a day, not a sprint.

"Customer taps the bio link expecting a shop. They should see a shop, not a food court." — founder brief
9:41●●● ▮ 4G
B
Bap Bowl
Korean rice bowls · Open
Cart · 1
Bowls Sides Drinks
Bulgogi Bowl · telur ½
Rp 38.000
+
Spicy Pork Bap
Rp 42.000
+
● What we built

One catalog framework, six brand skins.

A single PWA shell renders the storefront. A brand slug in the URL pulls the right colour tokens, type, products and copy at boot — same code, different shop. Customer taps a brand's Instagram or TikTok bio link, lands on its focused storefront, never sees the others.

Spinning up a new brand for the client is now a row in a database, a colour and a product list. Geprek 28 and Sego Sambel shipped in week 2; Bap Bowl, Nasgor Mas and Tumis Bunda are pre-wired for whenever the client decides to flip them on.

9:41●●● ▮ 4G
G
Geprek 28
Sambal-first · Open
Cart · 2
Geprek Paket Minuman
Geprek Paha · sambal lvl 3
Rp 24.000
+
Paket Geprek + Es Teh
Rp 28.000
+
● The ask

"On day one, the cook can't be guessing which brand is which."

The risk the founders flagged early: with one prep team handling five brands, brand context disappears between order and plate. A "nasi + sambal + ayam" ticket could belong to Geprek 28, Sego Sambel or Nasgor Mas — and a wrong-brand box landing at a customer's door on launch week would be very public, very fast.

The kitchen surface had to make the brand impossible to miss, even at peak service, even on the first shift the team ever ran.

Dapur
4 antri 3 masak 2 siap
Geprek 28 #284
2× Geprek paha
1× Es teh
17:44 Masak
Bap Bowl #285
1× Bulgogi bowl
extra telur · pedas 2
17:45 Masak
Sego Sambel #286
1× Sego sambel daging
17:47 Antri
Nasgor Mas #287
3× Nasgor spesial
17:48 Siap
● What we built

Brand-tagged tickets, three-phase swipe.

Every ticket on the kitchen screen carries a coloured left-bar — one colour per brand. The brand name sits on top in bold mono. There's no scenario where the cook is reading a ticket without knowing whose order it is.

Tap to advance: antri → masak → siap. The customer's status tracker updates instantly. The admin's brand-queue chip ticks down by one. One tablet, three swipes, no second screen to keep in sync — ready for the staff to learn in an afternoon.

9:41●●● ▮ 4G
B
Bap Bowl · #285
Estimasi 18 menit
Live
Pesanan diterima
17:31 · QRIS confirmed
Antrian dapur
17:32 · #285 di queue
Sedang dimasak
17:45 · station 2
+ 6 menit
3
Berangkat antar
menunggu
4
Tiba di tempat
menunggu
● The ask

"We need to know which brand actually makes money."

Shared kitchen, shared staff, shared gas, shared packaging supplier. Per-brand revenue is trivial — sum the receipts. Per-brand profit is the harder number. The founders wanted that visible from month one, not after a year of guessing.

The point: the "do we launch the fourth brand?" call should be made on a number, not a feeling. That meant building shared-cost allocation into the system before the first order ever landed.

"If we can't see per-brand margin, we're flying blind on which brand to scale." — founder brief
9:41●●● ▮ 4G
$
Per-brand P&L
Live · auto-allocated
Month
G
Geprek 28
share of orders · 53%
margin tracked
S
Sego Sambel
share of orders · 47%
margin tracked
B
Bap Bowl
templated · awaiting launch
T
Tumis Bunda
templated · awaiting launch
● What we built

brand_id at every layer, allocation built into close-month.

Every order, ticket, status row and ledger entry carries the brand id from the moment it's written. Shared costs (rent, gas, base staff) auto-allocate by each brand's share of orders that month. Brand-specific costs (packaging SKUs, ingredients tagged to a brand) hit the right ledger directly.

Marketing spend is attached to a brand and a campaign — cost-per-customer computes itself from the order log. The brand-aware admin gives the owner one screen to read all six.

Admin · Live
17:48 · 11 aktif
Geprek 28
Antri4
Sego Sambel
Antri3
Bap Bowl
Antri2
#284Geprek 28 Masak 17:44
#285Bap Bowl Masak 17:45
#286Sego Sambel Antri 17:47
#287Nasgor Mas Siap 17:48
How it ran

Five weeks, brand-first.

We didn't start with the database. We started with a mocked catalog page for one of the launch brands, on a phone, and put it in front of the founders — would a customer believe this was a real shop? Once that landed, we worked backwards into the kitchen and the admin.

  1. Week 1–2 · Brand shells

    Built the customer-facing storefront framework first — one PWA shell, theme tokens per brand, IG-feed catalog, geo-checkout, status tracker. Wired up with the two launch brands' product photos and copy.

  2. Week 3 · Kitchen & admin

    Built the kitchen dashboard around the prep workflow the founders had on paper. Walked through it on the actual kitchen floor with the lead cook and the founders, rebuilt the screen twice. Brand-coloured tickets came from that walk-through.

  3. Week 4–5 · Finance + handover

    Shared-cost allocation, per-brand P&L, CAC tracking. Dry-run with seeded test orders end-to-end — customer flow, kitchen flow, admin flow, finance close. Handed over with the QR catalogs ready to switch on at opening.

What's ready at launch

Day-one scope, handed over.

This is what the founders walk into on opening day. Live-ops numbers (CAC, per-brand margin, throughput) come once the kitchen is running — we'll publish them when there's real data behind them.

2
Brands ready on day one
Geprek 28 + Sego Sambel · catalogs, pricing, status trackers all live
3
Brands templated
Bap Bowl, Nasgor Mas, Tumis Bunda — flip on when the founders are
5 wk
Brief to handover
customer storefront, kitchen, admin, finance — one engagement
1 day
To launch a 6th brand
new row, new theme tokens, new product list — same code path
100%
brand_id coverage
orders, tickets, ledger entries — nothing untagged in the database
0
SaaS subscriptions required
runs on infrastructure the founders already own — no monthly bill to scale
Under the hood

Built thin, on hardware we already had.

Architecture

One shell, brand-themed at render

The customer storefront is a single PWA. A brand slug in the URL pulls the brand's theme tokens (colours, typography, copy), product list and order destination at boot. New brand = new row in the brands table, new product list, ship.

Vanilla JS · CSS tokens · service-worker · per-brand manifest
Stack

Static frontend · PHP / MySQL backend

Hosted on the same XAMPP setup the owner already runs for the rest of the operation. No build step, no Docker, no reinvention. Cousin-can-read-it on purpose — the kitchen screen has to keep working when the developer is asleep.

XAMPP · PHP 8 · MySQL 8 · Swiper · Vanilla PWA
Order flow

Brand-tagged at every layer

Every order, ticket, status row and ledger entry carries the brand_id. Kitchen filters by it, admin filters by it, finance allocates by it. There is no place in the system where a row exists without knowing which brand it belongs to.

brand_id everywhere · soft delete · audit log
Customer surface

Bio-link first, no app install

Customer arrives from the brand's Instagram or TikTok bio link, opens a PWA, orders, pays QRIS, watches the status URL. No app store, no login. The brand's social does the marketing job; the storefront just has to convert.

PWA · IG / TikTok bio link · QRIS · localStorage cart
Kitchen surface

One tablet, three swipes

A wall-mounted Android tablet on the prep line. Tickets stream in, colour-bordered. Tap to advance through three states. Live counts at the top so the cook knows the queue depth without opening anything.

Android · fullscreen PWA · long-poll updates
Finance

Shared-cost allocation by order share

Rent, gas and base staff cost get split each month by each brand's share of total orders. Brand-specific costs (packaging SKUs, ingredients tagged to a single brand) hit the right ledger directly. Owner-readable formula visible per row.

monthly close · per-brand P&L · CAC linkage
Next → Mikro Farm

Running multiple
brands from one place?

Free scope-out call. We've done it for ourselves — we know what bites at month two.