Laravel
The Laravel bridge vskstudio/takt-laravel wires the PHP core into the container: a Blade directive for the snippet, a facade for S2S and a publishable config. 0.6.x, PHP 8.1+, Laravel 10 / 11 / 12.
composer require vskstudio/takt-laravel The TaktServiceProvider is auto-discovered: SnippetRenderer (snippet rendering) and Takt (S2S client) are bound in the container from config, and the IP / User-Agent are forwarded to the S2S client whenever an HTTP request is available.
This page is the option reference. For the end-to-end integration and its main trap — attributing S2S sends made outside a visitor request, from a queued job, an Artisan command or a webhook — see the guide cookieless analytics in a Laravel app.
Configuration
php artisan vendor:publish --tag=takt-config takt-config is the package’s only publishable tag. config/takt.php reads environment variables:
TAKT_DOMAIN=example.com
TAKT_API_KEY=tk_… # required only for S2S
TAKT_MODE=inline # inline (default) | cdn | asset | sdk
TAKT_ENDPOINT=https://taktlytics.com/api/event # see "Endpoint and first-party origin"
TAKT_SCRIPT_ORIGIN=https://analytics.example.com # first-party origin serving the tracker
TAKT_EXCLUDE_LOCALHOST=false # default: true — nothing is measured from localhost
TAKT_NONCE=… # CSP nonce for the script tag
TAKT_OUTBOUND=true
TAKT_FILES=true
TAKT_TAGGED=true
TAKT_NOT_FOUND=true
TAKT_FILE_EXTENSIONS=pdf,zip,docx
# Advanced options — leave empty to keep the tracker defaults
TAKT_SAMPLE_RATE=0.5 # send only a fraction (0–1) of hits
TAKT_TRACK_QUERY=true # keep the query string + hash (default: stripped)
TAKT_QUERY_PARAMS=utm_source,utm_medium # allowlist when track_query is off
TAKT_EXCLUDE=/app,/account # path prefixes never tracked — requires TAKT_MODE=sdk
TAKT_RESPECT_DNT=false # stop honoring Do-Not-Track
TAKT_ENABLED=false # kill-switch: no-op snippet
TAKT_REDACT_ROUTES=/verify/{token} # sensitive routes, requires TAKT_MODE=sdk
TAKT_ROUTE_TEMPLATES=true # every page as its route template, requires TAKT_MODE=sdk TAKT_EXCLUDE_LOCALHOST defaults to true: in local development nothing is measured until you set it to false.
Render modes
TAKT_MODE picks the source of the browser runtime:
TAKT_MODE | Output |
|---|---|
inline (default) | The vendored takt.auto.js bundle is embedded in an inline <script> tag: no extra request, but inline JS on every page — under a strict CSP, set TAKT_NONCE |
cdn | <script defer src="https://cdn.jsdelivr.net/npm/@vskstudio/[email protected]/dist/takt.auto.js"> |
asset | <script defer src="/takt/takt.auto.js"> — a self-hosted copy, prefixed with TAKT_SCRIPT_ORIGIN when that origin is set |
sdk | <script type="module">import{init}…;init({…})</script> — the full SDK, loaded from jsDelivr or from /takt/takt.esm.js on TAKT_SCRIPT_ORIGIN |
TAKT_OUTBOUND, TAKT_FILES, TAKT_TAGGED and TAKT_NOT_FOUND each add a token (outbound, downloads, tagged, 404) to the single data-auto attribute the bundle reads, in inline, cdn and asset modes; in sdk mode they are boolean keys (outbound, files, tagged, notFound) of the object passed to init().
public/. For TAKT_MODE=asset, copy vendor/vskstudio/takt-core-php/resources/takt.auto.js to public/takt/takt.auto.js — for instance from a post-update-cmd script in your composer.json, so the copy follows package updates.Options restricted to sdk mode
TAKT_SCRUB_URL — a raw JS function rewriting URLs, injected verbatim into the page and to be kept dev-controlled: never build it from user input — and TAKT_EXCLUDE only exist in the full SDK. Set in any other mode they are not ignored: building the SnippetRenderer throws an InvalidArgumentException, i.e. a 500 on every page that renders the snippet.
TAKT_MODE=sdk
TAKT_SCRUB_URL="(u) => u.split('#')[0]"
TAKT_EXCLUDE=/app,/account TAKT_REDACT_ROUTES and TAKT_ROUTE_TEMPLATES follow the same rule, see Route redaction.
Endpoint and first-party origin
TAKT_ENDPOINT feeds two consumers: the snippet rendered by @takt, which needs the full collect URL, and the Takt facade, which needs the origin it appends /api/event to. The package therefore accepts both forms and normalises them for each, so these two settings are equivalent:
TAKT_ENDPOINT=https://taktlytics.com
TAKT_ENDPOINT=https://taktlytics.com/api/event TAKT_ENDPOINT | The snippet POSTs to | The facade POSTs to |
|---|---|---|
https://taktlytics.com (package default) | https://taktlytics.com/api/event | https://taktlytics.com/api/event |
https://taktlytics.com/api/event | https://taktlytics.com/api/event | https://taktlytics.com/api/event |
/collect (same-origin proxy) | /collect on your domain | relative path: no send goes through |
A value carrying any other path is taken verbatim as the collect URL: that is the case for a first-party proxy served from your own domain. A relative path only suits the snippet; if you also send server-side events, keep an absolute URL.
TAKT_SCRIPT_ORIGIN is the first-party origin you proxy through to Takt to dodge ad-blockers. It is rendered as data-script-origin and serves the tracker file in asset and sdk modes. With the hosted endpoint (in either form above), no endpoint is rendered and the tracker derives the ingest path from this origin, followed by /api/event. With any other value, data-endpoint is rendered and wins over the origin, which then only loads the file.
@takt Blade directive
Drop @takt into your layout’s <head> to render the snippet:
<head>
<meta charset="utf-8">
@takt
</head> The directive takes no argument: it compiles a fixed call to SnippetRenderer::render(), and any expression written between parentheses is discarded without error. The snippet is therefore rendered from config alone.
For a different render, typically a per-request CSP nonce, rebind the renderer before the view is rendered, from a middleware. With TAKT_ROUTE_TEMPLATES, carry over the template of the current route:
use Illuminate\Support\Facades\App;
use Vskstudio\Takt\Laravel\RouteTemplate;
use Vskstudio\Takt\Options;
use Vskstudio\Takt\SnippetRenderer;
App::bind(SnippetRenderer::class, fn () => new SnippetRenderer(Options::fromArray([
'nonce' => $nonce,
'scriptOrigin' => config('takt.script_origin'),
'route_template' => RouteTemplate::of(request()),
] + config('takt')))); Options::fromArray() reads the other keys in snake_case, like the config (route_template included); scriptOrigin is the only one expected in camelCase.
Takt facade (server-to-server)
use Vskstudio\Takt\Laravel\Facades\Takt;
use Vskstudio\Takt\Revenue;
Takt::event('Signup', ['plan' => 'pro']);
Takt::event('Purchase', ['plan' => 'pro'], new Revenue(amount: '29', currency: 'EUR'));
Takt::pageview('https://example.com/thanks'); event(string $name, array $props = [], ?Revenue $revenue = null, ?string $url = null, ?string $referrer = null, ?string $route = null): voidpageview(?string $url = null, ?string $referrer = null, ?string $route = null): voidwithRoute(string|\Closure|null $route): Takt: default route template, see Route redaction- Without
$urlthe event is attached to the site home page (https://followed bydomain, then/). - Fire-and-forget: a
202means success; by default transport errors and non-202statuses are swallowed, since analytics must never break the app. No exception surfaces — confirm ingestion from the dashboard.Takt::strict()returns an instance that throws on non-202: keep it for tests.
The facade resolves the container’s Takt service, bound per request. The IP and User-Agent come from the current request via $request->ip() and $request->userAgent().
App\Http\Middleware\TrustProxies on Laravel 10, trustProxies() in bootstrap/app.php on Laravel 11 / 12): without it Takt receives the proxy's IP and your whole S2S audience is attributed to a handful of infrastructure addresses. Outside an HTTP request — queued job, Artisan command, webhook — there is no visitor to forward: pass the real IP and User-Agent yourself with withVisitor().Route redaction
URL scrubbing removes the query and the hash, but the path is sent as is: /verify/abc123 leaks the token. Since 0.6.0 (PHP core 0.6), two settings replace real paths with the route pattern, in the @takt snippet and in the Takt facade alike (see Route redaction). In the snippet they require TAKT_MODE=sdk: in any other mode, building the SnippetRenderer throws an InvalidArgumentException, hence a 500 on every page rendering the snippet, since the minimal snippet cannot redact.
| Config key | Environment variable | Default | Role |
|---|---|---|---|
redact_routes | TAKT_REDACT_ROUTES (comma-separated list) | [] | Sensitive routes sent as their pattern |
route_templates | TAKT_ROUTE_TEMPLATES | false | Sends every page as its Laravel route template |
redact_routes
TAKT_MODE=sdk
TAKT_REDACT_ROUTES=/verify/{token},/reset/{code} Or straight in config/takt.php:
'mode' => 'sdk',
'redact_routes' => ['/verify/{token}', '/reset/{code}'], A path matching one of the patterns is sent as the pattern, every other path keeps its real value. Patterns accept the Laravel syntax ({token}, {page?}) as well as [p], [[p]], [...p], (group), :p, :p?, * and ** (details on the PHP core page).
The browser SDK does not read braces: {token} is handed to it as [token]. A browser pageview therefore reports /verify/[token], while an event sent through the facade reports /verify/{token}. Write /verify/[token] if both must land on the same row.
route_templates
TAKT_MODE=sdk
TAKT_ROUTE_TEMPLATES=true Every page is sent as its route template: /users/42 becomes /users/{id}. The package reads the template of the route that matched the current request ($request->route()->uri()), renders it into the @takt snippet and uses it as the default route of every Takt::pageview() and Takt::event() sent during that request. This template is not translated to brackets: browser and server both report /users/{id}. A request with no matched route keeps its real path, still subject to redact_routes.
route_templates suits a private app; on a public site it merges all your articles into a single row of the Pages report.
Explicit template and RouteTemplate
Any server-side call can pick its own template, which wins over the default:
use Vskstudio\Takt\Laravel\Facades\Takt;
Takt::event('Verified', url: url()->current(), route: '/verify/{token}');
Takt::withRoute('/invoices/{id}')->pageview(url()->current()); Takt::withRoute() returns an instance whose calls are all sent under that template. It also accepts a closure, resolved at send time.
Vskstudio\Takt\Laravel\RouteTemplate::of(?Request $request): ?string returns the route template of a request (/users/{id}), or null when no route matched. It is what the package uses; call it when you rebind SnippetRenderer yourself (route_template key of Options::fromArray(), see the Blade directive) or Takt (withRoute()), otherwise those bindings lose the template.
SnippetRenderer binding is scoped instead of singleton: under a long-lived worker such as Octane, the rendered snippet follows the route of the current request instead of keeping the one of the first request served.