Admin Panels: The Second App Hiding Inside Every SaaS

Every SaaS ships two interfaces: the product your users see and the back office your team lives in. How to build the second without doubling the codebase or the regret.

Admin panels: the second app hiding inside every SaaS

Almost every SaaS project starts with one app and ends with two. The first is the product. The second is the back office: the screens your support person opens at 9am to find a customer's subscription, refund a charge, unblock an account, or look up why an import failed. Teams treat that second app as an afterthought, and it repays the attitude with interest: hand-built one-off pages, permission checks that drift, and data edits nobody can trace.

An admin panel is a product too. It deserves a decision about architecture, not a pile of shortcuts.

Buy or build the shell

Laravel gives you a working admin shell almost for free: Filament or Nova generate CRUD screens, filters, and exports against your Eloquent models. For a back office with a handful of editors, that is the right default. Hand-building every list and form puts you months behind on features users would actually pay for.

The build-it-yourself case shows up when the admin stops being CRUD. Approval flows where one reviewer sees one slice of data, dashboards that mix internal and third-party data, anything where the workflow has more state than "list it, edit it, save it". Even then, keep the generated scaffolding for the boring 80% and build the 20% by hand.

What doesn't work is the middle path teams fall into: a hand-rolled panel grown page by page, each page with its own idea of how pagination, search, and permissions work. That codebase is where onboarding goes to die.

The features that earn their keep

A few admin capabilities pay for themselves within weeks, and we put them in every back office we build:

  • Search that finds a person fast. Email, order id, phone. Support lives or dies by this field; make it search across the entities at once.
  • User impersonation ("login as"). To debug what a customer sees, log in as them, in a clearly marked session with its own audit trail. Nothing else shortens a support loop as much.
  • Impersonation, refunds, and plan changes logged automatically. Every admin action that touches money or access should write to the audit log: who, what, when, old value, new value. Your future self, or your lawyer, will ask.
  • Read-only by default. An admin role that can inspect everything but modify little. Destructive power (refunds, deletions, plan overrides) goes to a separate role. The database of every SaaS contains exactly one accidental DELETE before this rule gets adopted.
  • Links deep into the domain. From a payment, jump to the subscription, the user, the webhook log. Admin work is mostly navigation between these records.

Don't fork your business logic

The expensive failure mode isn't a missing screen. It's the admin panel that quietly becomes a second implementation of the business rules. Admin pages that recalculate a price differently from checkout, approve refunds by writing a status directly instead of calling the same service the payment flow uses, or edit records the rest of the system has invariants about.

The rule that holds up: the admin panel is another client of your domain layer, not a second domain. Admin controllers call the same services and actions the product calls, with the actor's role attached. When that's honored, a change to the refund policy exists in one place, and the audit story stays coherent. When it isn't, every business rule grows a cheaper, darker twin that nobody tests.

Permissions without the sprawl

Keep the model boring: a handful of named roles (support, finance, admin), each with explicit capabilities, checked in one place. Resist per-user tweaks: "Dave can also delete users" ages badly, especially after Dave leaves. Laravel policies map onto this naturally, and since the panel shares the domain layer, the product's own authorization applies for free.

Ship it like the product

A few closing habits from panels we maintain: put the admin panel behind its own subdomain with separate rate limiting, require a second session factor for the privileged roles, and budget real time for it in the Sprint, because the back office is where churn quietly gets fought, one refund at a time.