Overview

Company business data is isolated at company or organization level, as described below. IAM also supports clients under an organization. Each selected company or client has an iam_scopes row, used as Spatie’s team ID. Authentication and profile switching can run before a scope has been selected. Clients do not automatically gain access to company business packages. Their guards are client-api and client-agent, with an explicit permission catalog. IAM roles and permission pivots use scope_id; company business tables keep company_id. Profiles and assignments have exactly one company or client owner. See IAM overview for the shared access model.

Hierarchy

  • An Organization groups multiple companies under one umbrella (e.g., a holding company)
  • A Company is the core business unit. It can be a main company (parent) or a branch (child)
  • Some data (like the product catalog and assets) is shared at the organization level — all companies in the same organization see the same products
  • Other data (like orders, customers, stock) is scoped to the company level — each branch has its own

How tenancy is enforced

1. JWT Token carries the context

After a company human selects a profile, the JWT token encodes:
Every request automatically carries this context.

2. Middleware resolves the tenant

The exowpee.teams middleware (registered by the IAM package) runs on every protected route and:
  1. Reads the JWT token
  2. Resolves the selected company_id or client_id and organization_id
  3. Sets the tenant context for the request
  4. Configures Spatie’s permission team system using the matching IAM scope
The parent does not replace authentication, active-session checks or business authorization. Protected routes also need a complete access group, such as exowpee.company or exowpee.client.

3. Queries are automatically scoped

Models use the resolved tenant context to scope queries:

4. Policies check ownership

Authorization policies verify that the requested resource belongs to the user’s current organization/company:

Data scoping by level

Why organization-level sharing?

For businesses with multiple branches, the product catalog should be the same everywhere. A “Samsung Galaxy A54” in the Cotonou branch is the same product in the Porto-Novo branch. But each branch manages its own stock levels, orders, and customers independently.

Switching companies

A user can belong to multiple companies through different UserProfiles. To switch context:
This returns a new JWT token with the updated company_id and organization_id.

Helper functions

Exowpee provides global helpers to access the current tenant context anywhere in the code:
These helpers are defined in packages/shared/src/Helpers/helpers.php and app/Helpers/helpers.php.

For developers

When writing new code:
  1. Always scope queries by company_id or organization_id
  2. Always check ownership in policies
  3. Never assume a single company — the system is multi-tenant
  4. Use the company() and getTeamOrganization() helpers instead of hardcoding IDs
  5. Write migrations with company_id or organization_id columns on any tenant-specific table