composer require thingstelemetry/laravel-traccar. Laravel's package discovery registers the service provider and the Traccar facade automatically; no manual config/app.php edits are needed on Laravel 9+.php artisan vendor:publish --tag="laravel-traccar-config". The package reads its connection details from config/laravel-traccar.php, which in turn pulls from env (TRACCAR_BASE_URL, plus the credential keys the config references — typically email/password or a pre-issued API token)..env with the URL of your Traccar server (usually https://traccar.example.com/api or the bare host, depending on how the config composes the base URL) and your credentials. Traccar's API is not versioned, so the base URL must point at the /api root.use Thingstelemetry\LaravelTraccar\Facades\Traccar;
$devices = Traccar::devices()->all(); // collection of device DTOs
$position = Traccar::positions()->latest($devices->first()->id);
vendor/thingstelemetry/laravel-traccar/src: the Saloon connector (base URL, auth, headers, middleware), the requests directory (one class per Traccar endpoint — this is the inventory of what's actually supported), the resources that group those requests, and the data/DTO classes returned by ->dto() / ->dtoOrFail(). The requests directory is the authoritative answer to "does this package support endpoint X?" — the README usually lags behind.Wrap the facade, don't sprinkle it. Traccar calls are I/O with auth attached; put them behind your own service or repository class (e.g. TrackingService) and inject that. This keeps the package's DTOs from leaking into controllers and gives you one place to add retries, caching, and logging.
Poll-and-persist for positions. Traccar is the source of truth for live location, but you rarely want to hit it on every page view. The common pattern is a scheduled job that pulls positions for your devices since a stored watermark, normalises them into your own positions table, and lets the UI read locally:
// app/Console/Commands/SyncPositions.php
foreach (Traccar::devices()->all() as $device) {
$positions = Traccar::positions()->forDevice(
deviceId: $device->id,
from: $lastSyncedAt,
to: now(),
);
foreach ($positions as $position) {
Position::updateOrCreate(
['traccar_id' => $position->id],
[...],
);
}
}
Deduplicate on Traccar's own position id — the API can return overlapping windows, and updateOrCreate makes the job idempotent and safe to re-run.
Keep time windows tight. Every positions/report call should be bounded by deviceId and a from/to range. Unbounded queries against a busy Traccar instance are slow and can time out your worker; the package will faithfully pass the request through and you'll get a 500 or a timeout, not a helpful validation error.
Cache reference data. Devices, groups, and geofences change rarely. Wrap those calls in Cache::remember(..., now()->addMinutes(10)) keyed by the connected server. Positions are the only thing worth fetching live.
Tests: use Saloon's mock client, not HTTP fakes. Because requests go through Saloon, register a mock client on the connector in your test suite (or use Http::fake() if the connector is built on Laravel's HTTP sender) and assert against recorded fixtures. Keep one real integration test against a docker-compose Traccar instance for auth/serialisation, and mock everything else — Traccar responses are large JSON arrays and fixtures are cheap.
Custom endpoints. Traccar's surface (commands, geofences, notifications, drivers, maintenance) is broad and no package covers 100% of it. Extend the package's base request class and dispatch it through the facade's connector rather than instantiating your own Http:: client — that way auth, base URL, and middleware stay consistent. Bind your own connector subclass in a service provider if you need to swap the sender.
POST /api/session/token). Confirm which one the config drives. Basic auth is simplest but sends credentials on every request; token mode needs a refresh path, and expired sessions surface as 401s that look like bad credentials. Log the response body — Traccar returns {"message": "..."} on failures and it's more specific than the status code.RequestException (and friends) on non-2xx by default. Wrap calls in try/catch at the job/service boundary and decide explicitly whether a failed sync is retryable. Use Saloon's retry middleware only for 5xx/timeouts — retrying a 400 wastes quota.id is the internal numeric device ID; uniqueId is the IMEI/protocol identifier that operators actually recognise. Every query needs id. Store both in your own schema so support tickets referencing an IMEI don't require a lookup round-trip.CarbonImmutable and convert at the presentation layer only.200 with [], not a 404. Check for emptiness rather than relying on exceptions to detect "device not found".laravel/pint dev bumps plus saloonphp/saloon 4.0.1 → 4.3.0. No API or behaviour changes for consumers, but if you've written a custom connector or sender, re-check it against Saloon 4.3's signatures after upgrading and pin Saloon in your own composer.json if you subclass package internals. Do not pin to 4.0.* — you'll fight the package's constraint.Traccar::connection('name') style accessor if supported, otherwise new Connector() with explicit config) and inject the right one per tenant. Sharing one connector across tenants leaks credentials.json_decode the whole response into an array you then hand to Blade.How can I help you explore Laravel packages today?