beziworld / laravel-unifi
Laravel client for UniFi Network: official API and classic controller API for sites, devices, clients, hotspot guests, vouchers, Wi-Fi and events.
Requires
- php: ^8.3
- ext-json: *
- guzzlehttp/guzzle: ^7.8
- illuminate/cache: ^12.0|^13.0
- illuminate/collections: ^12.0|^13.0
- illuminate/console: ^12.0|^13.0
- illuminate/contracts: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.7|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
- phpstan/phpstan-strict-rules: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-10 19:23:12 UTC
README
Laravel client for the UniFi Network application by Ubiquiti: sites, devices, connected clients, hotspot guest authorization, vouchers, networks, Wi-Fi broadcasts, events and alarms, for one or many controllers.
Features
- The official UniFi Network API (API key,
/proxy/network/integration/v1/...), locally or through the Ubiquiti cloud connector (api.ui.com) - The classic controller API (session login) for what the official API does not offer: events, alarms, blocking and reconnecting clients, WLAN passphrases, controllers older than Network 9
- UniFi OS consoles (Cloud Gateway, UDM, Cloud Key Gen2+) and self-hosted legacy controllers: login path, cookie,
CSRF token and
/proxy/networkprefix handled automatically - Multiple named controllers plus
using([...])for controllers stored in your database - Self-signed TLS per connection:
verify_ssltrue, false or a CA bundle path - Hotspot vouchers with schema validation,
forStay()for vouchers valid until a hotel check-out - Typed DTOs (
Site,Device,ConnectedClient,Voucher,Network,WifiBroadcast,Event, ...) that keep the raw payload, UTCCarbonImmutabledates - Pagination with
Pageobjects and lazy iteration over all pages, aFilterbuilder for the official filter syntax - Classic API sessions cached in Laravel's cache and renewed once on HTTP 401
- Reads retried on connection errors and HTTP 5xx, writes never retried, HTTP 429 as
RateLimitExceptionwithretryAfter() php artisan unifi:checkandphp artisan unifi:vouchers- PHP 8.3+, Laravel 12 and 13, PHPStan level 8, works with
Http::fake()
Why this package
Packagist (November 2026) lists art-of-wifi/unifi-api-client with about 449k downloads: a framework-agnostic,
curl-based client for the classic API only, which keeps cookies in the PHP session and returns stdClass or
false. Its official-API sibling art-of-wifi/unifi-network-application-api-client (Saloon based) and a few
small clients exist, but there is no Laravel package. This package adds:
- both APIs behind one Laravel facade, with config files, named connections and runtime credentials
- Laravel's HTTP client, so
Http::fake()andHttp::assertSent()work in your tests - typed exceptions instead of
falseandtrigger_error() - sessions in the Laravel cache (shared by queue workers) instead of
$_SESSION - hotel-oriented helpers: vouchers for a stay, guest lists, voucher clean-up, console voucher generation
Installation
composer require beziworld/laravel-unifi
php artisan vendor:publish --tag=unifi-config # optional
UNIFI_URL=https://192.168.1.1 # UniFi OS console, or https://unifi.example.com:8443 for a legacy controller UNIFI_API_KEY= # Network > Settings > Control Plane > Integrations UNIFI_SITE=default # short site name; its UUID is looked up once UNIFI_SITE_ID= # optional: site UUID, skips the lookup UNIFI_VERIFY_SSL=true # false or a CA bundle path for the self-signed console certificate # Only for the classic API (events, alarms, legacy controllers): UNIFI_USERNAME=api-user # a local account without two-factor authentication UNIFI_PASSWORD= UNIFI_CONTROLLER=auto # auto, unifi_os or legacy # Through the cloud connector instead of the local address (Site Manager API key): UNIFI_CONSOLE_ID=
php artisan unifi:check
Several controllers (for example one per hotel) go into config/unifi.php:
'connections' => [ 'default' => [...], 'hotel-krakow' => [ 'url' => 'https://10.20.0.1', 'api_key' => env('UNIFI_KRAKOW_API_KEY'), 'site' => 'default', 'verify_ssl' => storage_path('certs/unifi-krakow-ca.pem'), ], ],
Usage
Guest Wi-Fi vouchers for a hotel stay
use Beziworld\Unifi\Facades\Unifi; // One voucher for two devices, valid until check-out (e.g. from a PMS reservation) $voucher = Unifi::connection('hotel-krakow')->vouchers()->forStay( name: 'Room 204 / Smith', checkout: $reservation->departure->setTime(11, 0), devices: 2, ); $voucher->formattedCode(); // "48614-09510" // Batches with limits use Beziworld\Unifi\Data\VoucherRequest; $vouchers = Unifi::vouchers()->create(new VoucherRequest( name: 'Conference day', timeLimitMinutes: 600, count: 50, authorizedGuestLimit: 1, dataUsageLimitMBytes: 2048, rxRateLimitKbps: 20000, txRateLimitKbps: 5000, )); Unifi::vouchers()->findByCode('48614-09510'); Unifi::vouchers()->purgeExpired(); // returns the number of deleted vouchers
php artisan unifi:vouchers "Room 204" --minutes=4320 --guests=2 --connection=hotel-krakow
Guest authorization (hotspot)
$client = Unifi::clients()->findByMac('aa:bb:cc:dd:ee:01'); $result = Unifi::clients()->authorizeGuest($client->id, minutes: 1440, megabytes: 2048, downKbps: 10000, upKbps: 2000); $result->granted->expiresAt; // CarbonImmutable (UTC) Unifi::clients()->unauthorizeGuest($client->id); foreach (Unifi::clients()->guests() as $guest) { // LazyCollection over all pages $guest->isAuthorizedGuest(); }
Sites, devices and statistics
Unifi::info()->applicationVersion; // "9.1.120" Unifi::sites()->all(); // LazyCollection<Site> Unifi::forSite('hotel2')->devices()->list(); // short name or UUID foreach (Unifi::devices()->all() as $device) { if (! $device->isOnline()) { // alert } } Unifi::devices()->statistics($deviceId)->cpuUtilizationPct; Unifi::devices()->restart($deviceId); Unifi::devices()->powerCyclePort($switchId, portIdx: 7); // reboot a PoE access point Unifi::devices()->pending(); // devices waiting for adoption Unifi::devices()->adopt('94:2a:6f:26:c6:ca');
Networks and Wi-Fi broadcasts
Unifi::networks()->list()->items; // Network (name, management, vlanId, ...) Unifi::networks()->wans(); // ['uuid' => 'Internet 1'] Unifi::wifi()->all(); // WifiBroadcast (SSIDs) Unifi::wifi()->disable($broadcastId); // reads the definition and sends it back Unifi::wifi()->update($broadcastId, $definition); // full definition, as in the official schema
Filters and pagination
use Beziworld\Unifi\Support\Filter; $page = Unifi::vouchers()->list( Filter::and(Filter::eq('expired', false), Filter::like('name', 'Room*')), offset: 0, limit: 100, ); $page->items; $page->totalCount; $page->hasMore(); $page->nextOffset();
Classic API: events, alarms, blocking, WLAN passphrase
$classic = Unifi::classic(); // configured site, or Unifi::classic('hotel2') $classic->events()->recent(withinHours: 24, limit: 100); // list<Event> $classic->events()->alarms(archived: false); $classic->events()->archiveAlarm($alarmId); $classic->clients()->online(); // list<ClassicClient> $classic->clients()->block('aa:bb:cc:dd:ee:01'); $classic->clients()->reconnect('aa:bb:cc:dd:ee:01'); $classic->clients()->authorizeGuest('aa:bb:cc:dd:ee:01', minutes: 1440, downKbps: 10000); $classic->vouchers()->create(minutes: 1440, count: 10, quota: 1, note: 'Lobby'); $classic->wlans()->setPassphrase($wlanId, 'Guest-' . now()->format('dmy')); $classic->devices()->restart(['94:2a:6f:26:c6:ca']); $classic->sites()->health();
The classic API is not officially documented by Ubiquiti; endpoints follow the long-standing community
documentation. Availability of stat/event depends on the Network version.
Controllers stored in the database
Unifi::using([ 'url' => $hotel->unifi_url, 'api_key' => decrypt($hotel->unifi_api_key), 'site' => $hotel->unifi_site, 'verify_ssl' => false, ])->vouchers()->forStay('Room 1', $checkout);
Anything else
// Any endpoint of the official API; {siteId} is replaced with the current site UUID Unifi::request('GET', '/v1/sites/{siteId}/firewall/policies'); Unifi::request('POST', '/v1/sites/{siteId}/acl-rules', body: [...]); // Any classic endpoint of the current site Unifi::classic()->request('GET', 'stat/health');
Exceptions
All exceptions extend Beziworld\Unifi\Exceptions\UnifiException:
| Exception | When |
|---|---|
AuthenticationException |
API key or login rejected (HTTP 401/403). Logins are never repeated automatically. |
RateLimitException |
HTTP 429; retryAfter() returns the seconds from Retry-After |
ApiException |
Error response or meta.rc = error; errorCode() (e.g. api.err.InvalidPayload), httpStatus(), requestId(), payload() |
ConnectionFailed |
DNS, TCP, TLS or timeout error (after read retries) |
SiteNotFound |
No site matches UNIFI_SITE |
InvalidConfiguration |
Unknown connection, missing URL or credentials, missing API key for the official API |
Notes
- Create a dedicated local account for the classic API and a dedicated API key for the official API.
- UniFi OS blocks clients after repeated failed logins; this package never retries a rejected login.
- Disabling TLS verification exposes credentials to anyone on the network path. Prefer a CA bundle path.
- Calls are synchronous; voucher batches and device actions belong in queued jobs.
Development
composer install
composer ci # pint --test, phpstan (level 8), pest
Author
Created and maintained by Kamil Ćwiertnia, beziworld.eu.
- E-mail: biuro@beziworld.eu
- Issues and feature requests: GitHub issues
- Security vulnerabilities: see SECURITY.md
Need an integration? Get in touch through beziworld.eu.
Other packages by the author
- beziworld/laravel-previo: Previo hotel PMS
- beziworld/laravel-directadmin: DirectAdmin hosting panel
- beziworld/laravel-proxmox: Proxmox VE
License
MIT. See LICENSE.md. This package is not affiliated with, endorsed by or sponsored by Ubiquiti Inc. UniFi is a trademark of Ubiquiti Inc.