Marketplace
Two shops may sell a "Vintage Denim Jacket", and neither of them wants their draft or paused listings reachable by anyone who guesses a URL.
#[Polyslug(source: 'title', scope: 'shop_id')] // each shop's listings are unique within the shop
class Listing extends Model implements Sluggable
{
use HasPolyslug;
public function polyslugResolveQuery(Builder $query): Builder
{
return $query->where('status', 'published'); // a draft or paused listing 404s — no id enumeration
}
public function polyslugIsRoutable(?string $locale = null): bool
{
return $this->status === 'published'; // keep drafts out of the sitemap and hreflang
}
}
The three hooks answer three different questions:
scope: 'shop_id'— who may own the slug. Both shops getvintage-denim-jacket.polyslugResolveQuery()— what may be reached. A paused listing returns a404that looks exactly like a nonexistent one, so the URL space cannot be probed for unreleased inventory.polyslugIsRoutable()— what is advertised. Drafts stay out of the sitemap and out of every hreflang set.
Leaving the third one out is the subtle mistake: the listing is unreachable, but your sitemap still announces it exists.
Unpausing a listing
Nothing needs regenerating — the gate is evaluated per request, so a listing becomes
reachable and sitemap-eligible the moment its status changes. Regenerate the sitemap file
on a schedule or from a
SlugChanged listener.