---
title: "Capturing events — Plausible for Laravel | Roundly"
description: "Send pageviews and custom events server-side — synchronously, on the queue, or as a batch — with the visitor Plausible needs."
url: https://roundly-consulting.com/open-source/docs/plausible-for-laravel/events
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Plausible for Laravel](https://roundly-consulting.com/open-source/docs/plausible-for-laravel.md)

# Capturing events

Track conversions server-side — even where front-end JavaScript can’t reach: API calls, webhooks, queued jobs. Plausible needs the visitor’s user-agent and IP to count unique visitors, so provide them with fromRequest() or setVisitor() before capturing:

```php
use RoundlyConsulting\Plausible\DataTransferObjects\Revenue;
use RoundlyConsulting\Plausible\Facades\Plausible;
use RoundlyConsulting\Plausible\Requests\Events\CaptureEventRequest;

// Lift the visitor from the current request, send a pageview in one call
Plausible::events()->fromRequest(request())->pageview('https://my-site.com/blog/x');

// A custom event (goal) in one call — the site defaults to plausible.site
Plausible::events()->fromRequest(request())->event(
    'Signup',
    url: 'https://my-site.com/register',
    props: ['plan' => 'pro'],
    revenue: Revenue::of('49.00', 'EUR'),   // optional
    site: null,                              // optional override
);

// A custom event, fired on the queue so it never blocks the response
Plausible::events()
    ->fromRequest(request())
    ->captureQueued(CaptureEventRequest::make(
        site: 'my-site.com',
        url: 'https://my-site.com/signup',
        name: 'Signup',
        props: ['plan' => 'pro'],
    ));

// The explicit, synchronous form still works
$response = Plausible::events()
    ->setVisitor(userAgent: request()->userAgent(), ipAddress: request()->ip())
    ->capture(CaptureEventRequest::make(
        site: 'my-site.com',
        url: 'https://my-site.com/blog/some-post',
        referrer: 'https://affiliate-site.com',
        props: ['author' => 'jane'],
    ));
```

fromRequest() lifts the user-agent and IP from the request; setVisitor() takes them explicitly — for example, ones you stored with the original visit:

```php
// In a webhook handler or a job there is no browser request in scope —
// pass the identifiers you stored with the original visit
Plausible::events()
    ->setVisitor($order->user_agent, $order->ip_address)
    ->capture(CaptureEventRequest::make(
        site: 'my-site.com',
        url: 'https://my-site.com/checkout/complete',
        name: 'Payment Settled',
    ));
```

The visitor is sent as the User-Agent and X-Forwarded-For headers. Capturing without one throws RoundlyConsulting\\Plausible\\Exceptions\\UniqueVisitorIdentifierRequired. No API key is needed: the Events API is unauthenticated, and the package never sends your key there — only PLAUSIBLE\_URL is required.

## CaptureEventRequest

| Parameter | Default | Purpose |
| --- | --- | --- |
| `site` | — | The site (domain) the event belongs to. Required. |
| `url` | — | The page URL the event happened on. Required. |
| `name` | pageview | Event name — pageview, or any custom goal name. |
| `referrer` | null | Referrer URL, sent only when set. |
| `props` | \[\] | Custom event properties, sent only when not empty. |
| `revenue` | null | A Revenue DTO — see Revenue. |
| `interactive` | true | Set false to send the event as non-interactive. |

## Pageviews and queued capture

- pageview($url, $site = null) — captures a pageview and falls back to the configured default site; throws MissingConfiguration when neither is available.
- event($name, url:, props:, revenue:, site:, referrer:, interactive:) — the custom-event twin of pageview(): same default-site fallback, returns the Response.
- capture($request) — sends synchronously and returns the Response.
- captureQueued($request) — dispatches RoundlyConsulting\\Plausible\\Jobs\\CaptureEventJob with the visitor stored as plain strings, so the job stays serialisable and the response is never blocked.

## Batch capture

For bulk or historical imports, send many events at once — synchronously, or as a single queued job batch:

```php
$events = [
    CaptureEventRequest::make(site: 'my-site.com', url: 'https://my-site.com/a', name: 'import'),
    CaptureEventRequest::make(site: 'my-site.com', url: 'https://my-site.com/b', name: 'import'),
];

Plausible::events()->fromRequest(request())->captureMany($events);        // list<Response>
$batch = Plausible::events()->fromRequest(request())->captureManyQueued($events); // Illuminate\Bus\Batch
```

captureMany() sends each event in order and returns the responses. captureManyQueued() dispatches one CaptureEventJob per event as a Laravel job batch named plausible:capture, so your app needs job batching set up (the job\_batches table). The jobs are batchable: a job in a cancelled batch sends nothing.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/plausible-for-laravel.md)
