---
url: /guide/tenancy.md
description: >-
  Scope the media library per tenant with Filament tenancy, stancl/tenancy or a
  custom resolver. Covers strict mode, quotas and standalone pages.
---

# Tenancy

The library can keep every tenant's files apart. Folders, files and tags carry a tenant, uploads are stamped with it, and every query is filtered by it. Tenancy is off until a panel with tenancy, or your config, turns it on.

## Filament panel tenancy

Nothing to configure. When the panel uses `->tenant(Team::class)`, the plugin turns tenancy on and resolves the tenant with `Filament::getTenant()`.

```php
use Hoceineel\FilamentMediaLibrary\FilamentMediaLibraryPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->tenant(Team::class)
        ->plugin(FilamentMediaLibraryPlugin::make());
}
```

Everything is scoped to the current tenant:

| What | How it is scoped |
|---|---|
| Files, folders and tags | A `tenant_type` and `tenant_id` column on each table, filtered by a global scope. |
| New records | Stamped with the current tenant when they are created. |
| Upload and download routes | Registered as tenant routes, so the tenant is in the URL, for example `/studio/media-library/upload`. |
| Picker validation | The [picker field](/guide/picker-field) rejects ids that belong to another tenant. |
| Attachments | `syncMediaLibraryItems()` drops ids from another tenant. |
| Rich editor | The [rich editor plugin](/guide/rich-editor) never inserts another tenant's files. |
| Quota | Counted per tenant. See below. |

In strict mode, which is on for Filament tenancy, a request with no resolved tenant sees nothing at all.

## stancl/tenancy, single database

Store every tenant in one database with a `tenant_id` column, and resolve the tenant from stancl:

```php
use Hoceineel\FilamentMediaLibrary\Tenancy\StanclTenantResolver;

FilamentMediaLibraryPlugin::make()
    ->tenancy()
    ->resolveTenantUsing(StanclTenantResolver::class);
```

`StanclTenantResolver` calls stancl's `tenant()` helper. It returns the tenant model, or `null` when stancl has not been initialised for the request.

::: warning
On a panel without Filament tenancy, the plugin's `strict` argument has no effect: the library does not hide files when no tenant resolves. Make sure stancl's tenancy middleware runs on the panel's routes. For pages outside panels, use the `tenancy.strict` config key instead.
:::

## stancl/tenancy, database per tenant

The library tables live in each tenant's database, so the database already isolates tenants.

1. Add the package migration to your tenant migrations.
2. Keep `tenancy.enabled` set to `false`.
3. Enable stancl's `FilesystemTenancyBootstrapper`, so each tenant writes to its own disk path.

## Custom resolver

Pass a closure, a class name or a `TenantResolver` instance to `resolveTenantUsing()`:

```php
FilamentMediaLibraryPlugin::make()
    ->tenancy()
    ->resolveTenantUsing(fn () => auth()->user()?->organization);
```

A resolver class implements one method and returns an Eloquent model, or `null`:

```php
use Hoceineel\FilamentMediaLibrary\Tenancy\TenantResolver;
use Illuminate\Database\Eloquent\Model;

class OrganizationResolver implements TenantResolver
{
    public function resolve(): ?Model
    {
        return auth()->user()?->organization;
    }
}
```

The tenant is stored as a morph (`tenant_type` and `tenant_id`), so it can be any model. The `tenant_id` column type follows `key_types.tenant` in the config. Set it to `uuid`, `ulid` or `string` before you run the migration if your tenants do not use integer keys.

## Strict mode

| Strict | When no tenant resolves |
|---|---|
| on | Every query returns nothing. Safe by default. |
| off | The tenant filter is skipped, so the library shows every record. |

Leave strict mode on unless you know why you need otherwise. A missing tenant then shows an empty library, not another tenant's files.

## Standalone pages

Pages outside panels have no panel tenant. Turn tenancy on in `config/filament-media-library.php` and give it a resolver:

```php
'tenancy' => [
    'enabled' => true,
    'resolver' => App\Support\CurrentTeamResolver::class,
    'strict' => true,
],
```

| Key | Default | Meaning |
|---|---|---|
| `enabled` | `false` | Scope the library to a tenant outside panels. |
| `resolver` | `FilamentTenantResolver` | A class implementing `TenantResolver`. |
| `strict` | `true` | Hide everything when no tenant resolves. |

The default resolver reads the Filament panel tenant, which does not exist outside a panel, so always set your own. Panels with tenancy keep using their own tenant, whatever this config says. See [Outside Panels](/guide/outside-panels).

## Quotas

Limit storage per tenant, or for the whole app when tenancy is off:

```php
'quota' => 5 * 1024 ** 3,
'quota' => PlanQuota::class,
```

The value is a number of bytes, `null` for unlimited, or an invokable class that receives the current tenant and returns a byte limit, or `null`.

```php
use Illuminate\Database\Eloquent\Model;

class PlanQuota
{
    public function __invoke(?Model $tenant): ?int
    {
        return $tenant?->plan === 'pro' ? 50 * 1024 ** 3 : 5 * 1024 ** 3;
    }
}
```

Used space counts every file the tenant has, including files in the trash and previous versions. An upload that does not fit is refused. For chunked uploads the check runs when the first chunk arrives, before the file is stored. The library sidebar shows the storage used.

## Private files

Tenancy separates tenants. Private files separate people inside a tenant.

A user can mark a file or folder as **Only me**. Private records are visible only to their uploader, and to users who pass the `viewPrivate` policy ability, for example admins. Turn the feature off with `private_media => false`. See [Private Files](/guide/private-files) and [Authorization](/reference/authorization).
