Built for a multi-location salon group.Client named on request, under NDA.
- POS & Retail
- Multi-site operator
- End-to-end build
POS App Development Case Study: How We Built a Role-Based System for a Multi-Location Salon Group
A multi-location salon group replaced paper books, a standalone till, and a spreadsheet with one Flutter POS app — roles enforced at the data layer, offline-first, and every claim on this page verifiable.
Replacing Disconnected Systems with One Unified Platform
This POS app development case study covers a multi-location salon group that was running three disconnected systems: appointments in a paper book, sales through a standalone till, and staff hours in a spreadsheet — a separate copy of each, per location. We replaced all three with a single Flutter-built retail app where an owner, a supervisor, and a cashier see three genuinely different products, with permissions enforced at the data layer rather than the interface, and transactions that keep working through a network outage.
Three Systems That Were Never Designed to Talk to Each Other
Each salon kept its own paper appointment book, its own till, and its own weekly staff-hours spreadsheet. That setup holds up fine until someone needs an answer that spans locations — which services are actually profitable, which staff member is busiest and where, whether this week is ahead of last week. Every one of those questions meant manually reconciling three separate sources of truth, so in practice they went unasked. The client wanted one mobile app for the whole operation — the real brief was in what "one app" needed to mean.
Same App, Three Different Operating Realities
An owner needs visibility across every location at once. A supervisor needs full control of their own salon and zero visibility into any other. A cashier needs to move through a sale quickly and must never be able to reach payroll data.
The common shortcut — one interface with controls hidden per role — looks fine in a demo and fails in production: the data still reaches the device, the permission logic lives in the UI layer where a future refactor can quietly break it, and every new feature is a fresh chance to leak one location's numbers onto another's screen.
Two more constraints shaped the build: bookings made through the group's website had to reach the app with no manual re-entry, and the app had to keep taking payments through a network outage — a POS that stops selling on a busy Saturday because the router rebooted has not been given a solution, it has been given a liability.
Four Load-Bearing Decisions
Role-based architecture with per-salon isolation
Three access levels — owner, supervisor, cashier — enforced through role-scoped Firestore rules, not UI logic. A cashier's queries physically cannot return another location's records. The isolation lives in the backend, so it can't be undone by a future UI change.
A QR bridge between web bookings and the till
Customers book online; the app generates and scans a QR code that resolves a booking straight to the right service and staff member at checkout. No manual lookup, no re-typing, no transcription errors during rush hour.
Owner-readable reporting, no portal required
Sales graphs update live as transactions close. Owners and supervisors export revenue by service, staff, or period as a PDF or Excel file generated directly on the device.
Offline-first by design, not as a patch
Data is cached locally; transactions queue during outages and sync automatically on reconnect. This was an architecture decision from day one, not a resilience feature added later.
Enforce Roles in the Data Layer, Not the Interface
Build one screen set, hide controls per role. Fast, common, and looks correct in a demo.
Why it fails hereHiding a control doesn't stop the data reaching the device. In a multi-location business, that's not theoretical — one salon's takings could be pulled from a device sitting on another salon's counter. It also decays over time, since permission logic sitting in the UI means every future screen is a fresh chance to reintroduce the gap, silently.
What we did insteadDesigned the data model — roles, scopes, isolation — before building a single screen, and enforced it as backend rules. The UI just reflects what the query returns.
What that boughtA new screen can't introduce a permission bug, because screens no longer hold permission logic. The security property belongs to the data, and holds regardless of who builds the next feature.
Tech Stack & Architectural Choices
Build Facts & Delivery Results
The app shipped to both the App Store and Google Play. Owners see cross-location performance on one screen, supervisors run their own site independently, and cashiers complete a transaction in fewer steps with fewer opportunities to get it wrong.
Build facts, verified against the delivered app. No outcome numbers are published unless they were actually measured.
What You Can Check Yourself
How each claim on this page can be verified
What's not shown, and why
Some of this engagement stays private. Naming what's missing seems fairer than quietly leaving gaps.
- The interface itself (client's product, illustrations only).
- The client's name (by their preference).
- Revenue figures (theirs to share, not ours).
Architecting Systems That Stay Correct Over Time
Multi-tenant apps — one product serving users who need genuinely different views of the same data — are harder than they look, and the difficulty is never in the screens. It's in deciding where permission logic lives. Put it in the interface and you have a demo. Put it in the data layer and you have a system that stays correct as other people extend it, and can be handed to a new developer without becoming a security review. This is the same approach we bring to hospitality, retail, field services, or any multi-location build.
Frequently Asked Questions
How long does it take to build a role-based POS app like this with Flutter?
Depends on scope, but a multi-role, offline-first POS with backend permission enforcement typically runs several months for a full end-to-end build, from data modeling through app store release.
What does a multi-location retail app with role-based access cost?
Cost is driven mainly by the number of access levels, integrations (like QR/booking bridges), and offline-sync requirements — not by screen count. Get a scoped estimate based on your actual role structure rather than a flat industry number.
Why enforce permissions in the database instead of the app's UI?
Because UI-level permission hiding doesn't stop data from reaching the device — it only hides it visually. Data-layer enforcement means a permission bug can't be introduced by a future screen or refactor.
Does the app work if the internet goes down mid-sale?
Yes — transactions queue locally and sync automatically once connectivity returns, so a network outage doesn't stop sales.
Building something where roles actually matter?
Bring the awkward parts of your requirements; those are the useful ones.