Dealing with file uploads in multi-tenant Laravel applications is a challenge unlike others: how do you isolate each tenant’s files and make them accessible to them alone? The stancl/tenancy package provides extremely powerful multi-tenancy tools, but file storage requires attention to configuration.
Please check out the setup process for multi-tenancy.
Also checkout official docs for stancl/tenancy
Follow the steps below:
Step 1: Enable suffix_storage_path
First of all inside the tenancy config file, enable the suffix_storage_path.
Step 2: Publish your Livewire config file and modify the file
First of all, you must need to publish the livewire config file, if its not published before. To publish the livewire config file, just paste the command:
php artisan livewire:publish
Following that we need to update the temporary_file_upload.middleware , where we will add the middleware for tenant specific. Check the code below:
'temporary_file_upload' => [
.......
'middleware' => ['throttle:60,1',InitializeTenancyByDomain::class], // Example: 'throttle:5,1'
.......
],
Note: if you are using any other tenancy domain, please use that.
Step 3: Create Tenant Specific directories for storage
When using storage_path()
suffixing (for local filesystem tenancy), each tenant gets a separate subdirectory in storage/
. So, we must need to create the storage directories by ourselves. So, for that we will add a JOB that triggers when tenant created event is fired.
To create a job, hit the following code:
php artisan make:job CreateFrameworkDirectoriesForTenant
Copy the following code for your job.
<?php
namespace App\Jobs;
use App\Models\Tenant;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CreateFrameworkDirectoriesForTenant implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $tenant;
public function __construct(Tenant $tenant)
{
$this->tenant = $tenant;
}
public function handle()
{
$this->tenant->run(function ($tenant) {
$storage_path = storage_path();
$suffixBase = config('tenancy.filesystem.suffix_base');
if (!is_dir(public_path($suffixBase))) {
@mkdir(public_path($suffixBase), 0777, true);
}
if (!is_dir($storage_path)) {
@mkdir("{$storage_path}/app/public", 0777, true);
@mkdir("{$storage_path}/framework/cache", 0777, true);
symlink("{$storage_path}/app/public", public_path("{$suffixBase}{$tenant->id}"));
}
});
}
}
The above job will create all the necessary directories for us to upload the files.
Now to trigger the job, we will modify TenancyServiceProvider class. Inside the events array, we can modify .
Events\TenantCreated::class => [
JobPipeline::make([
Jobs\CreateDatabase::class,
Jobs\MigrateDatabase::class,
CreateFrameworkDirectoriesForTenant::class // add your job here..
After completing the migration, it will create the necessary framework directories for the tenants.
Step 4: Create a custom middleware to handle file path
After that, we will just create a custom middleware. To create a new middleware, you can use the following command:
php artisan make:middleware FileUrlMiddleware
It will create a new FileUrlMiddleware, just copy the following code:
public function handle(Request $request, Closure $next): Response
{
config()->set(
'filesystems.disks.public.url',
url('/' . config('tenancy.filesystem.suffix_base') . tenant('id'))
);
return $next($request);
}
We are just setting the public_url to tenant based. Now, add this middleware to every tenant based routes. I added this middleware on tenant.php , PanelProvider.
Conclusion
In this way, you can upload file on tenant basis. If you have any confusion or any queries. Please let me know.