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

Mixpanel Bundle Laravel Package

gordalina/mixpanel-bundle

A Symfony bundle that integrates the Mixpanel PHP library into your app, providing a configured service for tracking events, identifying users, and sending analytics data. Includes dependency-injection setup for your project token and easy injection into controllers and services.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the honest framing: this is a Symfony bundle. There is no Laravel service provider, no facade, no config/mixpanel.php. If you are in a Laravel codebase you will not composer require this — you'll take the underlying mixpanel/mixpanel-php SDK and bind it yourself (see below). If you're maintaining a Symfony app, or a Laravel dev dropped into one, the bundle saves you exactly one thing: wiring the token and consumer options into the container.

Version floor moved with 7.0.0 — check this before anything else. 7.x requires Symfony 8 or Symfony ^7.4 (7.0–7.3 are no longer supported). If you're on Symfony 6.x or an early 7.0–7.3 minor, you stay on 6.0.0 and none of this release is available to you. Run composer why-not gordalina/mixpanel-bundle 7.0.0 first; the answer is usually your Symfony constraint, not the bundle.

Minimal steps (Symfony 7.4+/8):

  1. composer require gordalina/mixpanel-bundle — pin the version; with ~14 stars and 0 dependents, you want a lockfile entry you can reason about later.
  2. Register the bundle: config/bundles.php → Gordalina\MixpanelBundle\GordalinaMixpanelBundle::class => ['all' => true].
  3. Set the token in config/packages/gordalina_mixpanel.yaml from an env var, never inline:
    gordalina_mixpanel:
        token: '%env(MIXPANEL_TOKEN)%'
    
  4. Inject the service into a controller or listener. The bundle exposes a configured Mixpanel instance — confirm the service id before you guess it; read Resources/config/services.xml (it's short). Whether it lands as mixpanel or a namespaced id varies by version, and bin/console debug:container mixpanel settles it in five seconds.
  5. First use case: track a meaningful conversion event, not a pageview.
public function register(Request $request, Mixpanel $mp): Response
{
    $user = $this->createUser($request);

    $mp->identify((string) $user->id);
    $mp->track('Account Created', [
        'plan' => $user->plan,
        'source' => $request->query->get('utm_source'),
    ]);

    return $this->redirectToRoute('dashboard');
}

Where to look first, in order: the 7.0.0 CHANGELOG (it now links an "Upgrading from 6.x" guide with Rector configs and a sed one-liner — read it even on a fresh install, because the Attribute rename shows up in any config you copy from a blog post), then the README (thin), then DependencyInjection/Configuration.php — that file, not the docs, is the truth about which keys exist. Then services.xml for the id. Anything beyond the token and consumer is probably not configurable via the bundle and you'll be configuring it in code.

Laravel equivalent (what you'd actually ship in a Laravel app):

// app/Providers/AppServiceProvider.php
$this->app->singleton(\Mixpanel\Mixpanel::class, fn () => new \Mixpanel\Mixpanel(
    config('services.mixpanel.token'),
    ['consumer' => 'curl', 'debug' => app()->environment('local')],
));

Wrap it behind your own Analytics interface so the SDK doesn't leak into twenty controllers.

Implementation Patterns

Centralize the event taxonomy. You will otherwise end up with Account Created, account_created, and AccountCreated in the same project within a month, and none of your funnels will join. A single AnalyticsEvents class of constants is the cheapest insurance available:

final class Events
{
    public const ACCOUNT_CREATED = 'Account Created';
    public const SUBSCRIPTION_STARTED = 'Subscription Started';
}

Identify once per request, at auth, not per event. The idiomatic flow is: middleware resolves the user, calls identify($distinctId), and downstream code just calls track() without repeating the id. If you're passing distinct_id into every call, you have a bug waiting where one call forgets and events land on an anonymous profile.

Keep tracking out of the request path. The curl consumer blocks. For signup flows you can live with it; for a loop over 500 rows you cannot. Two options that work with this bundle without forking it:

  • consumer: file — writes to a temp file; flush on a scheduled command or at request shutdown. Fast, but you own the flush and the failure mode (silent data loss on ephemeral containers).
  • Your own queued job — serialize the event, push to the queue, call track() inside the worker. More code, far better observability and retry semantics. This is what I'd choose for anything high-volume.

Environment segregation. Local dev should not pollute the production project's data. Set debug: true locally so the SDK logs payloads instead of sending them — the log line is also the best debugging tool you have. Point staging at a separate Mixpanel project token entirely; there is no "dry run" concept in Mixpanel once an event is in.

Enrich in one place. Rather than tagging app_version, plan, and $current_url at every call site, add a thin decorator around the injected service that merges a base property bag before delegating. It's ~20 lines and it means retroactive property changes don't require touching every event.

People vs events. $mp->track() records events; $mp->people->set() writes profile properties. They are separate pipelines with separate quotas and separate debugging paths. If a property needs to be on a profile ($email, $name, $created), use people->set — do not assume it lands there because you passed it to track.

Test with an interface, not the SDK. Bind a NullAnalytics or an in-memory spy in your test container and assert on the recorded event array. Never let a test hit the real consumer; with debug: true you get logs but tests still shouldn't depend on a third-party client object's internals.

Gotchas and Tips

The bundle is not the hard part; the SDK version is. ~14 stars and zero dependents means the bundle is a thin, rarely-exercised wrapper. Budget your risk review for the transitive mixpanel/mixpanel-php version, which has had real breaking changes across majors (API shape, consumer config, TLS/socket behavior). Read the lockfile diff after require, not the bundle's changelog.

7.0.0 renamed the Annotation namespace to Attribute and stopped reading Doctrine annotations entirely. If any of your config or extension code still uses the old annotation classes — or a vendor copy-paste does — it will be silently ignored rather than erroring, which is the worst failure mode. The changelog ships two migration paths (still-on-annotations, and already-on-attributes) plus ready-to-paste Rector configs and a sed one-liner; use them rather than hand-editing.

The bundled Rector set was removed in 7.0.0. If your CI relied on it (rector.php importing the bundle's set list), that config will now fail to resolve. Either pin the rules locally or drop the reference — but do it as part of the upgrade, not after a red pipeline.

symfony/yaml is now an explicit dependency. Functionally good (the bundle always needed it, it was just transitive), but it adds a constraint to your solver that could previously be satisfied by your own pin. If you're on an older symfony/yaml for a reason, composer update will surface it now.

Service id and config key guessing is the #1 time sink. debug:container and DependencyInjection/Configuration.php, always. If you copy a config snippet from a blog post written three years ago, expect an "unrecognized option" exception — Symfony validates config keys strictly, which is a gift here.

You cannot "just use it" in Laravel. I've seen this attempted. There's no Symfony\Component\HttpKernel\Bundle\Bundle compatibility shim that works in a Laravel container; even registering the bundle class manually gives you nothing, because Laravel never runs build(). Use the SDK. (7.0.0's Symfony 8 support changes nothing about this.)

Reserved property names. Anything starting with $ is Mixpanel-reserved ($email, $name, $created, $os, $ip). Sending $something_custom silently behaves differently than you expect, and some names are dropped. Prefix your own properties — app_plan rather than plan if it collides with a Super Property.

Backdating and idempotency. Server-side events get the receipt time. If you're importing historical data or retrying a queued job, set 'time' => $timestamp on the event and a stable $insert_id to make Mixpanel dedupe retries. Without $insert_id, your queue's retry semantics become duplicated revenue.

Client IP leakage. Server-side track() does not automatically capture IP. If you want geo, pass 'ip' => $request->getClientIp(); if you explicitly want none (GDPR-sensitive flows), pass 'ip' => 0. Getting this wrong in either direction is a compliance conversation later.

Silent failure is the default. The SDK's error handling is a callback, and the bundle likely doesn't wire one. Register error_callback in your own construction (or decorate the service) to log failures — otherwise a bad token silently drops 100% of your events and you find out three weeks later when the dashboard is flat.

Extension points, ranked by durability: (1) your own adapter interface around the SDK — survives a bundle replacement, (2) a Symfony service decorator on the bundle's service id — cheap and clean, (3) forking the bundle — don't; with this adoption level you'll be maintaining a fork for a wrapper that was 60 lines of XML.

Evaluate whether you need the bundle at all. For a single token and a default consumer, the Symfony DI config is:

services:
    Mixpanel\Mixpanel:
        arguments:
            $token: '%env(MIXPANEL_TOKEN)%'
            $options: { consumer: curl, debug: '%kernel.debug%' }

That is the entire value proposition of this package. If you're on Symfony and the wrapper is stale relative to the SDK — or, as is the case if you're on Symfony 6.x/7.0–7.3, you simply cannot take 7.0.0 — defining the service yourself is strictly lower risk than depending on an unmaintained indirection.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource