gufy/cpanel-whm
Laravel package for interacting with cPanel and WHM APIs. Manage accounts, email, databases, domains, DNS and more through a clean, fluent PHP interface, with support for both cPanel and WHM endpoints, simple configuration, and straightforward installation via Composer.
1. Install and wire it up
composer require gufy/cpanel-whm
This is a Laravel 5-era package. On Laravel 5.5+ it may auto-discover; if not, add the service provider (and optionally a facade alias) manually in config/app.php:
'providers' => [
// ...
Gufy\CpanelPhp\Providers\LaravelServiceProvider::class,
],
2. Publish and configure
php artisan vendor:publish --provider="Gufy\CpanelPhp\Providers\LaravelServiceProvider"
You get a config/cpanel.php with host, port, username, password, token, auth_type, and debug. Drive it from .env:
CPANEL_HOST=server1.example.com # use the hostname, not the IP
CPANEL_PORT=2087 # 2087 = WHM SSL, 2086 = WHM plain
CPANEL_USERNAME=root
CPANEL_PASSWORD= # or use a token instead
CPANEL_TOKEN=
CPANEL_AUTH_TYPE=password # or "token"
Two scopes matter from day one:
| Scope | Port | Purpose |
|---|---|---|
| WHM | 2087 | Server-wide: create/suspend/terminate accounts, packages, resellers |
| cPanel | 2083 | Per-account: email, forwarders, DNS zones, login URLs, disk usage |
3. First use case: smoke-test then get an SSO link
Instantiate directly (avoids guessing the container binding), point it at your server, and call a harmless WHM function:
use Gufy\CpanelPhp\Cpanel;
$cpanel = (new Cpanel())
->setHost(config('cpanel.host'))
->setPort(config('cpanel.port'))
->setUsername(config('cpanel.username'))
->setPassword(config('cpanel.password'));
$accounts = $cpanel->listaccts(); // method names mirror WHM API functions
Then use the most common day-one feature — logging a customer into cPanel without a password:
$url = $cpanel->getLoginUrl($username, 'cpanel');
return redirect($url);
Where to look first: the README for available methods, config/cpanel.php for auth options, and the Cpanel class itself to see how it wraps the HTTP call (you'll want to know this for mocking and timeouts).
Wrap it in your own service, don't scatter it. Bind a thin repository to the container so tests and future client swaps stay easy:
class WhmClient
{
public function __construct(private Cpanel $cpanel) {}
public function provision(array $spec): array
{
return $this->cpanel->createacct([
'username' => $spec['username'],
'domain' => $spec['domain'],
'password' => $spec['password'],
'plan' => $spec['plan'],
]);
}
}
Multi-server / multi-tenant. Because the setters are fluent, build a fresh instance per target server rather than mutating a singleton:
$client = (new Cpanel())
->setHost($server->hostname)
->setPort(2087)
->setUsername($server->whm_user)
->setToken($server->whm_token) // token auth is preferable to password
->setAuthType('token');
Store server credentials (encrypted cast) in a servers table and cache one client per server.
Common WHM admin flows to build first:
$cpanel->listpkgs(); // hosting packages
$cpanel->createacct($params); // provision
$cpanel->suspendacct(['user' => $user, 'reason' => 'billing']);
$cpanel->unsuspendacct(['user' => $user]);
$cpanel->removeacct(['user' => $user, 'keepdns' => 0]);
$cpanel->changepackage(['user' => $user, 'pkg' => 'premium']);
$cpanel->passwd(['user' => $user, 'pass' => $newPassword]);
cPanel user-scope flows. Use the same fluent setup but target port 2083 and the account user; this is where getLoginUrl, email/forwarder creation, and disk-usage lookups live.
Push provisioning into jobs. createacct and removeacct are slow and can time out. Dispatch a queued job, store the raw response for auditability, and emit domain events (AccountCreated, AccountSuspended) after success — that's the pattern that makes this package feel like part of your app instead of a script.
Normalize the responses. WHM returns {metadata: {result: 1, ...}, data: {...}}, while cPanel API2 returns {cpanelresult: {data: [...], error: ...}}. Write one adapter that converts both into a ['ok' => bool, 'data' => array, 'error' => ?string] shape so your controllers don't branch on API version.
Testing. Don't hit a live WHM in tests. Subclass Cpanel and override the request method to return fixture JSON, or bind a fake WhmClient interface into the container. Keep a couple of recorded real responses (anonymized) as fixtures — the response wrappers are the easiest thing to get wrong.
illuminate/support constraints to fight Laravel 9+. On modern Laravel you'll likely fork it, loosen constraints, or reimplement the ~200 lines over Guzzle/Laravel Http. Budget for that up front.metadata.result / cpanelresult.error before assuming a create/suspend worked.password and token are handled differently; if auth_type is token, set a token (not a password) and make sure your WHM user has API token access. Symptom of a mismatch: 401/403 that looks like a bad host.https://1.2.3.4:2087 triggers TLS verification errors. If you must, disable verification in the client config — but never in production.Connection refused is more often a security group than the package.getLoginUrl links are single-use and short-lived. Don't cache or email them; redirect immediately. They're a great SSO primitive for a customer dashboard.listaccts() returns every account on the server. On a busy box that's megabytes of JSON and a slow request. Use WHM's search/searchtype/page/chunk params rather than pulling everything and filtering in PHP.CPANEL_DEBUG off in production and make sure the raw request (which includes the Authorization header) never hits your normal log channel..env are a liability. Prefer a dedicated API token tied to a minimal-privilege reseller if you only manage a subset of accounts, and rotate on a schedule.How can I help you explore Laravel packages today?