YumKiosk YumKiosk Docs
Website Agent login Owner panel
Architecture

Multi-tenant model

How owners, users, kiosks, and sessions relate in the data model.

Multi-tenant model

YumKiosk is a multi-tenant SaaS where every restaurant operator has their own isolated dataset — their own agents, kiosks, menu, orders, and reports. This page explains how tenancy is implemented in the data layer, the entity hierarchy, and how we ensure one tenant never sees another's data even when we want to share infrastructure aggressively.

The tenant hierarchy

The top-level entity is the Owner, which represents a business account. Everything in the system belongs to exactly one owner:

Owner
 ├─ Users (agents, managers, viewers)
 ├─ Locations (physical restaurant addresses)
 │   └─ Kiosks (tablets paired to a location)
 │       └─ Sessions (individual customer interactions)
 │           └─ Orders (the cart + payment)
 ├─ Categories
 │   └─ MenuItems
 │       └─ ModifierGroups
 ├─ Subscription (Stripe Billing)
 └─ ConnectAccount (Stripe Connect, for customer payments)

Every row in every table that belongs to a tenant has an owner_id foreign key. Global queries that scan a table without that filter are forbidden by a Laravel global scope at the model layer — see the BelongsToOwner trait in app/Models/Traits/BelongsToOwner.php.

Tenant isolation

We use the stancl/tenancy package in a "single-database, scoped-queries" mode. This means:

  • One MySQL database for all tenants.
  • A global scope on every tenant-owned model that auto-filters WHERE owner_id = ? based on the current authenticated user's owner.
  • A middleware (EnforceTenant) that sets tenant()->id at the start of every request based on the subdomain (for owner.yumkiosk.com) or the authenticated user (for agent.yumkiosk.com).

This setup lets us share infrastructure aggressively — one Laravel process, one database, one cache — while giving tenants hard isolation at the query layer. It also makes backup/restore and migrations simple compared to the alternative of one database per tenant.

The tradeoff is that a bug in our query-scoping middleware could leak data across tenants. We mitigate this with:

  1. Extensive tests covering every tenant-owned model.
  2. A pre-commit hook that flags any raw DB::table() calls without an explicit owner_id filter.
  3. Regular third-party security audits.

Subdomains and white-labeling

Some subdomains are tenant-scoped (white-label subdomains like demo.yumkiosk.com serve a specific tenant's branded marketing page). Others are global (agent.yumkiosk.com serves all agents across all tenants, differentiated by login). The nginx config in front of Laravel sets an X-Tenant header where applicable, and the EnforceTenant middleware reads it.

Users and roles

The users table has a flexible role system via spatie/laravel-permission. A user can be:

  • An owner — a real person who runs the business. Has full access via the owner panel at owner.yumkiosk.com. Exactly one per Owner row.
  • A manager — a user with elevated permissions within an owner account. Can invite agents and view team reports.
  • An agent — a user who handles sessions. Scoped to their owner; sees only their own team.
  • A super admin — global access across all tenants. Used by YumKiosk staff for support. Lives in a separate auth guard and is not visible in any tenant's user list.

Super admins can impersonate an owner or an agent for debugging purposes via /admin/impersonate/owner/{id} or /admin/impersonate/agent/{id}. Impersonation sessions are logged in the activity log and a banner is shown at the top of the page so the impersonating admin always knows they're in someone else's account.

Cross-tenant features

A few features genuinely cross tenant boundaries and need special handling:

  • Shared agent pool: agents who opt in can take calls for any owner on YumKiosk. These sessions are attributed to the owner whose kiosk originated them for billing, but the agent belongs to the platform, not the owner. Implemented as a special owner_id = null flag on the user row plus a separate pool_sessions table.
  • Global analytics: YumKiosk internal dashboards (at admin.yumkiosk.com) aggregate across all tenants for platform health monitoring. Access is super-admin only.

Every other query in the system is strictly scoped to a single tenant.