Multi-tenant SaaS
Every tenant wants to name a document getting-started, and no tenant may reach another's
rows through a guessed URL. Those are two different problems, and each needs its own hook.
#[Polyslug(source: 'title', scope: 'tenant_id')] // 'hello' may repeat across tenants
class Doc extends Model implements Sluggable
{
use HasPolyslug;
public function polyslugResolveQuery(Builder $query): Builder
{
return $query->where('tenant_id', tenant()->id); // a slug resolves only within the tenant
}
}
scope: 'tenant_id'makes uniqueness per tenant, so two tenants can both owngetting-startedwithout a-2suffix appearing for the second one.polyslugResolveQuery()is the isolation contract. Without it, a URL from tenant A still resolves inside tenant B's session — the scope only decided who may own the slug, never who may reach it. A row outside the gate returns a404indistinguishable from a nonexistent one, so it is not an existence oracle either.
Use both. Either one alone leaves a hole.
Adding per-tenant rules
If tenants may define their own reserved words or their own encoder, implement
ConfiguresPolyslug instead of the static attribute — it is resolved fresh on every use:
public function polyslug(): PolyslugConfig
{
return PolyslugConfig::fromAttribute(new Polyslug(
source: 'title',
scope: 'tenant_id',
reserved: tenant()->reservedSlugs(),
));
}