jeffersongoncalves/filament-pixel
A Filament plugin by Jefferson Gonçalves that integrates Meta Pixel tracking into Laravel admin panels. Designed for developers, it lets you configure pixel IDs, inject tracking code, and capture conversion events from Filament pages, forms, and custom components with minimal setup and flexible even...
Install and boot in five minutes.
composer require jeffersongoncalves/filament-pixel
php artisan vendor:publish --tag=pixel-settings-migrations
php artisan migrate
Register the plugin in the panel provider where you want tracking:
use JeffersonGoncalves\Filament\Pixel\PixelPlugin;
public function panel(Panel $panel): Panel
{
return $panel->plugins([
PixelPlugin::make(),
]);
}
That single call does two things: it hooks the Meta Pixel base snippet into PanelsRenderHook::HEAD_START, and it registers a ManagePixelSettings page (backed by spatie/laravel-settings) so a non-developer can paste the Pixel ID from the admin UI.
Where to look first, in order:
ManagePixelSettings page — enter your Pixel ID and toggle tracking on. Until the ID is set, the snippet injects with nothing usable in it.settingsPage(bool), and navigationGroup() per the 3.5.0 release note) against what is actually installed in vendor/.config/pixel.php equivalent) — this is where per-panel or env-driven values belong if you don't want the DB settings path.PixelServiceProvider — the render hook registration, if the script is not appearing where you expect.First real use case: drop the plugin into your production panel, set the Pixel ID from the settings page, then verify in Meta Events Manager that a PageView lands. Everything else (multi-panel, consent gating, per-environment configuration) is a refinement on top of that loop.
Script-only mode (no admin page). When the Pixel ID comes from config/env and should never be editable in the panel:
PixelPlugin::make()->settingsPage(false),
Useful for read-only production deployments and for panels where you don't want a settings surface at all. Verify the script still injects with the page disabled — the flag is named for the page, not the tracking.
Environment gating. Meta will happily ingest your local and staging traffic and skew your audience data. Register the plugin conditionally rather than relying on the settings toggle:
->plugins(array_filter([
app()->isProduction() ? PixelPlugin::make() : null,
]))
Per-panel differentiation. The plugin is registered per panel, so each panel provider can hold a different instance and a different navigation group:
// Admin panel
PixelPlugin::make()->navigationGroup('Administration')
// Tenant panel
PixelPlugin::make()->navigationGroup('Analytics')
Be aware that the settings store is a spatie settings singleton keyed by settings class — it is effectively global, not per-panel. If two panels genuinely need different Pixel IDs, drive the ID from config and skip the settings page.
Aligning navigation with existing groups. Pass the exact visible label, and register the same group on the panel so ordering and collapse behaviour are consistent:
$panel
->navigationGroups(['Analytics', 'Settings'])
->plugins([PixelPlugin::make()->navigationGroup('Analytics')]);
Use a closure only where the label genuinely varies at request time (role, tenant, locale), and keep it allocation-light since it resolves during navigation rendering:
PixelPlugin::make()->navigationGroup(
fn () => auth()->user()?->isAdmin() ? 'Administration' : 'Analytics'
)
Consent / GDPR. The base snippet is injected unconditionally on the panel head. For consent-gated deployments, the usual pattern is to keep the package's injection but wrap the fbq('init', ...) call behind your CMP's consent callback — either by overriding the pixel::script view or by not registering the plugin until consent is captured server-side.
Testing. Assert the hook output rather than the DB settings row:
$this->get('/admin')->assertSee('fbq(', false);
Prefer disabling the plugin entirely in the test panel provider so you don't assert on third-party markup in unrelated feature tests.
Extending. The settings page is a normal Filament page — subclass it if you need extra fields (conversion events, multiple pixel IDs) or a canAccess() policy. Keep the plugin as the single source of truth for navigation metadata and avoid also setting navigation on a subclass, or you will get confusing overrides.
PixelPlugin; the 3.5.0 release note refers to Plugin::make()->navigationGroup(...) and cites a filament-analytics-core 3.1 dependency. Confirm which class and which method actually exist in your installed version before writing panel code — composer show jeffersongoncalves/filament-pixel then grep the vendor/ directory.php artisan vendor:publish --tag=pixel-settings-migrations leaves the settings page throwing on read. This is the single most common first-run failure.filament/spatie-laravel-settings-plugin ^5.0. If your panel is still on Filament 3 or 4, this package is not the drop-in you're hoping for.settingsPage(false) is not "disable tracking". It controls the admin page only. To stop injection, don't register the plugin, or gate it on environment.wire:navigate transitions; if it only fires on hard loads, add a Livewire::hook('navigate') or document.addEventListener('livewire:navigated', () => fbq('track', 'PageView')) to avoid undercounting. Test by navigating three pages and watching Meta Pixel Helper.php artisan cache:clear or save through the settings repository, otherwise stale IDs persist silently.php artisan config:cache in deploys; a env() call inside the package's config reads null once config is cached if the env var isn't present at build time.fbevents.js loads, then Meta Pixel Helper for event validation. Ad blockers and Safari ITP will make local verification flaky — test in a clean profile. If nothing appears at all, check whether the page being rendered actually includes HEAD_START (error pages and non-panel routes won't).Filament::getTenant()?->...) and keep it free of DB/network calls — it re-evaluates per request, per panel.'analytics' and 'Analytics' produce two separate headings; nothing errors, the admin UX just quietly degrades. Document the canonical group name for reviewers.php artisan optimize:clear plus any Filament component cache command in your deploy pipeline — navigation and hook changes are easy to miss when cached.navigationGroup(). Any test asserting the package's translated default group label will fail once you override it; assert the new label or its position instead.How can I help you explore Laravel packages today?