Skip to content

Companies

Reshape is multi-tenant: a Company is a workspace that users belong to, switch between, and act within. Tenancy is powered by spatie/laravel-multitenancy using a single shared database — every tenant's data lives in the same tables, scoped by company_id, rather than separate databases or schemas.

Switching between companies

The current tenant

Company implements Spatie's IsTenant contract. A middleware resolves "the current company" from the authenticated user on every request and calls Company::makeCurrent(), so tenant-aware queues dispatch jobs in that company's context automatically (config/multitenancy.php). This is also what scopes authorization checks — $user->in($company)->hasPermissionTo(...) uses exactly this "current company" concept.

A user isn't required to belong to a company at all — User::tenant falls back to the user themselves, so personal accounts work the same way individual features do.

Actions

php
// Create a company, make it current, assign the creator as CompanyOwner...
App\Actions\CreateCompany::create(User $user, array $input) : App\Models\Company

// Update a company's name and information record...
App\Actions\UpdateCompany::update(User $user, Company $company, array $input) : void

// Purge a company (detach members, soft-delete)...
App\Actions\DeleteCompany::delete(Company $company) : void

Company Object

php
// Access the company's owner...
$company->owner : App\Models\User

// Get every user belonging to the company, via the Membership pivot...
$company->users() : Illuminate\Database\Eloquent\Relations\BelongsToMany

// Detach every member, then soft-delete the company...
$company->purge() : void

Trait Methods (HasCompanies)

php
// Access a user's currently selected company, or null...
$user->currentCompany : App\Models\Company|null

// The companies a user owns, and the companies they're a member of...
$user->ownedCompanies() : Illuminate\Database\Eloquent\Relations\HasMany
$user->companies() : Illuminate\Database\Eloquent\Relations\BelongsToMany

// Switch the user's current company, if they belong to it...
$user->switchCompany(Company $company) : bool

// Determine if a given company is the user's current one...
$user->isCurrentCompany(Company|int|null $company) : bool

// Determine if a user owns a given company...
$user->ownsCompany(?Company $company) : bool

// Determine if a user belongs to (owns or is a member of) a company...
$user->belongsToCompany(Company|int|null $company) : bool

The frontend's sidebar company-switcher.vue component lists every company a user belongs to and switches on selection, clearing back to a personal context when no company is chosen.

Members

A company's members are tracked through a Membership pivot (company.users()), separate from ownership (company.owner(), via user_id). Adding a member both syncs the pivot and assigns roles scoped to that company in one transaction, then fires CompanyMemberAdded — which triggers a real-time notification to the new member.

Actions

php
// Add an existing user to a company and assign roles, firing CompanyMemberAdded...
App\Actions\AddCompanyMember::add(User $user, Company $company, string $email, Collection $roles) : App\Models\User

// Change a member's company-scoped roles...
App\Actions\UpdateCompanyMember::update(User $user, Company $company, User $member, array $roles) : void

// Remove a member (refuses to remove the company's creator)...
App\Actions\RemoveCompanyMember::remove(User $user, Company $company, User $companyMember) : void

Invitations

Inviting someone who doesn't have an account yet (or hasn't joined the company) sends an email with a signed acceptance link, carrying the roles they'll be granted on acceptance. The acceptance link is signed and unauthenticated (so it works from a cold email click) — accepting delegates to the same member-adding logic used for direct member creation, then redirects back into the app.

Action

php
// Create a pending invitation, attach roles, and email the invite...
App\Actions\InviteCompanyMember::invite(User $user, Company $company, string $email, array $roles) : void

Company profile

Company carries a CompanyInformation relation (legal name, structure type, registration details), plus logo and banner media collections via HasMedia. It also implements CommentableInterface, so comments can attach directly to a company. Companies own contacts, addresses, customers, and announcements the same way users do, through the shared Has* traits.