# Laravel

Serve the ProductLense script from your Laravel app and forward events with the HTTP client, excluded from CSRF checks, on the Scale plan.

Two routes in `routes/web.php` serve the script from your domain and, on the Scale plan, forward events. Read the [proxy overview](/docs/proxy/overview) first for what each one fixes.

## The routes

```php title="routes/web.php"
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;

Route::get('/js/a.js', function () {
    $js = Cache::remember('productlense-a-js', 3600, function () {
        $r = Http::timeout(5)->get('https://api.productlense.app/a.js');
        return $r->successful() ? $r->body() : null;
    });
    abort_if($js === null, 502);
    return response($js, 200, [
        'Content-Type' => 'application/javascript; charset=utf-8',
        'Cache-Control' => 'public, max-age=86400',
    ]);
});

Route::post('/m/api/e', function (Request $request) {
    $raw = $request->getContent();
    abort_if(strlen($raw) > 8192, 413);
    $body = json_decode($raw, true);
    abort_unless(is_array($body), 400);
    $extra = ['ip' => $request->ip(), 'ua' => (string) $request->userAgent()];
    if (isset($body['b']) && is_array($body['b'])) {
        $body['b'] = array_map(fn ($e) => is_array($e) ? array_merge($e, $extra) : $e, $body['b']);
    } else {
        $body = array_merge($body, $extra);
    }
    $r = Http::timeout(5)
        ->withToken(config('services.productlense.key'))
        ->post('https://api.productlense.app/api/v1/events', $body);
    return response()->noContent($r->status());
})->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);
```

```php title="config/services.php"
'productlense' => [
    'key' => env('PL_API_KEY'),
],
```

The events route is excluded from CSRF verification because the browser's beacon carries no token. `$request->ip()` is the visitor's address once `TrustProxies` is configured for your load balancer.

## Use it

Serve only the script on any plan:

```html
<script defer data-site="SITE_ID" data-api="https://api.productlense.app" src="/js/a.js"></script>
```

On the Scale plan, set `PL_API_KEY` in `.env` to a key with the `events` scope and change the tag to `data-api="{{ url('/m') }}"`.

## Check it works

1. Run `php artisan route:list --path=m/api` to see the route, then open `https://example.com/js/a.js`: it returns JavaScript.
2. Load a page with the developer tools open. The Network tab shows a `POST` to `api/e` with status `204`.
3. Open the Live page in the ProductLense dashboard. Your visit appears within a few seconds.
