francescomalatesta/laravel-amplitude
Laravel Amplitude provides a simple integration for sending analytics events from Laravel applications to Amplitude, with a clean API for tracking user activity and custom events.
Install and wire it up
composer require francescomalatesta/laravel-amplitude
Amplitude facade are auto-discovered. On older versions, register Malatesta\LaravelAmplitude\LaravelAmplitudeServiceProvider in config/app.php and add the alias 'Amplitude' => Malatesta\LaravelAmplitude\Facades\Amplitude::class..env:php artisan vendor:publish --provider="Malatesta\LaravelAmplitude\LaravelAmplitudeServiceProvider"
AMPLITUDE_API_KEY=your_project_api_key
Look here first
config/amplitude.php — this is where the API key and any HTTP/client options live. Read it before writing any tracking code; it determines whether the package reads from env() or expects a hard-coded key.src/ directory — it is small (a service class, a facade, a provider, a config). You can read the whole thing in ten minutes, and you should, because the public surface is only a handful of methods (sendEvent, and typically an identify/user-properties call).First use case
Track a signup so you can confirm the pipe works end-to-end:
use Malatesta\LaravelAmplitude\Facades\Amplitude;
Amplitude::sendEvent(
(string) $user->id,
'Signed Up',
['plan' => $user->plan, 'source' => 'web']
);
Then open Amplitude → User Activity / Events and confirm the event landed within a minute or two. Do this before building out twenty events — you want to validate the key, the environment, and the payload shape once.
Wrap it — don't sprinkle the facade everywhere. The facade is a static call into a thin HTTP client. Put a single AnalyticsService (or a small set of typed methods) in front of it so the rest of the app doesn't know the package exists:
class Analytics
{
public function track(User $user, string $event, array $props = []): void
{
Amplitude::sendEvent((string) $user->id, $event, $props + [
'environment' => app()->environment(),
]);
}
}
This gives you one place to add queuing, sampling, environment prefixes, and error swallowing later.
Drive it from domain events, not controllers. The cleanest integration in a Laravel app is event → listener, because tracking is a side effect and shouldn't sit in the request path:
// app/Listeners/TrackUserRegistered.php
public function handle(UserRegistered $event): void
{
$this->analytics->track($event->user, 'Signed Up', [
'plan' => $event->user->plan,
]);
}
Register the listener with ShouldQueue so the HTTP call happens on a worker. This is the single highest-value pattern with this package.
Model the event taxonomy before you instrument. Amplitude rewards a consistent schema: one naming convention (Signed Up, Order Completed — pick sentence case or snake_case and stick to it), a stable set of user properties, and reserved $-prefixed properties for Amplitude's own semantics. Define the event names as constants or an enum so typos don't silently create phantom events.
Use reserved properties deliberately. Amplitude understands $revenue, $price, $quantity, $productId for revenue tracking and $insert_id for deduplication. If you're sending purchases, use these — don't invent order_total and then wonder why revenue charts are empty.
Identify users once, then track. For logged-in users, send an identify/user-properties call on login or profile update (name, plan, company, signup date) so Amplitude can segment events later. Then every event carries only the user id. Avoid stuffing the full user profile into every event payload — it bloats the request and drifts out of sync.
Guard anonymous traffic. Amplitude needs either a user_id or a device_id. Pre-login events should be tracked against a device/session id, and Amplitude's SDKs normally handle the merge on identify. This package is a server-side client with no cookie/device handling — you have to supply an identifier yourself. If pre-login tracking matters, capture a UUID in a cookie and pass it.
Test with a fake. Because everything funnels through your Analytics wrapper, you can bind a fake in tests and assert on the events your app would send, without hitting Amplitude:
$this->swap(Analytics::class, $fake = new FakeAnalytics());
// ... act ...
$fake->assertTracked('Signed Up', ['plan' => 'pro']);
Only the wrapper's own test should touch the real facade.
The client is synchronous. sendEvent performs a blocking HTTP request. Called from a controller, that's tens to hundreds of milliseconds added to every response, and a slow/hanging Amplitude endpoint becomes your outage. Always push it to a queued listener or job. If you can't queue, at minimum wrap calls in try/catch and set a short timeout on the underlying HTTP client.
One request per event, no batching. Amplitude's HTTP API has a /batch endpoint for sending many events in one call. This package (being a thin, low-activity wrapper) sends individual events. For high-volume flows — imports, backfills, bulk state changes — you'll either accept N requests or write your own batch call. Don't fire a tracking event inside a loop over 10,000 rows.
Failures are easy to miss. A wrong API key or malformed payload typically surfaces as a non-2xx response that the package may or may not throw on. Decide explicitly what you want: silent-and-logged (usual for analytics) or loud (useful in staging). Log the response body on failure at minimum, and add an environment property to every event so staging events don't pollute production dashboards — or use separate Amplitude projects per environment.
No retries, no persistence. If the HTTP call fails, the event is gone. Analytics is usually lossy-by-design and that's fine, but don't use this package as your source of truth for anything billable — keep your own database records and treat Amplitude as a read-only mirror.
Config quirks. The published config is the authority on the env var name; older releases have used variations on AMPLITUDE_API_KEY. If config:cache is in play, remember env changes require a re-cache. Also check whether the config expects the key at boot time — a missing key may fail silently rather than throwing, which is confusing to debug.
Reserved names and typing. Property keys starting with $ are reserved by Amplitude. Event and property names are case-sensitive and space-sensitive, so Signed Up and signed up are different events — a common source of "where did my data go?" Validate names in your wrapper in non-production.
Debugging flow. When an event doesn't appear: (1) dump the exact payload and confirm the user id is a non-empty string, not an int-cast-to-empty; (2) check the HTTP response status and body; (3) search Amplitude's User Activity for that user id — if the user doesn't exist, the event never arrived; (4) verify you're looking at the right Amplitude project for that API key.
Maturity risk. Twelve stars and low activity means: pin the version in composer.json, check the package's Laravel/HTTP-client compatibility against your app before upgrading either, and be prepared to fork. The extension points are all on your side of the boundary — publish the config, wrap the facade, and if you ever need batching or retries, swap the wrapper's internals for a direct call to Amplitude's HTTP API rather than waiting on the package. Its value is the facade and config wiring, not the client logic, and that's a few dozen lines to replace.
How can I help you explore Laravel packages today?