Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Mixpanel Laravel Package

genealabs/laravel-mixpanel

Laravel Mixpanel integrates Mixpanel analytics into Laravel apps with a simple facade, config-based tracking, and automatic user identification. Send events, track page views, and manage distinct IDs and opt-out without boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish config.

composer require genealabs/laravel-mixpanel
php artisan vendor:publish --provider="GeneaLabs\LaravelMixpanel\LaravelMixpanelServiceProvider"

In 13.x the Mixpanel settings live in a dedicated publishable config file (PR #121), separate from where 12.x kept them. Publish it, read it top to bottom, and treat that file — not the release notes — as the source of truth for key names.

Read composer.json before anything else. The release notes advertise Laravel 13 and PHP 8.5 support but say nothing about what was dropped. composer show genealabs/laravel-mixpanel and the package's own require block tell you whether 13.x is even installable on your framework line. If you're on Laravel 11/12 and not ready to move, pin ^12.0 and defer.

Set the token in .env, then clear the config cache.

MIXPANEL_TOKEN=your-project-token
php artisan config:clear   # or config:cache if you cache in prod

Stale cached config is the single most common reason a correct-looking setup emits nothing.

First use case: track one server-side event and verify it end-to-end.

In a controller or listener for signup:

use GeneaLabs\LaravelMixpanel\Facades\LaravelMixpanel;

LaravelMixpanel::identify($user->id, [
    'email'      => $user->email,
    'plan'       => $user->plan,
    '$created'   => $user->created_at->toIso8601String(),
]);

LaravelMixpanel::track('account_created', [
    'plan'       => $user->plan,
    'source'     => 'organic',
]);

(Confirm exact method names against the facade shipped in your installed version — the wrapper tracks the underlying Mixpanel PHP SDK, and names have shifted across majors.)

Then open Mixpanel → Live View and confirm the event arrives on the expected profile. Do this before wiring a second call site; it validates token, network, identity, and config in one shot.

Where to look first, in order: the publishable config file → the service provider (to see what's bound and whether the facade alias is registered) → the facade class (for the real method surface) → the getMixpanelKey() hook if you have a B2B identity model.

Implementation Patterns

Wrap the facade in your own service — always. The facade is convenient and terrible to have scattered across 200 call sites. Define a thin Analytics/Tracker class in your app, bind it in a service provider, and let only that class touch LaravelMixpanel.

final class Analytics
{
    public function event(string $name, array $props = [], ?User $user = null): void
    {
        if (! config('laravel-mixpanel.enabled')) {
            return;
        }

        if ($user) {
            LaravelMixpanel::identify($user->getMixpanelKey(), $this->userProps($user));
        }

        LaravelMixpanel::track($name, $props + ['environment' => app()->environment()]);
    }
}

This buys you: vendor containment if Mixpanel is ever swapped, one place to enforce the taxonomy, one place to redact PII, and a trivially fakeable seam in tests.

Track from domain events and listeners, not controllers. Controllers are the wrong altitude — the same "plan upgraded" moment can be triggered by a webhook, a queue job, or an admin action. Fire a domain event (PlanWasUpgraded) and let a queued listener do the tracking. This keeps instrumentation attached to the business fact rather than to one HTTP entry point.

Respect transactions. Use ShouldQueue on the listener, or DB::afterCommit(), so a rolled-back transaction doesn't emit a phantom order_placed. Events fired inside a transaction that later fails are the most common source of inflated conversion numbers.

Adopt one naming convention and write it down. object_past_tense (invoice_paid, seat_added, onboarding_step_completed), snake_case, no version suffix. Put the list in the repo (docs/analytics.md or an enum of event names) so instrumentation is reviewable in a PR. The package gives you zero help here — this is the asset you actually own.

Use typed event-name constants. An AnalyticsEvent enum or a const-only class prevents invoice_paid vs InvoicePaid vs invoice-paid from proliferating. Reviewers can grep them; dashboards stay stable.

Implement getMixpanelKey() on your User model deliberately (new in 13.0.0).

public function getMixpanelKey(): string
{
    return $this->uuid; // or (string) $this->id, email, or workspace ID
}

For B2C, an internal immutable UUID is usually right. For B2B with seats/workspaces, decide whether reporting follows the person or the account — this is a one-way door. Whatever you choose, it must match what the client-side SDK passes as its distinct ID, or one human becomes two profiles and every cross-surface funnel breaks.

Identify early, alias the anonymous. Call identify at registration and login. If you track pre-auth anonymous activity (marketing pages, onboarding wizard steps), use alias to stitch the anonymous ID to the real one before the first identified event — not after.

Keep people/profile writes separate from event tracking. Profile writes are idempotent and safe to repeat; events are not. Don't put mutable properties (last_seen, event_count) on the profile via an event-triggered path unless you actually want last-write-wins semantics.

Blade integration is for the client snippet only. The package's Blade directive/partial emits the browser SDK boilerplate and token. Use it once in your layout, and make the JS render the same distinct ID the server passes. If you use a SPA, note that the browser SDK needs a manual page-view call per route change — the wrapper doesn't do that for you.

Testing. Never hit Mixpanel in tests. Either swap your Analytics wrapper with a fake in the container and assert on recorded events, or mock the facade. Assert names and property shapes, not just "was called" — the property contract is what dashboards depend on.

Per-environment isolation. Set MIXPANEL_TOKEN to null/empty and disable tracking outside production (or point staging at a separate Mixpanel project). Dev and CI noise in a production project is very hard to filter out of funnels retroactively.

Gotchas and Tips

Silent failure is the headline risk. A missing or null token does not throw — events are dropped and the app looks healthy. Engineering sees green; a week later the funnel chart falls off a cliff. Add a boot-time guard (a health check or a config('...token') assertion in non-local environments) and alert on event volume dropping to zero.

The 13.x config relocation is a breaking change and fails quietly. Everything that reads the old config path — application code, .env templates, CI secrets, deploy manifests, Docker envs — must be re-pointed. Republish and diff key-by-key. Then run a boot test with config:cache enabled and confirm the token resolves non-null. Do not bundle this with a Laravel major upgrade; ship the config migration on the current framework line first.

config:cache will happily serve you yesterday's token. Any deploy that changes .env must re-run php artisan config:cache. This bites in containerized deploys where the cache is baked at build time.

Server/client distinct-ID mismatch is the expensive mistake. If the server identifies by internal UUID and the browser SDK identifies by email, you get two profiles per user and the merge is manual pain. Decide the key once, document it, and verify in Live View that a single user session produces one profile.

getMixpanelKey()'s return contract matters. Confirm null handling and string coercion in the installed source. Returning null (e.g. an unset UUID on a legacy user) can degrade or drop identity for that user. Guard it.

Retries can double-count. If you dispatch tracking onto a queue, confirm whether the underlying Mixpanel SDK version supports $insert_id for idempotent ingestion. If it doesn't, a retried job will bill you twice and skew counts — either add dedupe keys upstream or accept at-least-once and reconcile.

Don't send PII in event properties. Emails, names, phone numbers, and free-text user input all leak into a third party. Pass IDs; resolve PII later in the warehouse or Mixpanel's own profile join. This is a data-governance review, not just a code review.

Cost is a product-level gotcha. Event-based pricing means a foreach over 10k rows with a track() inside is a billing incident. Never track inside a loop over large collections; batch, or aggregate at the domain level.

Debugging checklist, in order: (1) is enabled true in this environment? (2) does config('laravel-mixpanel.token') return a non-null value with config cached? (3) does the call site actually execute (add a temporary log)? (4) does Live View show it? (5) is it landing on the profile you expect? Ninety percent of "analytics is broken" resolves at step 2.

Facade alias collisions. The package registers a facade alias; if another analytics package or an older version of this one registers the same alias, you get confusing resolution failures after a deploy. Check config/app.php aliases and the provider's register() after upgrading.

Bus factor is real. 123 stars, zero dependents, a single primary maintainer. Keep every tracking call behind your own Analytics class so a future vendor swap — or an abandonment — is a one-file change rather than a codebase-wide hunt. Flag it in your dependency register, and pin a version you've smoke-tested rather than floating on ^13.0.

Extension point worth knowing: the config file is the intended customization surface, and your own wrapper class is the intended abstraction. Anything beyond that — custom consumers, endpoint overrides for EU data residency — should be checked against the underlying Mixpanel PHP SDK docs, since the Laravel wrapper is thin by design and won't document SDK-level options.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin