Adment
CommunityA simple custom ad & Google AdSense manager for Filament v5 with placements, responsive (and GIF/video) creatives, weighted A/B rotation, scheduling windows, geo/device targeting, impression & CTR analytics, obfuscated click tracking, AdSense Auto Ads & ad units, ads.txt management, and an optional public JSON API.
Author:
Usama Muneer
Package health
BetaAutomated checks of this plugin's Composer package
15 checks
- Failed: GitHub Actions pinned to SHA — View details on Plumb
- Skipped: GitLab CI includes pinned to SHA
- Passed: Open security advisories
- Passed: Dependabot PR responsiveness — No open Dependabot PRs.
- Skipped: Renovate MR responsiveness
- Failed: Dependabot or Renovate configured — No dependency updater configuration found. View details on Plumb
- Skipped: Dependency update cooldown configured
- Failed: Provides a security policy — View details on Plumb
- Passed: Abandoned or archived — No consulted source marks the package abandoned (packagist, github).
- Passed: Commit and release recency — Active: last commit 6 days ago; last release 6 days ago.
-
Passed:
composer.lock not committed by library
—
composer.lockis absent from the released dist archive. - Warning: Dist archive is lean
-
Passed:
Current Laravel version supported
—
Package dependencies resolve together with current Laravel
13.0. -
Passed:
Current PHP version supported
—
Constraint
^8.3supports current PHP8.5. - Skipped: Current Symfony version supported
filament/
namespace. Review the source and install at your own risk. Found
malware or an unresolved security issue the author won't
address?
Report it
.
Documentation
- Features
- Installation
- Registering locations
- Displaying ads
- Weighted rotation & scheduling
- Geo & device targeting
- Media types
- Impression tracking & CTR
- AdSense
- Click tracking
- Public API (optional)
- Extending
- Architecture
- Testing
- Screenshots
- License
A simple custom ad & Google AdSense manager for Filament v5 on Laravel 12/13 — placements, responsive (and GIF/video) creatives, weighted A/B rotation, scheduling windows, geo/device targeting, impression & CTR analytics, obfuscated click tracking, AdSense Auto Ads & ad units, ads.txt management, and an optional public JSON API.
Requires: PHP 8.3+, Laravel 12 or 13, Filament 5 (Livewire 4).
#Features
- Ad placements (locations): register named slots in config or at runtime; drop ads into any Blade view.
- Weighted A/B rotation: when showing a single ad per location, selection is weighted by the ad's Weight (
ordercolumn). Higher weight = more likely to be shown. - Scheduling windows: optional
starts_atandexpired_atso custom ads only serve inside a time window. - Geo & device targeting: restrict custom ads to ISO country codes (via CDN headers) and/or desktop / tablet / mobile.
- Media types: image, GIF, or video creatives for custom ads (desktop / tablet / mobile variants with fallbacks).
- Impression tracking & CTR: client viewport beacon (
IntersectionObserver) records impressions; lifetime counters + daily stats power CTR dashboards. - Click tracking: obfuscated
/ac-{sha1}/{key}route increments a counter (without touchingupdated_at) and 302s to the destination. The raw destination URL never appears in your markup. - Analytics dashboard: Filament page with impressions/clicks/CTR stats, a daily performance chart, and a top-ads-by-CTR table.
- Google AdSense: Auto Ads snippet injection (with a strict, security-hardened snippet validator) or per-unit slot rendering via a global publisher client ID.
ads.txtmanagement from the admin panel.- Filament resource: full CRUD with conditional fields per ad type, filters, impressions, CTR, schedule highlighting.
- Events:
AdsLoading,AdClicked, andAdImpressionRecordedfor analytics/extension packages. - Optional public API:
GET/POST /api/v1/adswith key filtering. - Octane-safe: services are container-scoped, not singletons.
#Installation
composer require usamamuneerchaudhary/adment
php artisan vendor:publish --tag=adment-migrations
php artisan migrate
# optional
php artisan vendor:publish --tag=adment-config
php artisan vendor:publish --tag=adment-views
Register the plugin in your panel provider:
use Usamamuneerchaudhary\Adment\AdmentPlugin;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugin(AdmentPlugin::make());
// ->plugin(AdmentPlugin::make()->settingsPage(false)) to hide settings
// ->plugin(AdmentPlugin::make()->analyticsPage(false)) to hide analytics
}
#Registering locations
In config/adment.php:
'locations' => [
'not_set' => 'Not set',
'homepage-banner' => 'Homepage banner',
'sidebar' => 'Sidebar',
],
Or at runtime (e.g. in a service provider or theme boot):
use Usamamuneerchaudhary\Adment\Facades\Ads;
Ads::registerLocation('footer', 'Footer')
->registerLocation('post-content', 'Below post content');
#Displaying ads
Blade components (recommended):
{{-- One weighted-random published, in-window, targeted ad from a location --}}
<x-adment::display location="sidebar" />
{{-- All matching ads in a location, ordered by weight --}}
<x-adment::display location="sidebar" :single="false" />
{{-- A specific ad by key, with passthrough attributes --}}
<x-adment::display ad-key="HOMEPAGEBNNR" class="my-4" />
Facade / service:
Ads::display('sidebar'); // string of HTML
Ads::displayAds('HOMEPAGEBNNR'); // ?string
Ads::locationHasAds('sidebar'); // bool — for conditional layouts
Ads::getAd('HOMEPAGEBNNR'); // ?Ad model
Ads are loaded once per request and memoized; Ads::load(force: true) refreshes.
For impression beacons to work on public pages, include a CSRF meta tag in your layout:
<meta name="csrf-token" content="{{ csrf_token() }}">
#Weighted rotation & scheduling
- Weight — the Filament Weight field maps to the
ordercolumn. Values are treated as a minimum of1when picking a single ad for a location. - Starts at — leave empty to start immediately; future values keep the ad out of rotation until then.
- Ends at — required for custom ads; past values mark the ad expired.
AdSense units ignore schedule dates and never expire.
#Geo & device targeting
On each custom ad you can set:
- Countries — ISO-3166 alpha-2 tags (e.g.
US,GB). Empty = all countries. - Devices — desktop / tablet / mobile checkboxes. Empty = all devices.
Country is resolved from request headers, in order:
- Configured
adment.targeting.country_resolvercallable (if set) CF-IPCountryCloudFront-Viewer-CountryX-Country-Code
When an ad has country targeting and the country cannot be resolved, the ad is excluded by default (unknown_country_behavior = exclude_restricted).
// config/adment.php
'targeting' => [
'country_resolver' => fn (\Illuminate\Http\Request $request): ?string => /* ... */,
'unknown_country_behavior' => 'exclude_restricted',
],
#Media types
Custom ads support:
| Media type | Rendered as |
|---|---|
| Image | Responsive <picture> (desktop / tablet / mobile) |
| GIF | <img> with srcset fallbacks |
| Video | <video autoplay muted loop playsinline> (mp4 / webm) |
Upload accepted types are enforced in Filament based on the selected media type.
#Impression tracking & CTR
Custom creatives wrap in a beacon container. When ~50% of the ad is visible, a one-shot POST to /ai-{sha1}/{key} records an impression (lifetime impressions + today's ad_daily_stats row) and dispatches AdImpressionRecorded.
CTR is clicks / impressions × 100. Open Ad analytics in the Marketing nav group for:
- Period totals (impressions, clicks, CTR, active ads)
- Daily impressions & clicks chart
- Top ads by CTR (respects
analytics.min_impressions_for_ctr_ranking)
#AdSense
Configure in Ads settings in the panel (mode: Disabled / Auto Ads / Ad units), then add to your public layout:
<head>
...
<x-adment::adsense-head />
</head>
<body>
...
<x-adment::adsense-foot />
</body>
- Auto Ads: paste the exact snippet from AdSense. It's validated hard: a single empty async
<script>frompagead2.googlesyndication.comwith aca-pub-{16 digits}client — inline JS,eval,document.write, and data URLs are rejected. - Ad units: set your
ca-pub-…client ID, then create ads of type Google AdSense unit with a slot ID. Units render as<ins class="adsbygoogle">and never expire. Impressions for AdSense units are not tracked by Adment (AdSense handles its own metrics).
#Click tracking
Custom ads with a destination URL render links through /ac-{sha1(key.id)}/{key}. The handler verifies the hash (hash_equals), refuses non-HTTP(S) destinations, increments clicked and today's daily click stat without model events or timestamp bumps, dispatches AdClicked, and redirects. A legacy /ads-click/{key} route is included.
#Public API (optional)
Enable in config ('api' => ['enabled' => true]):
GET /api/v1/ads
GET /api/v1/ads?keys[]=HOMEPAGEBNNR&keys[]=SIDEBAR00001
Returns published, in-window ads ordered by weight, with tracked link URLs; the raw destination is never exposed.
#Extending
- Custom model: point
adment.models.adat your subclass (add tenancy scopes, relations, etc.). - Analytics: listen to
AdClicked,AdImpressionRecorded(ad, referer, user agent), andAdsLoading. - Rendering: publish and override the views, or swap the whole
ManagesAdsbinding. - Country detection: set
adment.targeting.country_resolverto a callable that returns an ISO country code.
#Architecture
Blade component / Facade
│
▼
AdsManager (scoped service, per-request memoized)
│ filterDisplayable(): published + schedule window
│ filterTargeted(): country + device rules
│ WeightedAdSelector (when single=true)
▼
partials/ad-display.blade.php
├── adsense-slot.blade.php (<ins class="adsbygoogle">)
└── custom-ad.blade.php (image / gif / video + tracked click URL)
└── viewport beacon ── POST /ai-… ── AdImpressionController
AdClickController ── hash check ── recordClick() ── AdClicked ── redirect()->away()
AdAnalyticsRecorder ── ads.impressions/clicked + ad_daily_stats
AdsSettings ── key/value table + forever-cache ── ManageAdsSettings (Filament page)
AdAnalyticsDashboard ── stats / chart / top CTR widgets
Design decisions:
- Contract-first: everything binds
ManagesAds, so a Pro/analytics package can decorate or replace the manager without touching consumers. - Scoped, not singleton, for Octane and queue-worker safety.
- No repository layer: Eloquent scopes (
displayable()) are the query API; the manager consumes collections so filtering logic exists exactly once in the model. - Viewport beacons over render-time increments so CTR reflects ads users actually saw.
- CDN headers for geo by default (zero new dependencies); swap in MaxMind or similar via
country_resolver. - Settings are dependency-free (key/value table + cache); swap for
spatie/laravel-settingsif your app standardizes on it.
#Testing
composer test # Pest
composer analyse # PHPStan (larastan, level 6)
composer format # Pint
The suite covers the model (key generation, hash, image fallbacks, scopes, schedule/CTR, click & impression counting), the manager (filtering, weighted selection, targeting, attribute passthrough, AdSense rendering, URL non-leakage), click & impression routes (hash tampering, unsafe URLs, legacy route, daily stats), Blade components (image / GIF / video), targeting & weighted selector units, the public API, the AdSense snippet validator, and the Filament resource + settings page via Livewire.
#Screenshots

#License
The author
Coder, Blogger, Tech Speaker & Web Technologies Enthusiast. Passionate about working on open-source Programming languages & Tools while utilizing my Product Development skills.
From the same author
Spatie Model States Visualizer
State-aware Kanban board and status timeline components for Filament v5. Filament Model States provides a plug and play auto-detection for enums, Spatie Model States, or plain database values. Built to sit on top of: spatie/laravel-model-status > powers the timeline (status history log) spatie/laravel-model-states > powers transition validation and column discovery on the Kanban board (optional but recommended)
Author:
Usama Muneer
Command Palette
A Spotlight/CMD+K style command palette for quick navigation and actions across Filament panels.
Author:
Usama Muneer
Notifier
A powerful notification system that handles multi-channel notifications with template management, scheduling, and real-time delivery. Built for developers who need enterprise-grade notifications without the complexity.
Author:
Usama Muneer
FilaRank Pro
SEO toolkit for Filament with live scoring, readability analysis, SERP preview, head tag rendering, redirect management, site-wide health reports and many more.
Author:
Usama Muneer
Featured Plugins
A selection of plugins curated by the Filament team
Blueprint
Filament Blueprint is a premium Laravel Boost extension that helps AI agents produce accurate, detailed implementation plans and security reports for Filament apps.
Filament
Custom Dashboards
Let your users build and share their own dashboards with a drag-and-drop interface. Define your data sources in PHP and let them do the rest.
Filament
Spotlight Pro
Browse your Filament Panel with ease. Filament Spotlight Pro adds a Spotlight like Command Palette to your Filament Panel.
Dennis Koch