I recently began working on my first Filament v3 project and needed to hide the IDs for the records. For this task, I opted to use Sqids.
To begin, install sqids/sqids
by running the following command:
$ composer require sqids/sqids
To make Filament display Sqids in links instead of model IDs, let's create a trait:
use Sqids\Sqids;
/**
* @property-read string $sqid
*/
trait ModelMasksRecordId {
public static function getSqids(): Sqids {
return new Sqids(
alphabet: defined('self::SQID_ALPHABET') ? self::SQID_ALPHABET : Sqids::DEFAULT_ALPHABET,
minLength: defined('self::SQID_MIN_LENGTH') ? self::SQID_MIN_LENGTH : 6,
);
}
protected function getSqidAttribute(): string {
return self::getSqids()->encode([$this->getKey()]);
}
public function getRouteKey(): string {
return $this->sqid;
}
}
This trait offers a static method to obtain a Sqids
instance tailored to your model. It includes a $sqid
property that returns the encoded ID and implements the getRouteKey()
method used by Filament for record referencing.
Simply apply this trait to your models. Optionally, you can configure a custom alphabet and minimum length directly in the model:
class User extends Model {
use ModelMasksRecordId;
public const string SQID_ALPHABET = "348abe21dc7069f5";
public const int SQID_MIN_LENGTH = 10;
// ...
}
Filament needs assistance in decoding our Sqids. Let's create another trait for this purpose:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
trait ResourceMasksRecordId {
public static function resolveRecordRouteBinding(int|string $key): ?Model {
/** @var class-string<ModelMasksRecordId> $model */
$model = self::$model;
$key = $model::getSqids()->decode($key)[0] ?? null;
if (is_null($key)) {
throw new ModelNotFoundException();
}
return parent::resolveRecordRouteBinding($key);
}
}
We override the resolveRecordRouteBinding
method to attempt decoding before passing the ID to the original method. Apply this trait to your resources:
class UserResource extends Resource {
use ResourceMasksRecordId;
// ...
}
For multi-tenancy scenarios where you want to conceal tenant IDs, additional steps are required. First, ensure you apply ModelMasksRecordId
to your tenant model. Next, create a middleware:
use App\Models\Team;
use Closure;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
class ResolveTenantSqid {
public function handle(Request $request, Closure $next) {
if ($tenant = $request->route()->parameter("tenant")) {
$tenant = Team::getSqids()->decode($tenant)[0] ?? null;
if (is_null($tenant)) {
throw new ModelNotFoundException();
}
$request->route()->setParameter("tenant", $tenant);
}
return $next($request);
}
}
This middleware checks for a tenant
parameter and replaces it with the decoded id. Add this middleware to your panel provider:
use Filament\Panel;
use Filament\PanelProvider;
class AppPanelProvider extends PanelProvider {
public function panel(Panel $panel): Panel {
return $panel
// ...
->middleware([ ResolveTenantSqid::class ], true)
// ...
;
}
}
We must keep our ResolveTenantSqid
middleware separate from the original middleware()
list, due to the true
parameter. This parameter designates the middleware as "persistent", ensuring its addition as a Livewire middleware.
That's all there is to it!