NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

A placement is a zone where ads render — sidebar, header, in-feed. It has a translatable name, a code-facing slug and optional width and height in pixels, which size the creative’s display variant and the text-ad fallback:

use RoundlyConsulting\Advertisements\Models\Placement;

$sidebar = Placement::query()->create([
    'name' => ['en' => 'Sidebar', 'sk' => 'Bočný panel'],
    'slug' => 'sidebar',   // optional — generated from the fallback-locale name when empty
    'width' => 300,        // optional pixel size for the display variant and the text ad
    'height' => 250,
]);

$leaderboard = Placement::query()->create(['name' => ['en' => 'Leaderboard'], 'width' => 728, 'height' => 90]);
$leaderboard->slug;        // "leaderboard"

Leave slug empty and it’s generated from the fallback-locale name (Side Banner → side-banner, suffixed -2 when taken). A slug you supply is kept byte-for-byte, a taken one throws SlugAlreadyTakenException, and it never changes afterwards — it’s the storage key of the placement’s creatives (creative:{slug}), so a rename must not orphan them.

Assigning and serving

use RoundlyConsulting\Advertisements\Facades\Advertisements;
use RoundlyConsulting\Advertisements\Models\Advertisement;

Advertisements::for($ad)->placements()->attach(['sidebar', $leaderboard->id]); // models, ids, or slugs
Advertisements::for($ad)->placements()->sync(['sidebar']);
Advertisements::for($ad)->placements()->detach(['sidebar']);
Advertisements::for($ad)->runsIn('sidebar'); // true

// Active ads in a placement, or one at random:
Advertisements::in('sidebar')->get();
Advertisements::random('sidebar');

Advertisement::query()->forPlacement($sidebar)->get(); // model, id, or slug
  • for($ad)->placements() — attach() adds without detaching existing ones, sync() replaces the set, detach() removes. Each returns the ad with placements reloaded.
  • Every method accepts placement models, ids or slugs. A string is matched as a slug first; a digit-only string no placement uses as its slug is then taken as an id, so form and route input ('12') works as-is. An unknown reference is skipped by attach(), sync() and detach() and matches nothing in in() or forPlacement().
  • for($ad)->runsIn($placement) tells whether the ad is attached.
  • in($placement) is active()->forPlacement($placement); random() picks one active ad at random, optionally per placement, or returns null.
  • targetedIn($placement, $viewer) is the geo-aware version of in() — see Geo-targeting.

Relations

$ad->placements;            // BelongsToMany<Placement> — the pivot carries meta + timestamps
$sidebar->advertisements;   // BelongsToMany<Advertisement>
$sidebar->width;            // ?int
$sidebar->height;           // ?int

The advertisement_placement pivot is unique per pair; its foreign keys cascade when an ad or placement row is permanently removed. Placements use soft deletes.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.