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
| Key | Default | Role |
|---|---|---|
domain | '' | Site domain (data-domain attribute, and the domain carried by every S2S event) |
endpoint | https://taktlytics.com | Ingest target — two distinct contracts, see below |
script_origin | null | First-party origin serving the runtime and deriving the endpoint from it (dodges ad-blockers); endpoint wins over it |
api_key | null | scope=ingest API key — used by the S2S client only |
mode | inline | Runtime source: inline, cdn, asset or sdk |
outbound | false | Tracks outbound links (outbound token) |
files | false | Tracks downloads (downloads token) |
tagged | false | Tracks elements marked with data-takt-event (tagged token) |
not_found | false | Tracks 404 pages (404 token) |
file_extensions | [] | Restricts which downloads are counted; empty keeps the tracker’s built-in list |
exclude_localhost | true | Drops hits fired from localhost — the most common reason nothing shows up in dev |
nonce | null | CSP nonce set on the <script> tag (required in mode: inline under a strict CSP) |
sample_rate | null | Sends only a fraction (0–1) of hits |
track_query | null | Keeps 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_dnt | null | false stops honoring Do-Not-Track |
enabled | null | false = kill-switch, no-op snippet |
scrub_url | null | Raw 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 vendoredtakt.auto.jsbundle 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-endpointand the tracker POSTs to it: it needs a full ingest URL; Taktservice (S2S) — the value is a base origin, with/api/eventappended on each send.
endpoint value | The snippet POSTs to | The S2S service POSTs to |
|---|---|---|
https://taktlytics.com (default) | https://taktlytics.com | https://taktlytics.com/api/event |
https://taktlytics.com/api/event | https://taktlytics.com/api/event | https://taktlytics.com/api/event/api/event |
/api/event (same-origin proxy) | /api/event on your domain | relative path: no send goes through |
No single value satisfies both usages. Set it according to what you use:
- snippet only —
endpoint: '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.comdefault; - both in the same application — set
endpointfor 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.
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.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.