PHP
The PHP core vskstudio/takt-core-php (Packagist, v0.5.0) is framework-agnostic. It covers two server-side needs: rendering the snippet for the browser runtime into your HTML, and sending server-to-server (S2S) events straight from PHP. The Laravel and Symfony bridges build on it.
composer require vskstudio/takt-core-php PHP 8.1+. SnippetRenderer needs no HTTP dependency: that line is enough to render the snippet.
The S2S client Takt, on the other hand, discovers its transport via PSR-18 / PSR-17 (php-http/discovery), and the package only declares the interfaces — you bring the implementation:
composer require vskstudio/takt-core-php guzzlehttp/guzzle
# or: composer require vskstudio/takt-core-php symfony/http-client nyholm/psr7 With no PSR-18 / PSR-17 implementation installed, new Takt(...) throws at construction time (discovery happens in the constructor, outside the fire-and-forget net).
SnippetRenderer — the browser runtime
SnippetRenderer produces the <script> block to drop into your <head>. It is configured with a readonly Options value object.
use Vskstudio\Takt\SnippetRenderer;
use Vskstudio\Takt\Options;
use Vskstudio\Takt\Mode;
$renderer = new SnippetRenderer(new Options(
domain: 'example.com',
outbound: true,
files: true,
tagged: true,
notFound: true,
fileExtensions: ['pdf', 'zip'],
));
echo $renderer->render(); // place inside <head> | Field | Default | Meaning |
|---|---|---|
domain | (required) | Site domain |
endpoint | https://taktlytics.com/api/event | Ingest endpoint — defaults to the hosted Takt origin so a bare setup works; pass /api/event for a same-origin proxy |
scriptOrigin | null | First-party origin to serve the runtime + derive the endpoint (dodge ad-blockers; endpoint wins) |
outbound | false | Track outbound links (outbound token) |
files | false | Track file downloads (downloads token) |
fileExtensions | [] | Restrict downloads to these extensions (data-downloads-ext); empty keeps the default list |
tagged | false | Track elements marked data-takt-event (tagged token) |
notFound | false | Track 404 pageviews (404 token) |
excludeLocalhost | true | Drop localhost events |
nonce | null | CSP nonce for the <script> tag |
sampleRate | null | Keep only this fraction (0–1) of hits (data-sample-rate) |
trackQuery | null | Keep the query string + hash (data-track-query); off strips them |
queryParams | [] | Allowlist of query params to keep (data-query-params) — no effect when trackQuery is on, which keeps everything |
respectDnt | null | false stops honoring Do-Not-Track (data-respect-dnt) |
enabled | null | false = kill-switch, no-op snippet (data-enabled) |
scrubUrl | null | Raw JS URL-rewrite function — requires Mode::Sdk |
exclude | [] | Path prefixes never tracked (segment-bounded) — requires Mode::Sdk |
mode | Mode::Inline | Runtime sourcing (below) |
enum Mode:
Inline— embeds the vendoredtakt.auto.jsbundle inline in the tag (zero extra requests, CSP-friendly with a nonce).Cdn— emits a<script defer src="https://cdn.jsdelivr.net/npm/@vskstudio/[email protected]/dist/takt.auto.js">loader.Asset—defer src="/takt/takt.auto.js"pointing at a self-hosted copy, or{scriptOrigin}/takt/takt.auto.jswhenscriptOriginis set.Sdk— emits a<script type="module">import{init}…;init({…})</script>that boots the full SDK. The only mode able to expressscrubUrl; the module is loaded from{scriptOrigin}/takt/takt.esm.jswhenscriptOriginis set, otherwise from jsDelivr. The package only vendorstakt.auto.js:takt.esm.jsis not shipped, it is on you to serve it at that URL, otherwise the module 404s and nothing is measured.
Autocapture is opt-in: outbound, files, tagged and notFound each add a token to a single data-auto attribute the bundled takt.auto.js reads; fileExtensions narrows which downloads count.
Advanced options
Each advanced option defaults to null (“unset” — the tracker’s own default applies); only a non-default value is rendered. sampleRate, trackQuery, queryParams, respectDnt and enabled mirror to data-* attributes in Inline, Cdn and Asset modes (snippet parity, see Configuration); in Mode::Sdk no data-* attribute is emitted, everything goes into the object passed to init(). scrubUrl is a raw JS function injected verbatim into the page: it can only be expressed in Mode::Sdk (passing it in any other mode throws) and stays dev-controlled — never build it from user input. exclude likewise lives only in the full SDK (the ≤ 1 kB minimal snippet omits it): it requires Mode::Sdk and throws in the other modes rather than silently dropping paths you believed were excluded.
new Options(
domain: 'example.com',
mode: Mode::Sdk,
sampleRate: 0.5,
queryParams: ['utm_source'],
exclude: ['/app', '/account'],
scrubUrl: '(u) => u.split("?")[0]',
); spa toggle). Do-Not-Track is honored by default; only pass respectDnt: false (or data-respect-dnt="false") if you know exactly why.Options::fromArray(array $a): self builds the options from an array and additionally accepts snake_case aliases (exclude_localhost, not_found, file_extensions, sample_rate, track_query, query_params, respect_dnt, scrub_url) — that is what makes the Laravel and Symfony bridges config-file driven. Two constants save you from retyping the URLs: Options::HOSTED_ORIGIN (https://taktlytics.com) and Options::HOSTED_ENDPOINT (https://taktlytics.com/api/event). If you assemble an inline tag yourself, SnippetRenderer::neutralizeScriptClose(string $js): string is public: it escapes the script closing tags contained in a bundle.
Takt — the server-to-server client
Takt posts events to POST /api/event with an API key carrying the events:write permission (takt_ik_ prefix), bound to the site whose domain matches domain exactly — otherwise ingest answers 401. Ideal for actions that don’t happen in the browser (payment webhook, queued job…).
The S2S client’s endpoint is a base origin, never a path: /api/event is appended on every send. So don’t copy the SnippetRenderer default from the table above, which is a full URL — it would yield https://taktlytics.com/api/event/api/event. Pass Options::HOSTED_ORIGIN, or the origin of your first-party proxy if you serve one.
use Vskstudio\Takt\Options;
use Vskstudio\Takt\Revenue;
use Vskstudio\Takt\Takt;
// Forward the real visitor's IP + User-Agent for attribution
// (adapt how you read the request to your framework):
$takt = (new Takt(
endpoint: Options::HOSTED_ORIGIN, // https://taktlytics.com
domain: 'example.com',
apiKey: $_ENV['TAKT_API_KEY'],
))->withVisitor($_SERVER['REMOTE_ADDR'] ?? null, $_SERVER['HTTP_USER_AGENT'] ?? null);
$takt->event('Signup', ['plan' => 'pro'], null, 'https://example.com/signup');
$takt->event(
'Purchase',
['plan' => 'pro'],
new Revenue(amount: '29.00', currency: 'EUR'),
'https://example.com/thanks',
);
$takt->pageview('https://example.com/thanks'); __construct(string $endpoint, string $domain, ?string $apiKey = null, ?ClientInterface $httpClient = null, ?RequestFactoryInterface $requestFactory = null, ?StreamFactoryInterface $streamFactory = null)— the last three parameters inject the PSR transport (required for testing); leftnull, they are discovered.withVisitor(?string $ip, ?string $userAgent): self— bind the real visitor before sending (returns an instance to send from)event(string $name, array $props = [], ?Revenue $revenue = null, ?string $url = null, ?string $referrer = null): void—$propsis anarray<string,scalar>whose every value is cast to a string (truegoes out as'1')pageview(?string $url = null, ?string $referrer = null): voidstrict(): self— returns a clone that throws on a transport error or on any response other than202(handy in tests)- Fire-and-forget: in default mode, transport errors and error responses alike are swallowed (analytics must never break the app). A
202means “accepted”, not “recorded”: a User-Agent classified as a bot, or an opt-out (DNT / GPC), is dropped server-side — still with a202.
$url as an absolute http(s) URL. Left out, it goes out empty, ingest answers 400 and the event is not recorded — in default mode the rejection is swallowed, the caller sees nothing.withVisitor(), S2S events attribute to the application server, not the real visitor — forward the IP / User-Agent from the current request.Revenue is a readonly value object (amount, currency): the amount is a decimal string, the currency a 3-letter uppercase code (mirroring the JS SDK). Both are validated at construction — '29,00' or 'eur' throw an InvalidArgumentException.