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

Symfony Utm Bundle Laravel Package

tourze/symfony-utm-bundle

A Symfony bundle for UTM parameter tracking that helps capture campaign query parameters and persist attribution data across requests, making it easier to integrate marketing analytics and conversion tracking into Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

tourze/symfony-utm-bundle is a Symfony bundle, not a Laravel package — that distinction drives everything below. Its job is capturing UTM query parameters (utm_source, utm_medium, utm_campaign, utm_term, utm_content) on inbound requests and making them available later in the request/session lifecycle for attribution.

composer require tourze/symfony-utm-bundle

In a Symfony app: register the bundle in config/bundles.php (there's no Flex recipe, so it's manual), then check vendor/tourze/symfony-utm-bundle/DependencyInjection/Configuration.php for the config tree and add config/packages/tourze_utm.yaml.

In a Laravel app: Laravel's package discovery only looks at extra.laravel.providers. A class extending Symfony's Bundle will not be auto-registered, and the bundle's kernel.request listener will never fire under Laravel's HTTP kernel. Treat this as a reference implementation: read the subscriber class and port the ~40 lines into a middleware, or (if you're on a hybrid/Symfony-based stack) wire the listener manually in a service provider.

Where to look first, in order:

  1. The event subscriber/listener on kernel.request — that's where capture happens.
  2. DependencyInjection/Configuration.php — the only real documentation of available knobs.
  3. Whatever storage service holds the values (session, cookie, or a request attribute).

First use case: a marketing landing page at /pricing?utm_source=newsletter&utm_campaign=spring24. Capture on entry, persist, then read the values when the visitor converts (form submit, signup, order) and write them onto the lead/order record. That single flow is 90% of real-world usage.

Implementation Patterns

Capture → persist → read at conversion. Keep these three phases explicit in your code. Capture belongs in middleware/subscriber (before your controller), persistence belongs in a dedicated service, and reading happens in your form handler or model observer.

First-touch vs. last-touch. Decide this deliberately, because it changes your schema:

  • Last-touch: overwrite stored values on every request carrying UTMs. Simplest, but internal navigation with UTM-tagged links pollutes attribution.
  • First-touch: only write if nothing is stored yet, then clear on conversion. Better for lead-gen; requires an explicit reset.

Middleware ordering (Laravel). Register the capture middleware in the web group before StartSession, Authenticate, and anything that issues redirects — otherwise a login redirect or locale redirect swallows the query string. In Symfony, give the listener a priority high enough to beat firewall/redirect listeners.

Laravel middleware shape (porting the bundle's behaviour):

public function handle($request, Closure $next)
{
    if ($request->hasAny(['utm_source', 'utm_medium', 'utm_campaign'])) {
        if (! $request->session()->has('utm')) {
            $request->session()->put('utm', $request->only([
                'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
            ]));
        }
    }
    return $next($request);
}

Carry UTMs across multi-step forms. Session survives same-site steps but not a redirect to an external payment domain. Stash the array into a hidden field or a meta JSON column when the flow starts.

Don't fork the bundle to change storage. If the values are held by a named service, decorate it (Symfony's decorates: / a Laravel binding swap) rather than vendoring the class — you'll want to upgrade when 1.1.x patches land.

Test it like a behaviour, not a class. Assert on the session/cookie after hitting a URL with params, and assert that a second request without params doesn't clear it:

$this->get('/?utm_source=google&utm_campaign=spring')
     ->assertSessionHas('utm.utm_source', 'google');

Gotchas and Tips

  • The Symfony/Laravel mismatch is the #1 trap. No config publishing, no config/utm.php, no facades. If you're in Laravel, budget a half-day to port rather than hours debugging why nothing fires.
  • 1.1.0's release notes are empty (just a compare link). A minor bump implies additive-only, but read the diff at compare/1.0.1...1.1.0 before bumping production — small single-maintainer bundles occasionally slip behaviour changes into minor versions. Pin ^1.0.1 first, test, then move.
  • Redirects eat the query string. Capture on the inbound request even if the response is a 302. This is the most common "why is attribution empty" bug.
  • Full-page cache / CDN. If the landing page is cached at the edge, your server-side capture may never see the query string. Either exclude UTM-bearing requests from cache (Vary/bypass) or capture client-side and post it back.
  • Cookie domain for subdomains. Marketing site on www. and checkout on shop. means you need domain: .example.com or the attribution vanishes at the most important step.
  • Normalize on capture. utm_source=Google vs google will fragment your reports forever. Lowercase and trim; whitelist known values if you can.
  • Ignore self-referrals. Links in your own emails/footers often carry UTM tags; filter by referrer host or you'll attribute organic traffic to yourself.
  • Consent. UTM params in a URL are generally fine; persisting them in a long-lived cookie may need consent under GDPR/ePrivacy. Session-scoped storage or a short TTL cookie is the low-friction choice.
  • Debugging: if nothing is captured, first confirm the listener/middleware is actually registered (debug:event-dispatcher in Symfony, route:list -v for middleware in Laravel), then check event priority — silent no-ops are almost always registration or ordering, not parsing.
  • Normalize your schema early. Store UTMs as five nullable string columns (or one JSON attribution column) on every convertible model before you ship, so historical reporting isn't a backfill project later.
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.
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
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect