takt

Symfony

The Symfony bridge vskstudio/takt-symfony exposes the PHP core as a bundle: a Twig function for the snippet and an autowired Takt service for S2S. Supports Symfony 6.4 / 7.x, PHP 8.1+.

composer require vskstudio/takt-symfony

The package ships no Flex recipe: register the bundle yourself.

// config/bundles.php
return [
    // …
    Vskstudio\Takt\Symfony\TaktBundle::class => ['all' => true],
];

TaktBundle then registers its services from semantic config: the SnippetRenderer behind the Twig function, and the Takt S2S client. The latter is RequestStack-aware: it forwards the current request’s IP and User-Agent for attribution.

This page is the option reference. For the end-to-end integration and its traps — snippet placement under Turbo, return controller vs webhook for conversions — see the guide cookieless analytics in a Symfony app.

Configuration

config/packages/takt.yaml:

takt:
    domain: 'example.com'
    endpoint: 'https://taktlytics.com/api/event'   # see "The endpoint key" below
    api_key: '%env(TAKT_API_KEY)%'                 # S2S only — omit the key otherwise
    mode: inline
    outbound: true
    files: true
    tagged: true
    not_found: true
    file_extensions: ['pdf', 'zip']

A %env(...)% must match a declared variable: without it, the container throws EnvNotFoundException on every page that renders the snippet.

# .env
TAKT_API_KEY=tk_…

Key reference

KeyDefaultRole
domain''Site domain (data-domain attribute, and the domain carried by every S2S event)
endpointhttps://taktlytics.comIngest target — two distinct contracts, see below
script_originnullFirst-party origin serving the runtime and deriving the endpoint from it (dodges ad-blockers); endpoint wins over it
api_keynullscope=ingest API key — used by the S2S client only
modeinlineRuntime source: inline, cdn, asset or sdk
outboundfalseTracks outbound links (outbound token)
filesfalseTracks downloads (downloads token)
taggedfalseTracks elements marked with data-takt-event (tagged token)
not_foundfalseTracks 404 pages (404 token)
file_extensions[]Restricts which downloads are counted; empty keeps the tracker’s built-in list
exclude_localhosttrueDrops hits fired from localhost — the most common reason nothing shows up in dev
noncenullCSP nonce set on the <script> tag (required in mode: inline under a strict CSP)
sample_ratenullSends only a fraction (0–1) of hits
track_querynullKeeps the query string + hash (default: stripped)
query_params[]Allowlist of params kept when track_query is off
exclude[]Path prefixes never tracked — requires mode: sdk
respect_dntnullfalse stops honoring Do-Not-Track
enablednullfalse = kill-switch, no-op snippet
scrub_urlnullRaw JS function rewriting URLs — requires mode: sdk

The last five advanced options default to null (“unset”: the tracker default applies); only set them for a non-default value — sample_rate: 0.5 throws away half your traffic and enabled: false shuts tracking off entirely.

The modes:

  • inline — embeds the vendored takt.auto.js bundle in the tag (no extra request).
  • cdn — loader <script defer src="https://cdn.jsdelivr.net/npm/@vskstudio/[email protected]/dist/takt.auto.js">.
  • asset — loader pointing at /takt/takt.auto.js, a copy you host yourself (see the callout at the end of this page).
  • sdk<script type="module"> booting the full SDK.

mode is not validated: an unknown value silently falls back to inline.

The endpoint key

The same key feeds two consumers that expect different shapes:

  • Twig snippet — the value is rendered verbatim as data-endpoint and the tracker POSTs to it: it needs a full ingest URL;
  • Takt service (S2S) — the value is a base origin, with /api/event appended on each send.
endpoint valueThe snippet POSTs toThe S2S service POSTs to
https://taktlytics.com (default)https://taktlytics.comhttps://taktlytics.com/api/event
https://taktlytics.com/api/eventhttps://taktlytics.com/api/eventhttps://taktlytics.com/api/event/api/event
/api/event (same-origin proxy)/api/event on your domainrelative path: no send goes through

No single value satisfies both usages. Set it according to what you use:

  • snippet onlyendpoint: 'https://taktlytics.com/api/event' for hosted collection, or '/api/event' if you proxy ingestion first-party from your own domain;
  • S2S only — keep the https://taktlytics.com default;
  • both in the same application — set endpoint for the snippet and redeclare the S2S client with the bare origin:
# config/services.yaml
services:
    Vskstudio\Takt\Takt:
        factory: ['Vskstudio\Takt\Symfony\TaktFactory', 'create']
        arguments:
            - 'https://taktlytics.com'
            - 'example.com'
            - '%env(TAKT_API_KEY)%'
            - '@request_stack'

script_origin only derives the snippet endpoint when endpoint is exactly https://taktlytics.com/api/event: the tracker then POSTs to {script_origin}/api/event. With any other value, endpoint is emitted as data-endpoint and wins.

mode: sdk

mode: sdk loads the SDK as an ES module. It is the only mode able to express scrub_url, a raw JS function rewriting URLs that is injected verbatim into the page, and exclude, the path prefixes never tracked. The minimal snippet carries neither: setting them in another mode throws at render time instead of silently dropping them. Keep scrub_url dev-controlled: never build it from user input.

takt:
    mode: sdk
    exclude: ['/app', '/account']
    scrub_url: "(u) => u.split('#')[0]"

takt() Twig function

Render the snippet in your base template’s <head>:

<head>
    <meta charset="utf-8">
    {{ takt() }}
</head>

takt() takes no arguments: autocapture (outbound, files, tagged, not_found) and every other option are set in config/packages/takt.yaml only. An argument passed from a template is ignored without error — there is no per-template override.

Autowired Takt service (server-to-server)

Just type-hint Vskstudio\Takt\Takt — autowiring does the rest:

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Vskstudio\Takt\Revenue;
use Vskstudio\Takt\Takt;

final class CheckoutController
{
    public function __construct(private readonly Takt $takt) {}

    public function success(): Response
    {
        $this->takt->event('Purchase', ['plan' => 'pro'], new Revenue(amount: '29', currency: 'EUR'));

        return new Response('Thanks');
    }
}

Sending is fire-and-forget: a 202 counts as success, and transport errors are swallowed so analytics never breaks the request. $this->takt->strict() returns an instance that throws on any status other than 202 (useful in tests). pageview() and withVisitor() are covered on the PHP core page.

The service derives the visitor IP through Request::getClientIp(), which only honors X-Forwarded-For when framework.trusted_proxies is configured. Behind an undeclared CDN or reverse proxy, every S2S event is attributed to the proxy's IP.
In mode: asset the loader points at /takt/takt.auto.js, but the bundle publishes no asset: copy vendor/vskstudio/takt-core-php/resources/takt.auto.js to public/takt/takt.auto.js yourself. In mode: sdk with script_origin, the module is loaded from /takt/takt.esm.js on that origin — that file is not vendored either, so you must serve it.