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:
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:
// 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:
$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\BatchcaptureMany() 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).
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 cryptoBy donating, you agree to our donation terms.
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.