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

Matomo Bundle Laravel Package

nucleos/matomo-bundle

Symfony bundle for integrating Matomo analytics into your application. It offers configuration for Matomo tracking, Twig helpers to embed the tracking code, and services that simplify adding privacy-friendly web analytics to Symfony projects without boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and its HTTP transport requirements:

composer require nucleos/matomo-bundle

The bundle is not in Symfony Flex's recipe set (low adoption), so register it manually if it wasn't auto-registered:

// config/bundles.php
return [
    // ...
    Nucleos\MatomoBundle\NucleosMatomoBundle::class => ['all' => true],
];

In a Laravel app you usually skip the bundle registration and just consume the underlying nucleos/matomo-client service through the container — check whether the bundle's service definitions are worth pulling in over wiring the client yourself.

Configure the connection to your Matomo instance. The root key is nucleos_matomo; the keys below are the ones you'll be editing first — confirm exact names against the bundle's DependencyInjection/Configuration.php since the package moves fast:

# config/packages/nucleos_matomo.yaml
nucleos_matomo:
    host: '%env(MATOMO_HOST)%'        # e.g. https://matomo.example.com
    site_id: '%env(int:MATOMO_SITE_ID)%'
    token_auth: '%env(MATOMO_TOKEN)%' # only required for server-side / API calls
MATOMO_HOST=https://matomo.example.com
MATOMO_SITE_ID=1
MATOMO_TOKEN=your-api-token

Where to look first: read src/DependencyInjection/Configuration.php and src/Resources/config/*.xml. These two files tell you the real config keys and the actual service IDs you'll inject — far more reliable than the README.

First use case: render tracking on a page. The bundle's headline feature is a block service, so the fastest win is embedding the tracking block in your layout:

{# templates/base.html.twig, before </body> #}
{{ sonata_block_render({ type: 'nucleos_matomo.tracking' }) }}

If you're not using a block renderer, inject the underlying client service directly in a controller/listener and call its tracking method (trackPageView, trackEvent, etc.) — run bin/console debug:container matomo to find the exact service ID.

Version note (4.4.0, 2026-05-02): this release is dependency-only — Symfony 8 support, PHPUnit 13, and frontend dependency bumps. No API, config-key, or service-ID changes, so anything written against 4.3.x carries over untouched. What does change is the install surface: the bundle's Symfony component constraints now reach ^8.0, which is wider than the Symfony components a given Laravel release pins. If composer require fails on a Symfony constraint, run composer why-not symfony/http-client (or symfony/options-resolver) before assuming the bundle is incompatible — it's usually a transitive pinned component.

Implementation Patterns

Inject the client, don't hand-roll HTTP. The bundle wraps nucleos/matomo-client; treat it as the single integration point. Constructor-inject the client interface and call typed methods:

final class OrderTracker
{
    public function __construct(private MatomoClient $matomo) {}

    public function trackPurchase(Order $order): void
    {
        $this->matomo
            ->setUserId($order->getCustomerId())
            ->trackEvent('checkout', 'purchase', $order->getNumber(), $order->getTotal());
    }
}

Server-side tracking for things JS can't see. Use the client in domain-event listeners or Messenger handlers for refunds, subscription changes, and back-office actions. Wrap the call so a Matomo outage never breaks the domain flow:

try {
    $this->matomo->trackEvent(...);
} catch (\Throwable $e) {
    $this->logger->warning('Matomo tracking failed', ['exception' => $e]);
}

Front-end block for page views. Keep the block in your base layout and drive per-page data through block settings rather than duplicating the snippet:

{{ sonata_block_render({ type: 'nucleos_matomo.tracking' }, {
    'site_id': app.request.attributes.get('matomo_site_id', 1),
    'goal': 'newsletter_signup'
}) }}

Multi-site / multi-tenant. If the bundle exposes named clients or a factory, configure one client per Matomo site and select by request (host, locale, tenant). Inject a small resolver service rather than spreading if statements through controllers.

Test with a stub transport. Because the client speaks PSR-18/HTTPlug, you can bind a mock HTTP client in the test container and assert on the recorded requests. This keeps trackEvent calls out of your real Matomo instance in CI:

# config/packages/test/nucleos_matomo.yaml
services:
    matomo.http_client:
        class: Symfony\Component\HttpClient\MockHttpClient

Gate rendering, don't gate the service. Wrap the block and the Twig call in {{ matomo_enabled ? ... : '' }} style checks driven by consent state and kernel.debug, so enabling/disabling is a config concern, not a code change.

Gotchas and Tips

  • Never ship token_auth to the browser. It belongs to the server-side client only. Keep it in an env var and expose no route that reflects it back. If you see it in a rendered page, you're using the block where the client was meant.
  • Dev/test will hit your real Matomo. There is no built-in when@dev guard — add nucleos_matomo: { enabled: false } (or short-circuit in a decorator) for dev and test, or you'll pollute analytics with localhost page views.
  • CSP will block the injected script. If you run a strict Content-Security-Policy, the Matomo tracker snippet needs a nonce or a script-src entry for your Matomo host. The block does not add nonces for you; wire it into your CSP listener.
  • Consent handling is out of scope. The bundle renders/records unconditionally. You must gate it behind your cookie-consent mechanism (e.g. only render the block once consent is granted) or you'll be non-compliant in the EU.
  • Trailing slashes and subdirectory installs. host should be the base URL with no trailing slash, and Matomo installed under a path (e.g. /analytics) must be included in host. Mismatches produce silent no-ops rather than errors.
  • Missing HTTP client = confusing container error. If no PSR-18 implementation is installed, the client service fails to build. composer require symfony/http-client (or nyholm/psr7 + a client) resolves it.
  • enabled: false vs. omitted config. Disabling a site can leave a null/placeholder service that throws \LogicException when injected. Prefer if ($this->matomo->isEnabled()) style guards, or bind a no-op decorator in dev.
  • Don't chase the bundle's dev tooling. 4.4.0 moves the repo to PHPUnit 13 / symfony/phpunit-bridge v8; those are require-dev constraints for the bundle's own CI and do not affect your app's test stack. Keep your project on whatever test runner your Laravel version ships with, and don't bump phpunit to satisfy the bundle.
  • Symfony 8 compatibility doesn't mean Symfony 8 components. Since 4.4.0 the constraint range is wider, but installing Symfony 8 packages into a Laravel app that pins Symfony 7 classes will produce mixed-version runtime bugs long before Composer complains. Let Laravel's framework decide the component versions; only override with composer require --with-all-dependencies if you've verified the full graph.
  • Extension points: decorate the client service (or MatomoClientInterface) to add logging, buffering, or a NullClient for environments; decorate the block service to alter render array data. Both are cleaner than forking the bundle.
  • Low adoption (6 stars) means thin docs. Budget time for reading the service XML. Pin the version in composer.json — the 2026 release cadence suggests breaking changes between minors are possible, and there are zero dependents to surface regressions. 4.4.0 itself is safe to take: dependency-only, no runtime behaviour changed.
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.
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections