# Laravel Installation Guide

> Install Virent Human Analytics and AI Crawler Analytics on Laravel.

Canonical: https://virent.app/docs/installation/laravel
Last updated: 2026-08-26

This is the canonical Virent installation guide for Laravel applications.

### Install with your coding agent

Paste this prompt into Claude Code, Codex, Cursor or another coding agent.

```text
Install Virent analytics on my Laravel website.

Read the canonical guide first and follow its code exactly:
https://virent.app/docs/installation/laravel.md

Install two separate things:
1. Human Analytics (browser): Install the browser script in the shared Blade layout used by public pages.
2. AI Crawler Analytics (server): Create a Laravel middleware class and append it to the web middleware group.

Values I will give you (they are in Virent under Settings > Connection):
- Publishable browser key, starts with vha_pk_. Safe in browser code.
- Site ID (VIRENT_SITE_ID) and server secret (VIRENT_INGEST_SECRET, starts with vha_sk_). Server only.
Ask me for them. Never invent a key or use a masked preview such as vha_sk_••••.

Human Analytics best practices:
- Add the script once to the shared Blade layout, usually resources/views/layouts/app.blade.php.
- Use the publishable key in browser markup only.
- Keep the script out of admin-only or partial templates unless those are the only public pages.

AI Crawler Analytics best practices:
- Add Virent to Laravel's web middleware group so normal web page requests are covered.
- Use the Http facade with server-only .env values for crawler telemetry.
- Use terminable middleware so telemetry is sent after the application response and includes the final status code.

Rules:
- Keep VIRENT_INGEST_SECRET in server environment variables. Never put it in client code, public env variables (NEXT_PUBLIC_, VITE_, PUBLIC_) or committed files.
- Exclude API routes, static assets, images, favicons, robots.txt and sitemaps from crawler capture.
- If the site sends a Content-Security-Policy, allow https://app.virent.app in script-src and connect-src.
- Do not change unrelated code.

When you finish, list the files you changed and how I verify each path:
- Run php artisan view:clear if needed.
- Open a public page and inspect the head.
- Refresh Human Analytics status.
- Run php artisan optimize:clear after adding env values.
- Request a page with curl -A "GPTBot".
- Refresh AI Analytics status.
```

## Research Notes

- Laravel 13.x configures middleware in `bootstrap/app.php`.
- Middleware can run before and after `$next($request)`, which makes final response status available.
- The HTTP facade is the recommended built-in client for outbound server requests.

Primary references:

- https://laravel.com/docs/13.x/middleware
- https://laravel.com/docs/13.x/http-client

## Human Analytics

Add the script to the shared Blade layout.

```blade
<!-- resources/views/layouts/app.blade.php -->
<head>
  <meta charset="utf-8">
  <script
    defer
    data-write-key="vha_pk_..."
    src="https://app.virent.app/js/script.js"
  ></script>
  @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
```

Best practices:

- Install in the layout used by public web routes.
- Use only the publishable browser key in Blade output.
- Add nonce attributes if the app's CSP requires them.
- Clear compiled views if the deployed page does not update.

Verification:

```sh
php artisan view:clear
```

- Open a public page and inspect the head.
- Check Network for the Virent script and pageview request.
- Refresh Human Analytics status.

Troubleshooting:

- If some pages are not tracked, find additional Blade layouts.
- If CSP blocks Virent, update Laravel or proxy headers.
- If cached views hide the change, run `php artisan view:clear`.

### First-party (recommended)

Ad blockers and privacy tools often block third-party analytics hosts, which silently drops visits. Relay the script and events through your own domain under `/_vr`, and load the script with `data-api="/_vr"` so events go to your domain too. The relay must forward the visitor's IP and country in `x-virent-forwarded-for` and `x-virent-forwarded-country`, with your website's secret key (`vha_sk_…`) in `x-virent-proxy-key`; without the key Virent ignores the forwarded headers and sees your server's network instead of your visitors'. Relay only `/_vr/js/script.js` and `/_vr/v1/*`, and never forward cookies or authorization headers. See [First-party analytics](/docs/installation/first-party) for how Virent trusts these headers.

With nginx, add these locations to the site's server block:

```text
location = /_vr/js/script.js {
  proxy_pass https://app.virent.app/js/script.js;
  proxy_set_header Host app.virent.app;
  proxy_ssl_server_name on;
  proxy_set_header Cookie "";
}

location /_vr/v1/ {
  proxy_pass https://app.virent.app/v1/;
  proxy_set_header Host app.virent.app;
  proxy_ssl_server_name on;
  proxy_set_header Cookie "";
  proxy_set_header Authorization "";
  proxy_set_header X-Virent-Forwarded-For $remote_addr;
  # Your website's secret key: Virent trusts the forwarded IP only with it.
  proxy_set_header X-Virent-Proxy-Key "vha_sk_...";
}
```

On Cloudflare, deploy a Worker on the `example.com/_vr/*` route that copies only `accept-language`, `content-type`, `origin` and `user-agent`, sets `x-virent-forwarded-for` from `cf-connecting-ip`, `x-virent-forwarded-country` from `request.cf.country` and `x-virent-proxy-key` from a Worker secret holding your website's secret key, and fetches the same path without `/_vr` from `https://app.virent.app`. The dashboard install panel has the full Worker.

Then load the script from your domain:

```html
<script
  defer
  data-api="/_vr"
  data-write-key="vha_pk_..."
  src="/_vr/js/script.js"
></script>
```

## AI Analytics

Set server values:

```env
VIRENT_SITE_ID=site_...
VIRENT_INGEST_SECRET=vha_sk_...
VIRENT_INGEST_ENDPOINT=https://app.virent.app/v1/ingest/bot
```

Create middleware:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Symfony\Component\HttpFoundation\Response;

class TrackVirentCrawler
{
	public function handle(Request $request, Closure $next): Response
	{
		return $next($request);
	}

	public function terminate(Request $request, Response $response): void
	{
		$userAgent = $request->userAgent() ?? '';

        if (
            preg_match('/\.(?:png|jpg|jpeg|gif|svg|ico|webp|css|js|map|txt|xml)$/i', $request->path()) ||
            ! preg_match('/GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|claudebot|Claude-Web|Claude-User|Claude-SearchBot|PerplexityBot|PerplexityBot\/|Perplexity-User|Perplexity-User\/|Google-CloudVertexBot|DeepSeekBot|DeepSeekSpider|CCBot|Bytespider|meta-externalagent|meta-webindexer|meta-externalfetcher|Amazonbot|DuckAssistBot|YouBot/i', $userAgent)
        ) {
			return;
		}

		try {
			Http::withHeaders([
				'x-api-key' => env('VIRENT_INGEST_SECRET'),
			])->timeout(2)->post(env('VIRENT_INGEST_ENDPOINT'), [
				'siteId' => env('VIRENT_SITE_ID'),
				'url' => $request->fullUrl(),
				'host' => $request->getHost(),
				'path' => '/' . ltrim($request->path(), '/'),
				'method' => $request->method(),
				'referer' => $request->headers->get('referer'),
				'userAgent' => $userAgent,
				'statusCode' => $response->getStatusCode(),
				'requestId' => (string) str()->uuid(),
				'timestamp' => now()->toIso8601String(),
			]);
		} catch (\Throwable) {
			// Analytics must never change the application response.
		}
    }
}
```

Register it:

```php
// bootstrap/app.php
use App\Http\Middleware\TrackVirentCrawler;
use Illuminate\Foundation\Configuration\Middleware;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->web(append: [
        TrackVirentCrawler::class,
    ]);
})
```

Best practices:

- Append to Laravel's `web` middleware group for public page routes.
- Do not include API routes unless AI crawler traffic to APIs should be tracked.
- Use terminable middleware so telemetry is sent after the application response with its final status.
- Clear config cache after env changes.

Verification:

```sh
php artisan optimize:clear
curl -A "GPTBot" https://your-domain.com/
```

- Confirm Laravel sends an outbound request to Virent.
- Refresh AI Analytics status.

Troubleshooting:

- If middleware does not run, confirm it is appended in `bootstrap/app.php`.
- If env values are missing, clear config cache.
- If high-traffic pages slow down, lower timeout values or queue telemetry.
