chamikasamaraweera/laravel-3x-ui-manager

Laravel package to manage multiple 3x-ui (Xray) panels for hosting/billing systems

Maintainers

Package info

github.com/ChamikaSamaraweera/laravel-3x-ui-manager

pkg:composer/chamikasamaraweera/laravel-3x-ui-manager

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0 2026-05-09 01:53 UTC

This package is auto-updated.

Last update: 2026-05-09 03:05:22 UTC


README

Latest Version PHP Version License

A Laravel package to manage multiple 3X-UI (Xray) panels from a single billing or hosting control panel.

Designed for hosting businesses that sell VPN plans across multiple regional servers (EU, US, SG, AU, etc.) — each user buys a plan on a chosen server and this package handles the full lifecycle: provisioning, traffic sync, suspension, renewal, and termination.

Features

  • Multi-panel support — manage unlimited panels (EU, US, SG, AU, JP, …) each with its own credentials and custom base path
  • Custom URI path support — works with 3X-UI's configurable web base path (/xui, /base-xui, /mypath, …)
  • Full client lifecycle — create, enable/disable, reset traffic, renew, delete
  • Auto session management — login, cookie caching, and automatic re-authentication on expiry
  • Traffic sync — scheduled command syncs usage from all panels; auto-suspends over-quota or expired accounts
  • Local databasevpn_accounts table mirrors panel state for fast queries without hitting the panel API
  • Complete API coverage — all endpoints from the official 3X-UI API wiki
  • Laravel 10/11/12/13 compatible

Requirements

  • PHP ^8.3
  • Laravel ^10.0 | ^11.0 | ^12.0 | ^13.0
  • A running 3X-UI panel (any version with API enabled)

Installation

1. Install via Composer

composer require chamikasamaraweera/laravel-3x-ui-manager

Local development: If you are working with a local path repository, add it to your project's composer.json first:

"repositories": [
    { "type": "path", "url": "./packages/laravel-3x-ui-manager" }
],
"minimum-stability": "dev",
"prefer-stable": true

2. Publish config and migrations

php artisan vendor:publish --tag=3xui-config
php artisan vendor:publish --tag=3xui-migrations
php artisan migrate

3. Configure .env

Add a block for each of your panels. The BASE_PATH must match the URI Path set in your 3X-UI panel under Panel Settings.

# ── Europe ──────────────────────────────────────
THREEXUI_EU_URL=https://eu.yourdomain.com:8443
THREEXUI_EU_USER=admin
THREEXUI_EU_PASS=your_password
THREEXUI_EU_BASE_PATH=xui          # default — change if you set a custom path

# ── United States ────────────────────────────────
THREEXUI_US_URL=https://us.yourdomain.com:8443
THREEXUI_US_USER=admin
THREEXUI_US_PASS=your_password
THREEXUI_US_BASE_PATH=xui

# ── Singapore ────────────────────────────────────
THREEXUI_SG_URL=https://sg.yourdomain.com:8443
THREEXUI_SG_USER=admin
THREEXUI_SG_PASS=your_password
THREEXUI_SG_BASE_PATH=xui

# ── Global ───────────────────────────────────────
THREEXUI_DEFAULT_INBOUND_ID=1

Custom base path example: If your panel URL is https://host:8443/base-xui/panel/, set BASE_PATH=base-xui.

Configuration

The published config file is at config/3xui.php.

'panels' => [
    'eu' => [
        'name'               => 'Europe',
        'url'                => env('THREEXUI_EU_URL'),
        'username'           => env('THREEXUI_EU_USER'),
        'password'           => env('THREEXUI_EU_PASS'),
        'base_path'          => env('THREEXUI_EU_BASE_PATH', 'xui'),
        'default_inbound_id' => 1,   // optional per-panel override
        'enabled'            => true,
    ],
    // add as many panels as you need …
],

'default_inbound_id' => env('THREEXUI_DEFAULT_INBOUND_ID', 1),
'session_ttl'        => 60,   // minutes — how long to cache login sessions
'timeout'            => 30,   // seconds — HTTP request timeout

Quick Start

Provision a VPN account when a user purchases a plan

use ThreeXUI\Services\VpnAccountService;

class OrderController extends Controller
{
    public function completePurchase(Request $request, VpnAccountService $vpn)
    {
        $account = $vpn->provision(auth()->id(), $request->server, [
            'traffic_gb' => 50,   // GB quota  (0 = unlimited)
            'days'       => 30,   // expiry in days (0 = never)
            'limit_ip'   => 2,    // simultaneous device limit (0 = unlimited)
        ]);

        $order->update(['vpn_account_id' => $account->id]);

        return response()->json($account);
    }
}

Show live traffic to the user

use ThreeXUI\Facades\ThreeXUI;
use ThreeXUI\Models\VpnAccount;

class DashboardController extends Controller
{
    public function show()
    {
        $account = VpnAccount::forUser(auth()->id())->active()->firstOrFail();

        $traffic = ThreeXUI::getTraffic($account->panel_key, $account->email);
        $inbound = ThreeXUI::panel($account->panel_key)->getInbound($account->inbound_id);

        return view('vpn.dashboard', compact('account', 'traffic', 'inbound'));
    }
}

Usage Reference

VpnAccountService

The primary service for your billing system to call. Inject it via the constructor or use it directly.

use ThreeXUI\Services\VpnAccountService;

$vpn = app(VpnAccountService::class);

provision(int $userId, string $panelKey, array $options): VpnAccount

Creates a client on the 3X-UI panel and stores the account in your local database.

$account = $vpn->provision($user->id, 'sg', [
    'traffic_gb'  => 100,         // quota in GB, 0 = unlimited
    'days'        => 30,          // expiry from now, 0 = never
    'limit_ip'    => 3,           // max simultaneous connections
    'inbound_id'  => 2,           // override the default inbound
]);

Returns a VpnAccount model instance.

suspend(VpnAccount $account): VpnAccount

Disables the client on the panel and marks the local record as suspended.

$vpn->suspend($account);

unsuspend(VpnAccount $account): VpnAccount

Re-enables the client and marks the local record as active.

$vpn->unsuspend($account);

renew(VpnAccount $account, int $additionalDays, ?int $newTrafficGB = null): VpnAccount

Resets traffic, extends the expiry, and optionally changes the quota.

// Extend by 30 days, keep existing quota
$vpn->renew($account, 30);

// Extend by 30 days and upgrade to 100 GB
$vpn->renew($account, 30, newTrafficGB: 100);

terminate(VpnAccount $account): void

Deletes the client from the panel and soft-deletes the local record.

$vpn->terminate($account);

syncTraffic(VpnAccount $account): VpnAccount

Pulls live traffic from the panel into the local DB. Auto-suspends if over quota or expired.

$account = $vpn->syncTraffic($account);
echo $account->traffic_used_gb; // e.g. 12.45

syncAllTraffic(): void

Syncs every active account in chunks of 100. Use this in the scheduled command.

ThreeXUI Facade

Direct access to panel operations without the billing layer.

use ThreeXUI\Facades\ThreeXUI;
Method Description
ThreeXUI::panel('sg') Returns a PanelClient for the given panel key
ThreeXUI::availablePanels() Lists all enabled panels [['key','name'], …]
ThreeXUI::createClient($panel, $options) Low-level client provisioning
ThreeXUI::deleteClient($panel, $inboundId, $uuid) Remove a client
ThreeXUI::getTraffic($panel, $email) Get formatted traffic stats
ThreeXUI::suspendClient($panel, $inboundId, $email) Disable a client
ThreeXUI::unsuspendClient($panel, $inboundId, $email) Enable a client
ThreeXUI::resetTraffic($panel, $inboundId, $email) Reset usage counter
ThreeXUI::serverStatus($panel) Server resource stats
ThreeXUI::allServerStatuses() Stats for all panels at once

PanelClient — Raw API Access

For advanced use cases you can call the panel API directly.

$client = ThreeXUI::panel('eu');

Inbounds

$client->getInbounds();                        // GET  /panel/api/inbounds/list
$client->getInbound(int $id);                  // GET  /panel/api/inbounds/get/:id
$client->addInbound(array $payload);           // POST /panel/api/inbounds/add
$client->updateInbound(int $id, array $data);  // POST /panel/api/inbounds/update/:id
$client->deleteInbound(int $id);               // POST /panel/api/inbounds/del/:id
$client->resetAllTraffics();                   // POST /panel/api/inbounds/resetAllTraffics
$client->resetAllClientTraffics(int $id);      // POST /panel/api/inbounds/resetAllClientTraffics/:id
$client->deleteDepletedClients(int $id = -1);  // POST /panel/api/inbounds/delDepletedClients/:id
$client->importInbound(array $payload);        // POST /panel/api/inbounds/import

Clients

$client->addClient(int $inboundId, array $payload);           // POST /panel/api/inbounds/addClient
$client->updateClient(string $uuid, array $payload);          // POST /panel/api/inbounds/updateClient/:clientId
$client->deleteClient(int $inboundId, string $uuid);          // POST /panel/api/inbounds/:id/delClient/:clientId
$client->deleteClientByEmail(int $inboundId, string $email);  // POST /panel/api/inbounds/:id/delClientByEmail/:email
$client->enableClient(int $inboundId, string $email);         // (fetches inbound → updateClient with enable=true)
$client->disableClient(int $inboundId, string $email);        // (fetches inbound → updateClient with enable=false)

Traffic & Stats

$client->getClientTraffic(string $email);         // GET  /panel/api/inbounds/getClientTraffics/:email
$client->getClientTrafficById(int $inboundId);    // GET  /panel/api/inbounds/getClientTrafficsById/:id
$client->resetClientTraffic(int $id, string $email); // POST /panel/api/inbounds/:id/resetClientTraffic/:email
$client->updateClientTraffic(string $email, array $payload); // POST /panel/api/inbounds/updateClientTraffic/:email
$client->getOnlineClients();                      // POST /panel/api/inbounds/onlines
$client->getLastOnline();                         // POST /panel/api/inbounds/lastOnline

IP Management

$client->getClientIps(string $email);    // POST /panel/api/inbounds/clientIps/:email
$client->clearClientIps(string $email);  // POST /panel/api/inbounds/clearClientIps/:email

Server

$client->getServerStatus();    // POST /server/status
$client->getPanelBaseUrl();    // debug: returns resolved base URL

VpnAccount Model

All provisioned accounts are stored in the vpn_accounts table.

use ThreeXUI\Models\VpnAccount;

Scopes

VpnAccount::active()->get();                  // status = active
VpnAccount::forUser($userId)->get();          // filter by user
VpnAccount::forPanel('sg')->get();            // filter by panel
VpnAccount::forUser($userId)->active()->first(); // combine scopes

Database columns

Column Type Description
user_id bigint Your billing system user ID
panel_key string e.g. sg, eu, us
inbound_id int 3X-UI inbound ID
client_uuid string UUID used in 3X-UI
email string Unique identifier in 3X-UI (not a real email)
traffic_limit_bytes bigint 0 = unlimited
expires_at timestamp null = never expires
status enum active / suspended / expired / deleted
traffic_used_up bigint Upload bytes (synced from panel)
traffic_used_down bigint Download bytes (synced from panel)
traffic_synced_at timestamp Last sync time

Computed accessors

$account->traffic_used_bytes   // int   — up + down bytes
$account->traffic_used_gb      // float — e.g. 12.45
$account->traffic_limit_gb     // string — e.g. "50 GB" or "Unlimited"
$account->expiry_timestamp_ms  // int   — Unix ms for 3X-UI (0 = never)
$account->is_expired           // bool
$account->is_over_limit        // bool

Scheduled Traffic Sync

Register the built-in command in your scheduler. In Laravel 10 (app/Console/Kernel.php):

$schedule->command('threexui:sync-traffic')->everyFiveMinutes();

In Laravel 11+ (routes/console.php):

use Illuminate\Support\Facades\Schedule;

Schedule::command('threexui:sync-traffic')->everyFiveMinutes();

The command:

  1. Fetches live traffic from all panels for every active account
  2. Updates traffic_used_up / traffic_used_down in the local DB
  3. Auto-suspends accounts that have exceeded their quota
  4. Auto-suspends and marks as expired accounts past their expires_at

Billing System Integration Examples

Auto-suspend on invoice overdue

// In your invoice webhook / event listener:
public function handle(InvoiceOverdue $event): void
{
    $account = VpnAccount::where('user_id', $event->invoice->user_id)
        ->active()
        ->first();

    if ($account) {
        app(VpnAccountService::class)->suspend($account);
    }
}

Reactivate on payment

public function handle(PaymentReceived $event): void
{
    $account = VpnAccount::where('user_id', $event->invoice->user_id)
        ->where('status', 'suspended')
        ->first();

    if ($account) {
        app(VpnAccountService::class)->unsuspend($account);
    }
}

Plan upgrade — change quota and extend

public function handleUpgrade(Request $request, VpnAccountService $vpn): void
{
    $account = VpnAccount::find($request->vpn_account_id);

    $vpn->renew($account, additionalDays: 30, newTrafficGB: 200);
}

Get all server statuses for an admin dashboard

$statuses = ThreeXUI::allServerStatuses();

// Returns:
// [
//   'eu' => ['cpu' => 12.4, 'mem' => [...], 'xray' => ['running' => true, ...]],
//   'sg' => ['cpu' => 8.1, ...],
//   'us' => ['error' => 'Connection timed out'],  ← panel offline, won't throw
// ]

Add more panels at any time

Just add a new entry to config/3xui.php and the matching .env vars — no code changes needed:

'panels' => [
    'eu' => [...],
    'us' => [...],
    'sg' => [...],
    'au' => [                                          // ← new panel
        'name'      => 'Australia',
        'url'       => env('THREEXUI_AU_URL'),
        'username'  => env('THREEXUI_AU_USER'),
        'password'  => env('THREEXUI_AU_PASS'),
        'base_path' => env('THREEXUI_AU_BASE_PATH', 'xui'),
        'enabled'   => true,
    ],
],

API Endpoint Reference

All endpoints follow the structure:

https://{host}:{port}/{base_path}/{endpoint}
Category Base path
Login /{base_path}/login
Inbounds & Clients /{base_path}/panel/api/inbounds/…
Server status /{base_path}/server/status

See the official 3X-UI API documentation for the full endpoint list.

Troubleshooting

PanelAuthException: Login failed — HTTP 404

Your base_path is wrong. Open your panel URL in a browser. If it loads at https://host:8443/mypath/panel/, then set BASE_PATH=mypath.

PanelAuthException: No session cookie returned

The panel returned a successful login response but no cookie. Make sure your panel URL uses http:// or https:// correctly and that the panel's SSL certificate is valid (or set verify => false which is already the default).

PanelException: Client [email] not found in inbound

The email identifier does not exist on the panel. This can happen if the client was manually deleted from the panel UI. Call terminate() to clean up the local record.

composer require fails — version conflict

Make sure your project's composer.json has:

"minimum-stability": "dev",
"prefer-stable": true

Changelog

v1.0.0

  • Initial release
  • Full 3X-UI API coverage (/panel/api/inbounds/*)
  • Multi-panel routing with per-panel base_path
  • VpnAccountService with provision / suspend / renew / terminate / syncTraffic
  • VpnAccount Eloquent model with scopes and computed accessors
  • threexui:sync-traffic Artisan command
  • Laravel 10 / 11 / 12 / 13 support

License

MIT — see LICENSE.

Credits

Built on top of the 3X-UI panel by @MHSanaei.