Skip to main content

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 get vintage-denim-jacket.
  • polyslugResolveQuery() — what may be reached. A paused listing returns a 404 that 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.

See also