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

Piwik Bundle Laravel Package

webfactory/piwik-bundle

Symfony bundle that integrates Matomo (formerly Piwik) analytics into your app, offering Twig helpers and services to embed tracking code and query stats without manual setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what this is first: webfactory/piwik-bundle is a Symfony bundle that wires the Matomo (formerly Piwik) PHP tracker into a Symfony app and exposes a Twig helper for the JS tracking snippet. There is no Laravel service provider, no auto-discovery, no Blade directive. In a pure Laravel app you have two paths:

  1. Use the underlying tracker library directly — composer require matomo/matomo-php-tracker and bind it in a service provider. This is what most Laravel devs actually want.
  2. Install this bundle anyway in a hybrid/legacy app that already boots a Symfony kernel, or to reuse its config shape and Twig template.
composer require webfactory/piwik-bundle:^3.5

Where to look first: composer.json (PHP floor and symfony/* constraints — see Gotchas), then the bundle's DependencyInjection/Configuration.php for the exact config keys, then the Twig template it ships for the snippet markup.

First use case — pageview tracking in a layout. Symfony side:

# config/packages/webfactory_piwik.yaml
webfactory_piwik:
    host: '%env(MATOMO_HOST)%'
    site_id: '%env(int:MATOMO_SITE_ID)%'
{# templates/base.html.twig, immediately before </body> #}
{{ piwik_code() }}

Laravel equivalent — mirror the same config keys so the two apps stay symmetrical:

// config/services.php
'matomo' => [
    'host'    => env('MATOMO_HOST'),
    'site_id' => (int) env('MATOMO_SITE_ID'),
],
// app/Providers/AppServiceProvider.php
$this->app->singleton(\MatomoTracker::class, fn () => new \MatomoTracker(
    (int) config('services.matomo.site_id'),
    config('services.matomo.host'),
));
{{-- resources/views/layouts/app.blade.php --}}
@include('analytics.matomo')

Then run one page load and confirm a hit lands in Matomo's real-time visitor log before wiring anything else.

Implementation Patterns

Snippet in the layout, per-route overrides on top. Put the base snippet in your single layout, and disable/override it for admin, legal, and logged-in-user routes. In Laravel: a view composer that sets analytics.enabled, or @unless(request()->is('admin/*')).

Server-side event and goal tracking. The value of this package over a plain JS snippet is that it ships a tracker you can call from PHP. Inject the tracker (in Symfony, type-hint the tracker service — confirm the class name with bin/console debug:container | grep -i piwik; in Laravel, type-hint your bound MatomoTracker) and record server-side facts a browser can't see reliably:

public function handle(OrderPlaced $event): void
{
    $this->tracker->doTrackEvent('Checkout', 'Purchase', $event->order->reference, $event->order->total);
    $this->tracker->doTrackGoal(config('services.matomo.goal_purchase'), $event->order->total);
}

Wrap tracker calls in queued jobs. The tracker makes a synchronous HTTP call. Doing it inline adds latency to the user's request and can throw on timeout. Dispatch a TrackMatomoEvent job on a low-priority queue with $tries = 3 and retryUntil.

Set the user ID and custom dimensions once, near the top of the request, so pageview, event, and goal calls all attribute correctly:

$this->tracker->setUserId((string) auth()->id());
$this->tracker->setCustomDimension(1, auth()->user()?->plan ?? 'guest');

Consent and cookieless mode. Default to disableCookies() until consent is captured; only then render the cookie-setting snippet. Keep the decision in one place (a config flag or a MatomoConsent service) rather than branching inside templates.

Test with a fake, not against the network. Bind a NullTracker/recording fake in your test suite and assert calls, or Http::fake() if you route through Laravel's HTTP client. Never let the test suite hit a real Matomo instance.

Keep the config env-driven and shared between any Symfony and Laravel halves of the estate (MATOMO_HOST, MATOMO_SITE_ID), so a single deploy can't silently point at the wrong site.

Gotchas and Tips

3.5.0's headline change is the Symfony 8 allowance — and it comes with a requirements bump. The release also "cleans up package structure" and bumps requirements. Before upgrading, inspect the PHP floor:

composer show webfactory/piwik-bundle --all | grep -E 'requires|versions'
composer why-not webfactory/piwik-bundle 3.5.0

Symfony 8 implies PHP 8.4. If you're on Laravel 9/10 with PHP 8.0–8.1, stay on 3.4.x. The widened ^8.0 constraint is irrelevant to a Laravel app — it will not unlock anything for you, but the raised floor can break the upgrade.

Dependency resolution friction. Laravel pins symfony/* components per major version. A bundle that allows a different Symfony range can still conflict with those pins. Resolve with composer update webfactory/piwik-bundle --with-dependencies -W and check composer why-not rather than fighting the lockfile by hand.

It is not a Laravel package. composer require gives you classes, not a booted bundle. The piwik_code() Twig function does not exist in Blade — the most common failure is installing this and then wondering why nothing renders. Use the tracker library directly, or write a ten-line Blade partial with the standard Matomo _paq snippet.

Synchronous HTTP by default. The tracker blocks. Set a short timeout, prefer queued jobs, and consider Matomo's bulk tracking if you emit many events per request. A slow Matomo instance should never slow your checkout.

Ad blockers. Client-side snippet hits are blocked for a meaningful share of visitors, so numbers run low. Server-side tracking recovers the events but loses referrer, screen, and JS-only dimensions — don't mix the two into one site ID without documenting the discontinuity.

CSP. The snippet is inline JS. With a nonce-based Content-Security-Policy you must add the nonce (override the template, or self-host the snippet and pass the nonce through), otherwise tracking silently dies and the console is your only clue.

Caching. In Symfony, config changes require bin/console cache:clear. In Laravel, php artisan config:cache will freeze MATOMO_* values — a "wrong site ID in production" bug is usually a stale config cache.

Debugging undercounts. Check the browser network tab for the matomo.php request first, then Matomo's real-time log, then the system check page. Only after all three look clean is it a code problem.

Extension points. Override the shipped Twig template (or Blade partial) for markup; decorate/wrap the tracker service to add custom dimensions or filter out internal traffic; and if you fork, note that 3.5.0 moved the test suite to PHPUnit 10 — old phpunit.xml schemas need updating, and CI now runs composer-require-checker, which will flag any dependency you use but don't declare.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource