gufy/cpanel-php
A PHP client for the cPanel API that simplifies common hosting tasks like managing email accounts, databases, FTP users, and files. Framework-agnostic, with support for cPanel API 2 and UAPI, it lets you integrate cPanel management into any PHP app.
gufy/cpanel-php is a thin PHP wrapper around the cPanel API 2 (UAPI) and WHM API 1 HTTP endpoints. It is not a Laravel package in the modern sense — no published config, no facade, no first‑party service provider — but it is straightforward to wire up.
Install
composer require gufy/cpanel-php
Minimal boot
The class is constructed with an options array and then called with execute():
use Gufy\CpanelPhp\Cpanel;
$cpanel = new Cpanel([
'host' => env('CPANEL_HOST'), // full URL incl. scheme + port
'username' => env('CPANEL_USERNAME'),
'password' => env('CPANEL_PASSWORD'), // password OR API token
'auth_type' => env('CPANEL_AUTH_TYPE'), // 'basic' or 'hash'
'timeout' => 60,
]);
// WHM call — runs as the account in `username` (typically root / reseller)
$cpanel->execute('listaccts');
Where to look first
README.md — shows the constructor options and the two call styles (WHM vs. per‑cPanel‑account).src/Cpanel.php — the whole surface is here: setHost, setPort, setUsername, setPassword, setAuthType, setTimeout, setMethod, setCpanelUser, setModule, setFunction, execute, getResponse. Read it end to end; it's small.https://api.docs.cpanel.net/) — the wrapper does no validation, so the function/module names are your responsibility.First useful use case
Account provisioning from a Laravel admin panel: create a cPanel account with createacct, list accounts with listaccts, or manage a single account's mailboxes via the UAPI Email::list_pops. Anything that would otherwise be a manual cPanel click is a good fit.
Wrap it — don't call it from controllers. The class is stateful (fluent setters mutate the instance) and returns raw payloads. Bind a single configured instance in a service provider and expose intent‑revealing methods:
// app/Services/WhmClient.php
class WhmClient
{
public function __construct(private Cpanel $cpanel) {}
public function listAccounts(): array
{
$res = $this->cpanel->execute('listaccts');
return $this->unwrap($res)['acct'] ?? [];
}
public function createAccount(string $user, string $domain, string $plan, string $pass): array
{
return $this->unwrap($this->cpanel->execute('createacct', [
'username' => $user,
'domain' => $domain,
'plan' => $plan,
'password' => $pass,
]));
}
}
Config via environment, never code. Put the host, credentials, and auth type in config/cpanel.php, read from env(), and cache the config. Store tokens with Laravel's encrypted cast or a secrets manager; never commit them.
Two distinct auth/context modes:
host ends in :2087 (or :2086 if not TLS), username is root or a reseller, auth_type is basic with the root password or hash with a WHM API token.host ends in :2083 / :2082, username is the cPanel user, and you must call setCpanelUser($user) before setModule(...)->setFunction(...)->execute(). Mixing these up returns generic 401/403s that are painful to debug.Prefer API tokens over root passwords. WHM API tokens are created per user and can be revoked. For anything user‑facing (e.g. a customer managing their own mailboxes), generate a token scoped to that cPanel user and hand it to a client configured for :2083.
Queue long operations. createacct, restorepkg, transfer and backups take seconds to minutes. Dispatch a job, log the outcome, and notify on failure. The class is not async and has a fixed timeout.
Integrate with Laravel features:
Artisan command (php artisan cpanel:sync) for bulk reconciliation — it's the easiest way to run against a list of accounts with ->setCpanelUser().AccountCreated, MailboxQuotaChanged) around the wrapper so side effects are testable.Testing: the client ultimately uses Guzzle. Extract the HTTP layer or inject a mocked Cpanel into your service and assert on the arguments passed to execute(). If you need HTTP‑level tests, use Guzzle's MockHandler via a refactored client, since the package doesn't expose a pluggable handler.
"gufy/cpanel-php": "^1.0") and expect to fork or replace it if cPanel changes its API shape. Read the code once; it's a few hundred lines and you may end up owning it.host must include the scheme and port. 'https://server:2087' works; 'server:2087' and 'https://server' do not. This is the #1 cause of "it silently does nothing."auth_type must match the credential. basic sends a password; hash sends the token as the password. Using a token with basic (or vice versa) yields 401/403 responses that look like network errors in the logs.$cpanel->setCpanelUser('bob')
->setModule('Email')
->setFunction('add_pop')
->execute(['email' => '[email protected]', 'password' => '...', 'quota' => 500]);
Forgetting setCpanelUser on a WHM‑mode client falls back to the reseller's own context and can create resources in the wrong account — a security‑relevant bug.{"result": [...], "metadata": {...}}; UAPI returns {"result": {...}, "status": 1}. The wrapper does not normalize. Always inspect for status/result before using the data, and throw a domain exception when status !== 1.status: 0 means the API rejected you; a GuzzleHttp\Exception\ClientException or ServerException means transport/route failure. Catch both, log the full response body, and rethrow as a typed exception.listaccts per user, add a small sleep or a queued job with RateLimited middleware in Laravel 10+.pkgacct/backup calls, and set the same value on Guzzle's client options if you fork — the wrapper forwards the timeout.Authorization headers and request logs; scrub them before they reach your log store.Cpanel and overriding execute() or the request builder. If you need connection pooling, retries, circuit breaking, or a proper Laravel integration, that is the intended seam — subclass and wrap, don't monkey‑patch.Cpanel::class binding in AppServiceProvider) makes it trivial to swap in a fake client in feature tests and avoid network calls entirely.How can I help you explore Laravel packages today?