Implementing Multi-Company Switching in an Accounting Software
In my current project, we’re building an Accounting Software that integrates Amazon Selling Partner data and manages corresponding bank transactions. One of the key challenges we faced was allowing users to seamlessly switch between multiple companies without having to open another browser or log in again.
Existing Database Structure
Our user system is relatively simple. The users table looks like this:
Column | Description |
---|---|
id |
Primary key |
name |
User’s name |
email |
User’s email address |
created_by |
ID of the user who created this record (default: 0) |
type |
User type (e.g., super admin , company , etc.) |
Every related table references the creator using the created_by
foreign key. This setup worked well for single-company users but needed an enhancement for multi-tenancy support.
Designing Multi-Tenancy Support
To support multiple companies under one user account, we introduced two key changes:
-
A new column
current_company_id
in the users table → Tracks which company the user is currently managing. -
A new user type owner → Represents users who own one or more company accounts.
Whenever an owner switches to another company, the application simply updates their current_company_id:
// CompanySwitcherController.php
public function update(Request $request, User $company)
{
Auth::user()->update([
'current_company_id' => $company->id,
]);
return redirect()->route('dashboard');
}
Ensuring Data Accuracy
The critical part of this setup is ensuring that all database queries and relationships use the correct creator ID. Otherwise, data inconsistencies could occur.
Here’s the logic we use to determine which ID to use as the “creator” reference:
// User.php
public function creatorId()
{
if ($this->type === 'owner') {
return $this->current_company_id;
}
if (in_array($this->type, ['company', 'super admin'])) {
return $this->id;
}
return $this->created_by;
}
This ensures that no matter who’s logged in an owner, a company user, or an admin the application always loads and interacts with data under the correct company scope.
Onboarding Flow
The onboarding process remains mostly the same, with one addition: When a new owner signs up, we automatically create a default company user associated with that owner.
Here’s the onboarding logic:
$owner = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'type' => 'owner',
'last_login_at' => Carbon::now(),
'timezone' => 'America/Los_Angeles',
'onboarded' => 0,
]);
$user = User::create([
'name' => $request->name,
'email' => str_replace('@', '+company@', $request->email),
'password' => Hash::make($request->password),
'type' => 'company',
'last_login_at' => Carbon::now(),
'timezone' => 'America/Los_Angeles',
'created_by' => $owner->id,
]);
$owner->update([
'current_company_id' => $user->id,
]);
This way, every new owner starts with a ready-to-use company account and can later create or switch to other companies as needed, all within the same session.
Summary
By introducing current_company_id and differentiating between owner and company user types, we achieved a clean and efficient multi-company experience in our accounting platform.
This approach keeps the existing database structure intact, avoids major schema overhauls, and ensures that user actions always operate within the correct company context.