Repository files navigation

cPanel & WHM SDK for PHP

🖥 cPanel & WHM SDK for PHP

A modern, fully typed PHP SDK for driving cPanel (UAPI + API2) and WHM (API 1).

PHP VersionSymfonyTestsPackagistLicense

Email · DNS · MySQL · SSL · FTP · Files · Accounts · Resellers · Packages · AutoSSL · Backups

Installation · Quick Start · API Reference · Error Handling


$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);
$whm->accounts()->createUserSession('customer1');

Framework-agnostic core — usable from any PHP project, script, or worker — with an optional bundle for first-class Symfony integration. Authenticated with API tokens, typed exceptions, and a comment-free, strictly typed codebase (PHP 8.2+, declare(strict_types=1) everywhere).


Table of Contents


Features

  • Full coverage of the three cPanel API surfaces: UAPI (the modern cPanel API), API2 (legacy but still required for zone editing, subdomains, addon domains, file operations), and WHM API 1 (server administration).
  • Framework-agnostic: two plain facades (Cpanel, Whm) you can instantiate anywhere; only hard dependency is symfony/http-client, a standalone component that works in any PHP project.
  • Token authentication only — no passwords, no sessions, no cookies. Uses the official Authorization: cpanel user:token / Authorization: whm user:token schemes.
  • High-level, discoverable modules grouped by domain: email, MySQL, DNS, SSL, files, accounts, resellers, backups, PHP versions, security…
  • A single normalized response object (ApiResponse) regardless of which underlying API answered — you never parse cpanelresult or metadata envelopes yourself.
  • Typed exception hierarchy under one marker interface, so you can catch narrowly or broadly.
  • Escape hatches everywhere: any endpoint not wrapped by a module remains one method call away.
  • Optional Symfony bundle with semantic configuration and autowirable services.
  • Fully unit-tested against MockHttpClient (no network required).

Requirements

DependencyVersion
PHP>= 8.2
cPanel/WHMany version supporting API tokens (v64+)
Symfony6.4 LTS or 7.x — optional, only for the bundle integration

You will need at least one of:

  • a cPanel API token — created in cPanel » Security » Manage API Tokens
  • a WHM API token — created in WHM » Development » Manage API Tokens

Installation

The package is published on Packagist:

composer require chuckbartowski/cpanel-sdk

Quick Start (plain PHP)

No framework required — build the clients and go:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Client\WhmClient;
useChuckBartowski\CpanelSdk\Cpanel;
useChuckBartowski\CpanelSdk\Whm;
$cpanel = newCpanel(newCpanelClient(
host: 'server.example.com',
username: 'myaccount',
token: getenv('CPANEL_API_TOKEN'),
port: 2083,
));
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10');
$whm = newWhm(newWhmClient(
host: 'server.example.com',
username: 'root',
token: getenv('WHM_API_TOKEN'),
port: 2087,
));
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);

Client constructor signature (identical for both clients):

newCpanelClient(
string $host,
string $username,
string $token,
int $port, // 2083 for cPanel, 2087 for WHM
bool $verifySsl = true,
float $timeout = 30.0,
?HttpClientInterface $httpClient = null, // inject your own (retries, proxy, mock…)
);

Symfony Integration (optional)

A ready-made bundle wires everything into the container. Register it:

// config/bundles.phpreturn [
ChuckBartowski\CpanelSdk\CpanelSdkBundle::class => ['all' => true],
];

Then create config/packages/cpanel_sdk.yaml:

cpanel_sdk:
host: '%env(CPANEL_HOST)%'verify_ssl: truetimeout: 30cpanel:
username: '%env(CPANEL_USERNAME)%'token: '%env(CPANEL_API_TOKEN)%'port: 2083whm:
username: '%env(WHM_USERNAME)%'token: '%env(WHM_API_TOKEN)%'port: 2087

And the matching environment variables:

# .env.localCPANEL_HOST=server.example.comCPANEL_USERNAME=myaccountCPANEL_API_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWHM_USERNAME=rootWHM_API_TOKEN=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

Configuration reference

KeyTypeDefaultDescription
hoststringrequiredHostname of the cPanel/WHM server (no scheme, no port)
verify_sslbooltrueTLS peer/host verification; disable only for self-signed dev servers
timeoutfloat30.0Per-request timeout in seconds
cpanel.usernamestring''cPanel account name
cpanel.tokenstring''cPanel API token
cpanel.portint2083cPanel TLS port
whm.usernamestring''WHM user (usually root or a reseller)
whm.tokenstring''WHM API token
whm.portint2087WHM TLS port

The cpanel and whm sections are independent — configure only the side you need. Calling a client with missing credentials throws an AuthenticationException immediately, before any network request is made.

The bundle reuses your application's http_client service when available (so scoped clients, retry strategies, and profiler integration all apply), and falls back to a native client otherwise.

Architecture

src/
├── CpanelSdkBundle.php Symfony bundle: config tree + service wiring
├── Cpanel.php Facade: entry point for cPanel-level modules
├── Whm.php Facade: entry point for WHM-level modules
├── Client/
│ ├── AbstractClient.php Shared HTTP transport, auth header, error mapping
│ ├── CpanelClient.php uapi() and api2() generic executors
│ └── WhmClient.php call() (WHM API 1) and cpanelUapi() (root proxy)
├── Response/
│ └── ApiResponse.php Immutable, normalized response for all 3 API formats
├── Exception/
│ ├── CpanelSdkExceptionInterface.php
│ ├── ApiException.php API answered but reported a failure
│ ├── AuthenticationException.php
│ └── TransportException.php Network / TLS / timeout / invalid JSON
└── Api/
├── Cpanel/ EmailApi, DomainApi, MysqlApi, FtpApi,
│ SslApi, FileApi, DnsApi, StatsApi
└── Whm/ AccountApi, ResellerApi, PackageApi, DnsZoneApi,
IpApi, SecurityApi, BackupApi, PhpApi,
AutoSslApi, ConfigApi, ServerApi

Design decisions:

  • Facade + lazy modules: Cpanel/Whm instantiate each module on first use and cache it, so the DI container only carries four services.
  • Modules always validate: every module method calls ensureSuccess() internally and throws ApiException on failure. If you need to inspect a failed response without an exception, drop down to the client level.
  • Nothing is sealed off: the clients' generic methods accept any module/function/parameter combination, so a cPanel endpoint added tomorrow is usable today.

Usage

Standalone, instantiate the facades as shown in the Quick Start. In Symfony, both facades are autowirable in controllers, services, commands, and message handlers.

The Cpanel facade

useChuckBartowski\CpanelSdk\Cpanel;
finalclass MailboxProvisioner
{
publicfunction__construct(privatereadonlyCpanel$cpanel)
{
}
publicfunctionprovision(string$localPart, string$domain, string$password): void
{
$this->cpanel->email()->create($localPart, $domain, $password, quotaMb: 512);
}
}

The Whm facade

useChuckBartowski\CpanelSdk\Whm;
finalclass HostingAccountManager
{
publicfunction__construct(privatereadonlyWhm$whm)
{
}
publicfunctionopen(string$username, string$domain): void
{
$this->whm->accounts()->create($username, $domain, [
'plan' => 'starter',
'contactemail' => 'billing@example.com',
]);
}
publicfunctionsuspendForNonPayment(string$username): void
{
$this->whm->accounts()->suspend($username, 'unpaid invoice');
}
}

Generic calls (escape hatch)

Any endpoint not covered by a module remains reachable:

$cpanel->client()->uapi('Batch', 'strict', ['command' => $commands], 'POST');
$cpanel->client()->api2('Cron', 'listcron');
$whm->client()->call('sethostname', ['hostname' => 'srv2.example.com'], 'POST');
$whm->client()->cpanelUapi('customer1', 'Email', 'list_pops');

cpanelUapi() runs a UAPI function as any cPanel account through the WHM token — the standard pattern for hosting control panels where only the root/reseller token is stored.

API Reference

Every method returns an ApiResponse and throws on failure (see Error Handling). Named arguments are shown where they improve readability.

Email

$cpanel->email() — UAPI Email module.

MethodUnderlying functionNotes
accounts(?string $domain = null)list_pops_with_diskIncludes disk usage per mailbox
create(string $localPart, string $domain, string $password, int $quotaMb = 0)add_pop0 = unlimited quota
delete(string $localPart, string $domain)delete_pop
changePassword(string $localPart, string $domain, string $password)passwd_pop
setQuota(string $localPart, string $domain, int $quotaMb)edit_pop_quota
forwarders(?string $domain = null)list_forwarders
addForwarder(string $domain, string $localPart, string $destination)add_forwarder
deleteForwarder(string $address, string $forwarder)delete_forwarder
mailDirUsage(string $localPart, string $domain)get_pop_quota
$cpanel->email()->accounts('example.com');
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->email()->addForwarder('example.com', 'contact', 'inbox@elsewhere.com');

Domains

$cpanel->domains() — UAPI DomainInfo + API2 SubDomain / AddonDomain / Park.

MethodUnderlying function
list()UAPI DomainInfo::list_domains
data(?string $domain = null)UAPI domains_data / single_domain_data
addSubdomain(string $subdomain, string $rootDomain, ?string $documentRoot = null)API2 SubDomain::addsubdomain
deleteSubdomain(string $subdomain, string $rootDomain)API2 SubDomain::delsubdomain
addAddonDomain(string $newDomain, string $subdomain, string $documentRoot)API2 AddonDomain::addaddondomain
deleteAddonDomain(string $domain, string $subdomain)API2 AddonDomain::deladdondomain
park(string $domain) / unpark(string $domain)API2 Park
$cpanel->domains()->addSubdomain('api', 'example.com', 'public_html/api');

MySQL

$cpanel->mysql() — UAPI Mysql module.

MethodUnderlying function
databases() / users()list_databases / list_users
createDatabase(string $name) / deleteDatabase(string $name)create_database / delete_database
renameDatabase(string $oldName, string $newName)rename_database
createUser(string $name, string $password) / deleteUser(string $name)create_user / delete_user
setPassword(string $user, string $password)set_password
grant(string $user, string $database, string $privileges = 'ALL PRIVILEGES')set_privileges_on_database
revoke(string $user, string $database)revoke_access_to_database
addHost(string $host)add_host

Remember that cPanel prefixes database and user names with the account name (myaccount_app).

$cpanel->mysql()->createDatabase('myaccount_app');
$cpanel->mysql()->createUser('myaccount_app', 'S3cret!');
$cpanel->mysql()->grant('myaccount_app', 'myaccount_app');

FTP

$cpanel->ftp() — UAPI Ftp module: accounts(), create(), delete() (with optional home-dir destruction), changePassword(), setQuota(), setHomeDir().

$cpanel->ftp()->create('deploy', 'S3cret!', homeDir: 'public_html', quotaMb: 0);
$cpanel->ftp()->delete('deploy', destroyHomeDir: false);

SSL

$cpanel->ssl() — UAPI SSL module: certificates(), installedHosts(), install(), delete(), generateKey(), generateCsr().

$cpanel->ssl()->install('example.com', $certificatePem, $keyPem, $caBundlePem);

Files

$cpanel->files() — UAPI Fileman for content, API2 Fileman::fileop for filesystem operations: list(), read(), write(), info(), mkdir(), delete(), copy(), move(), chmod(), extract(), emptyTrash().

$cpanel->files()->write('public_html', '.htaccess', $rules);
$cpanel->files()->extract('backup.tar.gz', 'public_html');
$cpanel->files()->chmod('public_html/config.php', '0600');

DNS (cPanel zone editor)

$cpanel->dns() — API2 ZoneEdit module. addRecord() automatically maps the value to the right parameter name for the record type (address for A/AAAA, cname for CNAME, txtdata for TXT, exchange for MX…).

$cpanel->dns()->records('example.com', ['type' => 'A']);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10', ttl: 3600);
$cpanel->dns()->editRecord('example.com', line: 22, params: ['address' => '203.0.113.11']);
$cpanel->dns()->removeRecord('example.com', line: 22);

API2 zone records are addressed by line number in the zone file; always re-fetch records after a mutation before addressing another line.

Stats & quotas

$cpanel->stats()quota() (UAPI Quota), bars() (UAPI StatsBar, configurable display list), bandwidth() (API2 Stats::getmonthlybandwidth).

WHM — Accounts

$whm->accounts() — the account lifecycle, WHM API 1.

MethodUnderlying functionNotes
list(?string $search = null, string $searchType = 'user')listacctssearchType: user, domain, owner, ip, package
summary(string $user)accountsummary
create(string $username, string $domain, array $options = [])createacctoptions: plan, password, contactemail, quota, …
remove(string $user, bool $keepDns = false)removeacctDestructive
suspend(string $user, string $reason = '') / unsuspend(string $user)suspendacct / unsuspendacct
changePassword(string $user, string $password)passwd
modify(string $user, array $options)modifyacct
changePlan(string $user, string $plan)changepackage
domainOwner(string $domain)domainuserdata
createUserSession(string $user, string $service = 'cpaneld')create_user_sessionOne-click SSO URL into the user's cPanel
bandwidth(?string $user = null, ?string $month = null, ?string $year = null)showbwBandwidth usage, optionally filtered
limitBandwidth(string $user, int $limitMb)limitbw
$session = $whm->accounts()->createUserSession('customer1');
$redirectUrl = $session->data('url');

WHM — Resellers

$whm->resellers() — the full reseller lifecycle for multi-tier hosting.

MethodUnderlying functionNotes
list()listresellers
stats(string $reseller)resellerstatsDisk/bandwidth totals across owned accounts
accounts(string $reseller)acctcountsUsed/limit account counts
promote(string $user, bool $ownsSelf = false)setupresellerTurns an existing account into a reseller
demote(string $user)unsetupreseller
setLimits(string $user, array $limits)setresellerlimitse.g. enable_account_limit, account_limit, diskspace_limit
setPackageLimit(string $user, string $package, bool $allowed, ?int $number = null)setresellerpackagelimitRestrict which plans a reseller may sell
setAcls(string $reseller, array $acls)setaclsFine-grained privilege grants
setMainIp(string $user, string $ip)setresellermainip
setNameservers(string $user, array $nameservers)setresellernameservers
suspendAccounts(string $reseller) / unsuspendAccounts(string $reseller)suspendreseller / unsuspendresellerSuspends the reseller and all owned accounts
$whm->resellers()->promote('reseller1');
$whm->resellers()->setLimits('reseller1', ['enable_account_limit' => 1, 'account_limit' => 30]);
$whm->resellers()->setPackageLimit('reseller1', 'starter', allowed: true, number: 20);

WHM — Packages

$whm->packages()list(), create(), update(), delete() around listpkgs / addpkg / editpkg / killpkg.

$whm->packages()->create('starter', ['quota' => 5120, 'bwlimit' => 51200, 'maxaddons' => 1]);

WHM — DNS zones

$whm->dnsZones() — full zone lifecycle: list(), dump(), create(), delete(), addRecord(), editRecord(), removeRecord(), reset().

$whm->dnsZones()->create('customer1.com', '203.0.113.10');
$whm->dnsZones()->addRecord('customer1.com', [
'name' => 'mail',
'type' => 'A',
'address' => '203.0.113.10',
'ttl' => 3600,
]);

WHM — IP addresses

$whm->ips() — IP pool management for dedicated-IP offers.

MethodUnderlying function
list()listips
add(string $ip, string $netmask)addips
delete(string $ip)delip
assignToSite(string $domain, string $ip) / assignToUser(string $user, string $ip)setsiteip
usage()get_shared_ip
$whm->ips()->add('203.0.113.25', '255.255.255.0');
$whm->ips()->assignToSite('customer1.com', '203.0.113.25');

WHM — Security (cPHulk)

$whm->security() — brute-force protection management, the bread and butter of hosting support.

MethodUnderlying function
enableCphulk() / disableCphulk()enable_cphulk / disable_cphulk
whitelist(string $ip, string $comment = '') / blacklist(...)create_cphulk_record
listWhitelist() / listBlacklist()read_cphulk_records
removeFromWhitelist(string $ip) / removeFromBlacklist(string $ip)delete_cphulk_record
unblockBrute(string $ip)flush_cphulk_login_history_for_ips
flushLoginHistory()flush_cphulk_login_history
$whm->security()->unblockBrute('198.51.100.7');
$whm->security()->whitelist('203.0.113.50', 'office VPN');

WHM — Backups & restores

$whm->backups() — backup configuration and the account restore queue.

MethodUnderlying functionNotes
config() / setConfig(array $settings)backup_config_get / backup_config_set
users()backup_user_listUsers with backup metadata
dates()backup_date_listAvailable restore points
userBackups(string $user)backup_set_list
queueRestore(string $user, string $restorePoint, array $options = [])restore_queue_add_taskDefaults: keep IP, restore MySQL/subdomains/mail config
activateRestoreQueue()restore_queue_activateStarts processing queued restores
restoreQueueState()restore_queue_statePoll for progress
clearCompletedRestores()restore_queue_clear_completed_tasks
$whm->backups()->queueRestore('customer1', '2026-07-20');
$whm->backups()->activateRestoreQueue();

WHM — PHP versions

$whm->php() — MultiPHP management per virtual host.

MethodUnderlying function
installedVersions()php_get_installed_versions
systemDefault() / setSystemDefault(string $version)php_get_system_default_version / php_set_system_default_version
vhostVersions(string ...$vhosts)php_get_vhost_versions
setVhostVersion(string $version, string ...$vhosts)php_set_vhost_versions
handlers(string $version) / setHandler(string $version, string $handler)php_get_handlers / php_set_handler

Versions use EasyApache identifiers (ea-php83), not bare numbers.

$whm->php()->setVhostVersion('ea-php83', 'example.com', 'shop.example.com');

WHM — SSL & AutoSSL

$whm->autoSsl() — server-wide certificate automation plus manual installs with root privileges.

MethodUnderlying function
providers() / setProvider(string $provider)get_autossl_providers / set_autossl_provider
checkAllUsers()start_autossl_check_for_all_users
checkUser(string $user)start_autossl_check_for_one_user
enableForUser(string $user) / disableForUser(string $user)set_autossl_feature_for_users
installCertificate(string $domain, string $cert, string $key, ?string $caBundle = null)installssl
certificateInfo(string $domain)fetch_ssl_vhosts
$whm->autoSsl()->setProvider('LetsEncrypt');
$whm->autoSsl()->checkUser('customer1');

WHM — Server configuration

$whm->config() — Tweak Settings and global server preferences.

MethodUnderlying function
tweakSetting(string $key, string $module = 'Main')get_tweaksetting
setTweakSetting(string $key, string|int $value, string $module = 'Main')set_tweaksetting
updatePreferences() / setUpdatePreferences(array $settings)get_update_config / update_updateconf
hostname() / setHostname(string $hostname)gethostname / sethostname
nameserverConfig()nameserverconfig
$whm->config()->setTweakSetting('maxemailsperhour', 200);

WHM — Server

$whm->server()version(), hostname(), loadAverage(), serviceStatus(), restartService().

$whm->server()->serviceStatus('httpd');
$whm->server()->restartService('exim');

Responses

All calls return an immutable ApiResponse that normalizes the three wire formats (UAPI envelope, API2 cpanelresult, WHM metadata):

$response = $cpanel->mysql()->databases();
$response->success; // bool$response->data; // mixed — the payload's data section$response->data('acct'); // keyed access with optional default$response->errors; // list<string>$response->messages; // list<string>$response->warnings; // list<string>$response->raw; // the complete decoded JSON payload

data() is null-safe: it returns the default when the payload has no such key or when data is not an array.

Error Handling

All SDK exceptions implement CpanelSdkExceptionInterface, so a single catch covers everything:

ExceptionThrown whenExtras
ApiExceptionThe API answered but reported a failure (module methods validate automatically)getErrors(): array, getRaw(): array
AuthenticationExceptionCredentials are missing, or the server answered HTTP 401/403thrown before any request when credentials are empty
TransportExceptionNetwork error, TLS failure, timeout, or a non-JSON response bodywraps the underlying symfony/http-client exception
useChuckBartowski\CpanelSdk\Exception\ApiException;
useChuckBartowski\CpanelSdk\Exception\CpanelSdkExceptionInterface;
try {
$cpanel->email()->create('support', 'example.com', $password);
} catch (ApiException$e) {
$this->logger->warning('cPanel rejected the mailbox', ['errors' => $e->getErrors()]);
} catch (CpanelSdkExceptionInterface$e) {
thrownewProvisioningUnavailableException(previous: $e);
}

To inspect a failed response without exceptions, use the client directly — client-level methods return the response as-is:

$response = $cpanel->client()->uapi('Email', 'add_pop', $params, 'POST');
if (!$response->success) {
// $response->errors, $response->raw
}

Testing

The suite runs entirely offline against MockHttpClient:

composer install
vendor/bin/phpunit

To test your own services, inject a CpanelClient/WhmClient built with a mock:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Cpanel;
useSymfony\Component\HttpClient\MockHttpClient;
useSymfony\Component\HttpClient\Response\JsonMockResponse;
$http = newMockHttpClient(newJsonMockResponse(['status' => 1, 'data' => []]));
$cpanel = newCpanel(newCpanelClient('host', 'user', 'token', 2083, true, 30.0, $http));

Security Notes

  • API tokens are passed with #[\SensitiveParameter], so they never appear in stack traces.
  • Keep tokens in .env.local or your secret vault — never commit them.
  • Scope WHM tokens to the minimal privilege set in WHM » Manage API Tokens (e.g. deny Everything, allow only account functions).
  • Leave verify_ssl: true in production; the option exists solely for self-signed development servers.
  • removeacct and delete_ftp destroy=1 are irreversible — gate them behind confirmation flows in your application.

WHMCS module

A ready-to-use WHMCS provisioning module ships in whmcs/modules/servers/cpanelsdk/. It automates cPanel account provisioning through WHM using this SDK — create, suspend, unsuspend, terminate, change password, change package, and one-click SSO into cPanel.

Install

  1. composer require chuckbartowski/cpanel-sdk in your WHMCS root (so the SDK is autoloaded).
  2. Copy the cpanelsdk folder into <whmcs>/modules/servers/.
  3. In WHMCS, add a server (System Settings » Servers) with Type: cPanel (SDK), the WHM hostname, username root, and your WHM API token in the Access Hash field.
  4. Point a product at the server and set the Package config option to the WHM plan name.
OperationWHM function used
Create / Suspend / Unsuspend / Terminatecreateacct / suspendacct / unsuspendacct / removeacct
Change password / packagepasswd / changepackage
One-click logincreate_user_session

License

MIT

About

PHP SDK for the cPanel & WHM APIs (UAPI, API2, WHM API 1).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

cPanel & WHM SDK for PHP

🖥 cPanel & WHM SDK for PHP

A modern, fully typed PHP SDK for driving cPanel (UAPI + API2) and WHM (API 1).

PHP VersionSymfonyTestsPackagistLicense

Email · DNS · MySQL · SSL · FTP · Files · Accounts · Resellers · Packages · AutoSSL · Backups

Installation · Quick Start · API Reference · Error Handling


$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);
$whm->accounts()->createUserSession('customer1');

Framework-agnostic core — usable from any PHP project, script, or worker — with an optional bundle for first-class Symfony integration. Authenticated with API tokens, typed exceptions, and a comment-free, strictly typed codebase (PHP 8.2+, declare(strict_types=1) everywhere).


Table of Contents


Features

  • Full coverage of the three cPanel API surfaces: UAPI (the modern cPanel API), API2 (legacy but still required for zone editing, subdomains, addon domains, file operations), and WHM API 1 (server administration).
  • Framework-agnostic: two plain facades (Cpanel, Whm) you can instantiate anywhere; only hard dependency is symfony/http-client, a standalone component that works in any PHP project.
  • Token authentication only — no passwords, no sessions, no cookies. Uses the official Authorization: cpanel user:token / Authorization: whm user:token schemes.
  • High-level, discoverable modules grouped by domain: email, MySQL, DNS, SSL, files, accounts, resellers, backups, PHP versions, security…
  • A single normalized response object (ApiResponse) regardless of which underlying API answered — you never parse cpanelresult or metadata envelopes yourself.
  • Typed exception hierarchy under one marker interface, so you can catch narrowly or broadly.
  • Escape hatches everywhere: any endpoint not wrapped by a module remains one method call away.
  • Optional Symfony bundle with semantic configuration and autowirable services.
  • Fully unit-tested against MockHttpClient (no network required).

Requirements

DependencyVersion
PHP>= 8.2
cPanel/WHMany version supporting API tokens (v64+)
Symfony6.4 LTS or 7.x — optional, only for the bundle integration

You will need at least one of:

  • a cPanel API token — created in cPanel » Security » Manage API Tokens
  • a WHM API token — created in WHM » Development » Manage API Tokens

Installation

The package is published on Packagist:

composer require chuckbartowski/cpanel-sdk

Quick Start (plain PHP)

No framework required — build the clients and go:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Client\WhmClient;
useChuckBartowski\CpanelSdk\Cpanel;
useChuckBartowski\CpanelSdk\Whm;
$cpanel = newCpanel(newCpanelClient(
host: 'server.example.com',
username: 'myaccount',
token: getenv('CPANEL_API_TOKEN'),
port: 2083,
));
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10');
$whm = newWhm(newWhmClient(
host: 'server.example.com',
username: 'root',
token: getenv('WHM_API_TOKEN'),
port: 2087,
));
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);

Client constructor signature (identical for both clients):

newCpanelClient(
string $host,
string $username,
string $token,
int $port, // 2083 for cPanel, 2087 for WHM
bool $verifySsl = true,
float $timeout = 30.0,
?HttpClientInterface $httpClient = null, // inject your own (retries, proxy, mock…)
);

Symfony Integration (optional)

A ready-made bundle wires everything into the container. Register it:

// config/bundles.phpreturn [
ChuckBartowski\CpanelSdk\CpanelSdkBundle::class => ['all' => true],
];

Then create config/packages/cpanel_sdk.yaml:

cpanel_sdk:
host: '%env(CPANEL_HOST)%'verify_ssl: truetimeout: 30cpanel:
username: '%env(CPANEL_USERNAME)%'token: '%env(CPANEL_API_TOKEN)%'port: 2083whm:
username: '%env(WHM_USERNAME)%'token: '%env(WHM_API_TOKEN)%'port: 2087

And the matching environment variables:

# .env.localCPANEL_HOST=server.example.comCPANEL_USERNAME=myaccountCPANEL_API_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWHM_USERNAME=rootWHM_API_TOKEN=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

Configuration reference

KeyTypeDefaultDescription
hoststringrequiredHostname of the cPanel/WHM server (no scheme, no port)
verify_sslbooltrueTLS peer/host verification; disable only for self-signed dev servers
timeoutfloat30.0Per-request timeout in seconds
cpanel.usernamestring''cPanel account name
cpanel.tokenstring''cPanel API token
cpanel.portint2083cPanel TLS port
whm.usernamestring''WHM user (usually root or a reseller)
whm.tokenstring''WHM API token
whm.portint2087WHM TLS port

The cpanel and whm sections are independent — configure only the side you need. Calling a client with missing credentials throws an AuthenticationException immediately, before any network request is made.

The bundle reuses your application's http_client service when available (so scoped clients, retry strategies, and profiler integration all apply), and falls back to a native client otherwise.

Architecture

src/
├── CpanelSdkBundle.php Symfony bundle: config tree + service wiring
├── Cpanel.php Facade: entry point for cPanel-level modules
├── Whm.php Facade: entry point for WHM-level modules
├── Client/
│ ├── AbstractClient.php Shared HTTP transport, auth header, error mapping
│ ├── CpanelClient.php uapi() and api2() generic executors
│ └── WhmClient.php call() (WHM API 1) and cpanelUapi() (root proxy)
├── Response/
│ └── ApiResponse.php Immutable, normalized response for all 3 API formats
├── Exception/
│ ├── CpanelSdkExceptionInterface.php
│ ├── ApiException.php API answered but reported a failure
│ ├── AuthenticationException.php
│ └── TransportException.php Network / TLS / timeout / invalid JSON
└── Api/
├── Cpanel/ EmailApi, DomainApi, MysqlApi, FtpApi,
│ SslApi, FileApi, DnsApi, StatsApi
└── Whm/ AccountApi, ResellerApi, PackageApi, DnsZoneApi,
IpApi, SecurityApi, BackupApi, PhpApi,
AutoSslApi, ConfigApi, ServerApi

Design decisions:

  • Facade + lazy modules: Cpanel/Whm instantiate each module on first use and cache it, so the DI container only carries four services.
  • Modules always validate: every module method calls ensureSuccess() internally and throws ApiException on failure. If you need to inspect a failed response without an exception, drop down to the client level.
  • Nothing is sealed off: the clients' generic methods accept any module/function/parameter combination, so a cPanel endpoint added tomorrow is usable today.

Usage

Standalone, instantiate the facades as shown in the Quick Start. In Symfony, both facades are autowirable in controllers, services, commands, and message handlers.

The Cpanel facade

useChuckBartowski\CpanelSdk\Cpanel;
finalclass MailboxProvisioner
{
publicfunction__construct(privatereadonlyCpanel$cpanel)
{
}
publicfunctionprovision(string$localPart, string$domain, string$password): void
{
$this->cpanel->email()->create($localPart, $domain, $password, quotaMb: 512);
}
}

The Whm facade

useChuckBartowski\CpanelSdk\Whm;
finalclass HostingAccountManager
{
publicfunction__construct(privatereadonlyWhm$whm)
{
}
publicfunctionopen(string$username, string$domain): void
{
$this->whm->accounts()->create($username, $domain, [
'plan' => 'starter',
'contactemail' => 'billing@example.com',
]);
}
publicfunctionsuspendForNonPayment(string$username): void
{
$this->whm->accounts()->suspend($username, 'unpaid invoice');
}
}

Generic calls (escape hatch)

Any endpoint not covered by a module remains reachable:

$cpanel->client()->uapi('Batch', 'strict', ['command' => $commands], 'POST');
$cpanel->client()->api2('Cron', 'listcron');
$whm->client()->call('sethostname', ['hostname' => 'srv2.example.com'], 'POST');
$whm->client()->cpanelUapi('customer1', 'Email', 'list_pops');

cpanelUapi() runs a UAPI function as any cPanel account through the WHM token — the standard pattern for hosting control panels where only the root/reseller token is stored.

API Reference

Every method returns an ApiResponse and throws on failure (see Error Handling). Named arguments are shown where they improve readability.

Email

$cpanel->email() — UAPI Email module.

MethodUnderlying functionNotes
accounts(?string $domain = null)list_pops_with_diskIncludes disk usage per mailbox
create(string $localPart, string $domain, string $password, int $quotaMb = 0)add_pop0 = unlimited quota
delete(string $localPart, string $domain)delete_pop
changePassword(string $localPart, string $domain, string $password)passwd_pop
setQuota(string $localPart, string $domain, int $quotaMb)edit_pop_quota
forwarders(?string $domain = null)list_forwarders
addForwarder(string $domain, string $localPart, string $destination)add_forwarder
deleteForwarder(string $address, string $forwarder)delete_forwarder
mailDirUsage(string $localPart, string $domain)get_pop_quota
$cpanel->email()->accounts('example.com');
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->email()->addForwarder('example.com', 'contact', 'inbox@elsewhere.com');

Domains

$cpanel->domains() — UAPI DomainInfo + API2 SubDomain / AddonDomain / Park.

MethodUnderlying function
list()UAPI DomainInfo::list_domains
data(?string $domain = null)UAPI domains_data / single_domain_data
addSubdomain(string $subdomain, string $rootDomain, ?string $documentRoot = null)API2 SubDomain::addsubdomain
deleteSubdomain(string $subdomain, string $rootDomain)API2 SubDomain::delsubdomain
addAddonDomain(string $newDomain, string $subdomain, string $documentRoot)API2 AddonDomain::addaddondomain
deleteAddonDomain(string $domain, string $subdomain)API2 AddonDomain::deladdondomain
park(string $domain) / unpark(string $domain)API2 Park
$cpanel->domains()->addSubdomain('api', 'example.com', 'public_html/api');

MySQL

$cpanel->mysql() — UAPI Mysql module.

MethodUnderlying function
databases() / users()list_databases / list_users
createDatabase(string $name) / deleteDatabase(string $name)create_database / delete_database
renameDatabase(string $oldName, string $newName)rename_database
createUser(string $name, string $password) / deleteUser(string $name)create_user / delete_user
setPassword(string $user, string $password)set_password
grant(string $user, string $database, string $privileges = 'ALL PRIVILEGES')set_privileges_on_database
revoke(string $user, string $database)revoke_access_to_database
addHost(string $host)add_host

Remember that cPanel prefixes database and user names with the account name (myaccount_app).

$cpanel->mysql()->createDatabase('myaccount_app');
$cpanel->mysql()->createUser('myaccount_app', 'S3cret!');
$cpanel->mysql()->grant('myaccount_app', 'myaccount_app');

FTP

$cpanel->ftp() — UAPI Ftp module: accounts(), create(), delete() (with optional home-dir destruction), changePassword(), setQuota(), setHomeDir().

$cpanel->ftp()->create('deploy', 'S3cret!', homeDir: 'public_html', quotaMb: 0);
$cpanel->ftp()->delete('deploy', destroyHomeDir: false);

SSL

$cpanel->ssl() — UAPI SSL module: certificates(), installedHosts(), install(), delete(), generateKey(), generateCsr().

$cpanel->ssl()->install('example.com', $certificatePem, $keyPem, $caBundlePem);

Files

$cpanel->files() — UAPI Fileman for content, API2 Fileman::fileop for filesystem operations: list(), read(), write(), info(), mkdir(), delete(), copy(), move(), chmod(), extract(), emptyTrash().

$cpanel->files()->write('public_html', '.htaccess', $rules);
$cpanel->files()->extract('backup.tar.gz', 'public_html');
$cpanel->files()->chmod('public_html/config.php', '0600');

DNS (cPanel zone editor)

$cpanel->dns() — API2 ZoneEdit module. addRecord() automatically maps the value to the right parameter name for the record type (address for A/AAAA, cname for CNAME, txtdata for TXT, exchange for MX…).

$cpanel->dns()->records('example.com', ['type' => 'A']);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10', ttl: 3600);
$cpanel->dns()->editRecord('example.com', line: 22, params: ['address' => '203.0.113.11']);
$cpanel->dns()->removeRecord('example.com', line: 22);

API2 zone records are addressed by line number in the zone file; always re-fetch records after a mutation before addressing another line.

Stats & quotas

$cpanel->stats()quota() (UAPI Quota), bars() (UAPI StatsBar, configurable display list), bandwidth() (API2 Stats::getmonthlybandwidth).

WHM — Accounts

$whm->accounts() — the account lifecycle, WHM API 1.

MethodUnderlying functionNotes
list(?string $search = null, string $searchType = 'user')listacctssearchType: user, domain, owner, ip, package
summary(string $user)accountsummary
create(string $username, string $domain, array $options = [])createacctoptions: plan, password, contactemail, quota, …
remove(string $user, bool $keepDns = false)removeacctDestructive
suspend(string $user, string $reason = '') / unsuspend(string $user)suspendacct / unsuspendacct
changePassword(string $user, string $password)passwd
modify(string $user, array $options)modifyacct
changePlan(string $user, string $plan)changepackage
domainOwner(string $domain)domainuserdata
createUserSession(string $user, string $service = 'cpaneld')create_user_sessionOne-click SSO URL into the user's cPanel
bandwidth(?string $user = null, ?string $month = null, ?string $year = null)showbwBandwidth usage, optionally filtered
limitBandwidth(string $user, int $limitMb)limitbw
$session = $whm->accounts()->createUserSession('customer1');
$redirectUrl = $session->data('url');

WHM — Resellers

$whm->resellers() — the full reseller lifecycle for multi-tier hosting.

MethodUnderlying functionNotes
list()listresellers
stats(string $reseller)resellerstatsDisk/bandwidth totals across owned accounts
accounts(string $reseller)acctcountsUsed/limit account counts
promote(string $user, bool $ownsSelf = false)setupresellerTurns an existing account into a reseller
demote(string $user)unsetupreseller
setLimits(string $user, array $limits)setresellerlimitse.g. enable_account_limit, account_limit, diskspace_limit
setPackageLimit(string $user, string $package, bool $allowed, ?int $number = null)setresellerpackagelimitRestrict which plans a reseller may sell
setAcls(string $reseller, array $acls)setaclsFine-grained privilege grants
setMainIp(string $user, string $ip)setresellermainip
setNameservers(string $user, array $nameservers)setresellernameservers
suspendAccounts(string $reseller) / unsuspendAccounts(string $reseller)suspendreseller / unsuspendresellerSuspends the reseller and all owned accounts
$whm->resellers()->promote('reseller1');
$whm->resellers()->setLimits('reseller1', ['enable_account_limit' => 1, 'account_limit' => 30]);
$whm->resellers()->setPackageLimit('reseller1', 'starter', allowed: true, number: 20);

WHM — Packages

$whm->packages()list(), create(), update(), delete() around listpkgs / addpkg / editpkg / killpkg.

$whm->packages()->create('starter', ['quota' => 5120, 'bwlimit' => 51200, 'maxaddons' => 1]);

WHM — DNS zones

$whm->dnsZones() — full zone lifecycle: list(), dump(), create(), delete(), addRecord(), editRecord(), removeRecord(), reset().

$whm->dnsZones()->create('customer1.com', '203.0.113.10');
$whm->dnsZones()->addRecord('customer1.com', [
'name' => 'mail',
'type' => 'A',
'address' => '203.0.113.10',
'ttl' => 3600,
]);

WHM — IP addresses

$whm->ips() — IP pool management for dedicated-IP offers.

MethodUnderlying function
list()listips
add(string $ip, string $netmask)addips
delete(string $ip)delip
assignToSite(string $domain, string $ip) / assignToUser(string $user, string $ip)setsiteip
usage()get_shared_ip
$whm->ips()->add('203.0.113.25', '255.255.255.0');
$whm->ips()->assignToSite('customer1.com', '203.0.113.25');

WHM — Security (cPHulk)

$whm->security() — brute-force protection management, the bread and butter of hosting support.

MethodUnderlying function
enableCphulk() / disableCphulk()enable_cphulk / disable_cphulk
whitelist(string $ip, string $comment = '') / blacklist(...)create_cphulk_record
listWhitelist() / listBlacklist()read_cphulk_records
removeFromWhitelist(string $ip) / removeFromBlacklist(string $ip)delete_cphulk_record
unblockBrute(string $ip)flush_cphulk_login_history_for_ips
flushLoginHistory()flush_cphulk_login_history
$whm->security()->unblockBrute('198.51.100.7');
$whm->security()->whitelist('203.0.113.50', 'office VPN');

WHM — Backups & restores

$whm->backups() — backup configuration and the account restore queue.

MethodUnderlying functionNotes
config() / setConfig(array $settings)backup_config_get / backup_config_set
users()backup_user_listUsers with backup metadata
dates()backup_date_listAvailable restore points
userBackups(string $user)backup_set_list
queueRestore(string $user, string $restorePoint, array $options = [])restore_queue_add_taskDefaults: keep IP, restore MySQL/subdomains/mail config
activateRestoreQueue()restore_queue_activateStarts processing queued restores
restoreQueueState()restore_queue_statePoll for progress
clearCompletedRestores()restore_queue_clear_completed_tasks
$whm->backups()->queueRestore('customer1', '2026-07-20');
$whm->backups()->activateRestoreQueue();

WHM — PHP versions

$whm->php() — MultiPHP management per virtual host.

MethodUnderlying function
installedVersions()php_get_installed_versions
systemDefault() / setSystemDefault(string $version)php_get_system_default_version / php_set_system_default_version
vhostVersions(string ...$vhosts)php_get_vhost_versions
setVhostVersion(string $version, string ...$vhosts)php_set_vhost_versions
handlers(string $version) / setHandler(string $version, string $handler)php_get_handlers / php_set_handler

Versions use EasyApache identifiers (ea-php83), not bare numbers.

$whm->php()->setVhostVersion('ea-php83', 'example.com', 'shop.example.com');

WHM — SSL & AutoSSL

$whm->autoSsl() — server-wide certificate automation plus manual installs with root privileges.

MethodUnderlying function
providers() / setProvider(string $provider)get_autossl_providers / set_autossl_provider
checkAllUsers()start_autossl_check_for_all_users
checkUser(string $user)start_autossl_check_for_one_user
enableForUser(string $user) / disableForUser(string $user)set_autossl_feature_for_users
installCertificate(string $domain, string $cert, string $key, ?string $caBundle = null)installssl
certificateInfo(string $domain)fetch_ssl_vhosts
$whm->autoSsl()->setProvider('LetsEncrypt');
$whm->autoSsl()->checkUser('customer1');

WHM — Server configuration

$whm->config() — Tweak Settings and global server preferences.

MethodUnderlying function
tweakSetting(string $key, string $module = 'Main')get_tweaksetting
setTweakSetting(string $key, string|int $value, string $module = 'Main')set_tweaksetting
updatePreferences() / setUpdatePreferences(array $settings)get_update_config / update_updateconf
hostname() / setHostname(string $hostname)gethostname / sethostname
nameserverConfig()nameserverconfig
$whm->config()->setTweakSetting('maxemailsperhour', 200);

WHM — Server

$whm->server()version(), hostname(), loadAverage(), serviceStatus(), restartService().

$whm->server()->serviceStatus('httpd');
$whm->server()->restartService('exim');

Responses

All calls return an immutable ApiResponse that normalizes the three wire formats (UAPI envelope, API2 cpanelresult, WHM metadata):

$response = $cpanel->mysql()->databases();
$response->success; // bool$response->data; // mixed — the payload's data section$response->data('acct'); // keyed access with optional default$response->errors; // list<string>$response->messages; // list<string>$response->warnings; // list<string>$response->raw; // the complete decoded JSON payload

data() is null-safe: it returns the default when the payload has no such key or when data is not an array.

Error Handling

All SDK exceptions implement CpanelSdkExceptionInterface, so a single catch covers everything:

ExceptionThrown whenExtras
ApiExceptionThe API answered but reported a failure (module methods validate automatically)getErrors(): array, getRaw(): array
AuthenticationExceptionCredentials are missing, or the server answered HTTP 401/403thrown before any request when credentials are empty
TransportExceptionNetwork error, TLS failure, timeout, or a non-JSON response bodywraps the underlying symfony/http-client exception
useChuckBartowski\CpanelSdk\Exception\ApiException;
useChuckBartowski\CpanelSdk\Exception\CpanelSdkExceptionInterface;
try {
$cpanel->email()->create('support', 'example.com', $password);
} catch (ApiException$e) {
$this->logger->warning('cPanel rejected the mailbox', ['errors' => $e->getErrors()]);
} catch (CpanelSdkExceptionInterface$e) {
thrownewProvisioningUnavailableException(previous: $e);
}

To inspect a failed response without exceptions, use the client directly — client-level methods return the response as-is:

$response = $cpanel->client()->uapi('Email', 'add_pop', $params, 'POST');
if (!$response->success) {
// $response->errors, $response->raw
}

Testing

The suite runs entirely offline against MockHttpClient:

composer install
vendor/bin/phpunit

To test your own services, inject a CpanelClient/WhmClient built with a mock:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Cpanel;
useSymfony\Component\HttpClient\MockHttpClient;
useSymfony\Component\HttpClient\Response\JsonMockResponse;
$http = newMockHttpClient(newJsonMockResponse(['status' => 1, 'data' => []]));
$cpanel = newCpanel(newCpanelClient('host', 'user', 'token', 2083, true, 30.0, $http));

Security Notes

  • API tokens are passed with #[\SensitiveParameter], so they never appear in stack traces.
  • Keep tokens in .env.local or your secret vault — never commit them.
  • Scope WHM tokens to the minimal privilege set in WHM » Manage API Tokens (e.g. deny Everything, allow only account functions).
  • Leave verify_ssl: true in production; the option exists solely for self-signed development servers.
  • removeacct and delete_ftp destroy=1 are irreversible — gate them behind confirmation flows in your application.

WHMCS module

A ready-to-use WHMCS provisioning module ships in whmcs/modules/servers/cpanelsdk/. It automates cPanel account provisioning through WHM using this SDK — create, suspend, unsuspend, terminate, change password, change package, and one-click SSO into cPanel.

Install

  1. composer require chuckbartowski/cpanel-sdk in your WHMCS root (so the SDK is autoloaded).
  2. Copy the cpanelsdk folder into <whmcs>/modules/servers/.
  3. In WHMCS, add a server (System Settings » Servers) with Type: cPanel (SDK), the WHM hostname, username root, and your WHM API token in the Access Hash field.
  4. Point a product at the server and set the Package config option to the WHM plan name.
OperationWHM function used
Create / Suspend / Unsuspend / Terminatecreateacct / suspendacct / unsuspendacct / removeacct
Change password / packagepasswd / changepackage
One-click logincreate_user_session

License

MIT

About

PHP SDK for the cPanel & WHM APIs (UAPI, API2, WHM API 1).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

cPanel & WHM SDK for PHP

🖥 cPanel & WHM SDK for PHP

A modern, fully typed PHP SDK for driving cPanel (UAPI + API2) and WHM (API 1).

PHP VersionSymfonyTestsPackagistLicense

Email · DNS · MySQL · SSL · FTP · Files · Accounts · Resellers · Packages · AutoSSL · Backups

Installation · Quick Start · API Reference · Error Handling


$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);
$whm->accounts()->createUserSession('customer1');

Framework-agnostic core — usable from any PHP project, script, or worker — with an optional bundle for first-class Symfony integration. Authenticated with API tokens, typed exceptions, and a comment-free, strictly typed codebase (PHP 8.2+, declare(strict_types=1) everywhere).


Table of Contents


Features

  • Full coverage of the three cPanel API surfaces: UAPI (the modern cPanel API), API2 (legacy but still required for zone editing, subdomains, addon domains, file operations), and WHM API 1 (server administration).
  • Framework-agnostic: two plain facades (Cpanel, Whm) you can instantiate anywhere; only hard dependency is symfony/http-client, a standalone component that works in any PHP project.
  • Token authentication only — no passwords, no sessions, no cookies. Uses the official Authorization: cpanel user:token / Authorization: whm user:token schemes.
  • High-level, discoverable modules grouped by domain: email, MySQL, DNS, SSL, files, accounts, resellers, backups, PHP versions, security…
  • A single normalized response object (ApiResponse) regardless of which underlying API answered — you never parse cpanelresult or metadata envelopes yourself.
  • Typed exception hierarchy under one marker interface, so you can catch narrowly or broadly.
  • Escape hatches everywhere: any endpoint not wrapped by a module remains one method call away.
  • Optional Symfony bundle with semantic configuration and autowirable services.
  • Fully unit-tested against MockHttpClient (no network required).

Requirements

DependencyVersion
PHP>= 8.2
cPanel/WHMany version supporting API tokens (v64+)
Symfony6.4 LTS or 7.x — optional, only for the bundle integration

You will need at least one of:

  • a cPanel API token — created in cPanel » Security » Manage API Tokens
  • a WHM API token — created in WHM » Development » Manage API Tokens

Installation

The package is published on Packagist:

composer require chuckbartowski/cpanel-sdk

Quick Start (plain PHP)

No framework required — build the clients and go:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Client\WhmClient;
useChuckBartowski\CpanelSdk\Cpanel;
useChuckBartowski\CpanelSdk\Whm;
$cpanel = newCpanel(newCpanelClient(
host: 'server.example.com',
username: 'myaccount',
token: getenv('CPANEL_API_TOKEN'),
port: 2083,
));
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10');
$whm = newWhm(newWhmClient(
host: 'server.example.com',
username: 'root',
token: getenv('WHM_API_TOKEN'),
port: 2087,
));
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);

Client constructor signature (identical for both clients):

newCpanelClient(
string $host,
string $username,
string $token,
int $port, // 2083 for cPanel, 2087 for WHM
bool $verifySsl = true,
float $timeout = 30.0,
?HttpClientInterface $httpClient = null, // inject your own (retries, proxy, mock…)
);

Symfony Integration (optional)

A ready-made bundle wires everything into the container. Register it:

// config/bundles.phpreturn [
ChuckBartowski\CpanelSdk\CpanelSdkBundle::class => ['all' => true],
];

Then create config/packages/cpanel_sdk.yaml:

cpanel_sdk:
host: '%env(CPANEL_HOST)%'verify_ssl: truetimeout: 30cpanel:
username: '%env(CPANEL_USERNAME)%'token: '%env(CPANEL_API_TOKEN)%'port: 2083whm:
username: '%env(WHM_USERNAME)%'token: '%env(WHM_API_TOKEN)%'port: 2087

And the matching environment variables:

# .env.localCPANEL_HOST=server.example.comCPANEL_USERNAME=myaccountCPANEL_API_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWHM_USERNAME=rootWHM_API_TOKEN=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

Configuration reference

KeyTypeDefaultDescription
hoststringrequiredHostname of the cPanel/WHM server (no scheme, no port)
verify_sslbooltrueTLS peer/host verification; disable only for self-signed dev servers
timeoutfloat30.0Per-request timeout in seconds
cpanel.usernamestring''cPanel account name
cpanel.tokenstring''cPanel API token
cpanel.portint2083cPanel TLS port
whm.usernamestring''WHM user (usually root or a reseller)
whm.tokenstring''WHM API token
whm.portint2087WHM TLS port

The cpanel and whm sections are independent — configure only the side you need. Calling a client with missing credentials throws an AuthenticationException immediately, before any network request is made.

The bundle reuses your application's http_client service when available (so scoped clients, retry strategies, and profiler integration all apply), and falls back to a native client otherwise.

Architecture

src/
├── CpanelSdkBundle.php Symfony bundle: config tree + service wiring
├── Cpanel.php Facade: entry point for cPanel-level modules
├── Whm.php Facade: entry point for WHM-level modules
├── Client/
│ ├── AbstractClient.php Shared HTTP transport, auth header, error mapping
│ ├── CpanelClient.php uapi() and api2() generic executors
│ └── WhmClient.php call() (WHM API 1) and cpanelUapi() (root proxy)
├── Response/
│ └── ApiResponse.php Immutable, normalized response for all 3 API formats
├── Exception/
│ ├── CpanelSdkExceptionInterface.php
│ ├── ApiException.php API answered but reported a failure
│ ├── AuthenticationException.php
│ └── TransportException.php Network / TLS / timeout / invalid JSON
└── Api/
├── Cpanel/ EmailApi, DomainApi, MysqlApi, FtpApi,
│ SslApi, FileApi, DnsApi, StatsApi
└── Whm/ AccountApi, ResellerApi, PackageApi, DnsZoneApi,
IpApi, SecurityApi, BackupApi, PhpApi,
AutoSslApi, ConfigApi, ServerApi

Design decisions:

  • Facade + lazy modules: Cpanel/Whm instantiate each module on first use and cache it, so the DI container only carries four services.
  • Modules always validate: every module method calls ensureSuccess() internally and throws ApiException on failure. If you need to inspect a failed response without an exception, drop down to the client level.
  • Nothing is sealed off: the clients' generic methods accept any module/function/parameter combination, so a cPanel endpoint added tomorrow is usable today.

Usage

Standalone, instantiate the facades as shown in the Quick Start. In Symfony, both facades are autowirable in controllers, services, commands, and message handlers.

The Cpanel facade

useChuckBartowski\CpanelSdk\Cpanel;
finalclass MailboxProvisioner
{
publicfunction__construct(privatereadonlyCpanel$cpanel)
{
}
publicfunctionprovision(string$localPart, string$domain, string$password): void
{
$this->cpanel->email()->create($localPart, $domain, $password, quotaMb: 512);
}
}

The Whm facade

useChuckBartowski\CpanelSdk\Whm;
finalclass HostingAccountManager
{
publicfunction__construct(privatereadonlyWhm$whm)
{
}
publicfunctionopen(string$username, string$domain): void
{
$this->whm->accounts()->create($username, $domain, [
'plan' => 'starter',
'contactemail' => 'billing@example.com',
]);
}
publicfunctionsuspendForNonPayment(string$username): void
{
$this->whm->accounts()->suspend($username, 'unpaid invoice');
}
}

Generic calls (escape hatch)

Any endpoint not covered by a module remains reachable:

$cpanel->client()->uapi('Batch', 'strict', ['command' => $commands], 'POST');
$cpanel->client()->api2('Cron', 'listcron');
$whm->client()->call('sethostname', ['hostname' => 'srv2.example.com'], 'POST');
$whm->client()->cpanelUapi('customer1', 'Email', 'list_pops');

cpanelUapi() runs a UAPI function as any cPanel account through the WHM token — the standard pattern for hosting control panels where only the root/reseller token is stored.

API Reference

Every method returns an ApiResponse and throws on failure (see Error Handling). Named arguments are shown where they improve readability.

Email

$cpanel->email() — UAPI Email module.

MethodUnderlying functionNotes
accounts(?string $domain = null)list_pops_with_diskIncludes disk usage per mailbox
create(string $localPart, string $domain, string $password, int $quotaMb = 0)add_pop0 = unlimited quota
delete(string $localPart, string $domain)delete_pop
changePassword(string $localPart, string $domain, string $password)passwd_pop
setQuota(string $localPart, string $domain, int $quotaMb)edit_pop_quota
forwarders(?string $domain = null)list_forwarders
addForwarder(string $domain, string $localPart, string $destination)add_forwarder
deleteForwarder(string $address, string $forwarder)delete_forwarder
mailDirUsage(string $localPart, string $domain)get_pop_quota
$cpanel->email()->accounts('example.com');
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->email()->addForwarder('example.com', 'contact', 'inbox@elsewhere.com');

Domains

$cpanel->domains() — UAPI DomainInfo + API2 SubDomain / AddonDomain / Park.

MethodUnderlying function
list()UAPI DomainInfo::list_domains
data(?string $domain = null)UAPI domains_data / single_domain_data
addSubdomain(string $subdomain, string $rootDomain, ?string $documentRoot = null)API2 SubDomain::addsubdomain
deleteSubdomain(string $subdomain, string $rootDomain)API2 SubDomain::delsubdomain
addAddonDomain(string $newDomain, string $subdomain, string $documentRoot)API2 AddonDomain::addaddondomain
deleteAddonDomain(string $domain, string $subdomain)API2 AddonDomain::deladdondomain
park(string $domain) / unpark(string $domain)API2 Park
$cpanel->domains()->addSubdomain('api', 'example.com', 'public_html/api');

MySQL

$cpanel->mysql() — UAPI Mysql module.

MethodUnderlying function
databases() / users()list_databases / list_users
createDatabase(string $name) / deleteDatabase(string $name)create_database / delete_database
renameDatabase(string $oldName, string $newName)rename_database
createUser(string $name, string $password) / deleteUser(string $name)create_user / delete_user
setPassword(string $user, string $password)set_password
grant(string $user, string $database, string $privileges = 'ALL PRIVILEGES')set_privileges_on_database
revoke(string $user, string $database)revoke_access_to_database
addHost(string $host)add_host

Remember that cPanel prefixes database and user names with the account name (myaccount_app).

$cpanel->mysql()->createDatabase('myaccount_app');
$cpanel->mysql()->createUser('myaccount_app', 'S3cret!');
$cpanel->mysql()->grant('myaccount_app', 'myaccount_app');

FTP

$cpanel->ftp() — UAPI Ftp module: accounts(), create(), delete() (with optional home-dir destruction), changePassword(), setQuota(), setHomeDir().

$cpanel->ftp()->create('deploy', 'S3cret!', homeDir: 'public_html', quotaMb: 0);
$cpanel->ftp()->delete('deploy', destroyHomeDir: false);

SSL

$cpanel->ssl() — UAPI SSL module: certificates(), installedHosts(), install(), delete(), generateKey(), generateCsr().

$cpanel->ssl()->install('example.com', $certificatePem, $keyPem, $caBundlePem);

Files

$cpanel->files() — UAPI Fileman for content, API2 Fileman::fileop for filesystem operations: list(), read(), write(), info(), mkdir(), delete(), copy(), move(), chmod(), extract(), emptyTrash().

$cpanel->files()->write('public_html', '.htaccess', $rules);
$cpanel->files()->extract('backup.tar.gz', 'public_html');
$cpanel->files()->chmod('public_html/config.php', '0600');

DNS (cPanel zone editor)

$cpanel->dns() — API2 ZoneEdit module. addRecord() automatically maps the value to the right parameter name for the record type (address for A/AAAA, cname for CNAME, txtdata for TXT, exchange for MX…).

$cpanel->dns()->records('example.com', ['type' => 'A']);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10', ttl: 3600);
$cpanel->dns()->editRecord('example.com', line: 22, params: ['address' => '203.0.113.11']);
$cpanel->dns()->removeRecord('example.com', line: 22);

API2 zone records are addressed by line number in the zone file; always re-fetch records after a mutation before addressing another line.

Stats & quotas

$cpanel->stats()quota() (UAPI Quota), bars() (UAPI StatsBar, configurable display list), bandwidth() (API2 Stats::getmonthlybandwidth).

WHM — Accounts

$whm->accounts() — the account lifecycle, WHM API 1.

MethodUnderlying functionNotes
list(?string $search = null, string $searchType = 'user')listacctssearchType: user, domain, owner, ip, package
summary(string $user)accountsummary
create(string $username, string $domain, array $options = [])createacctoptions: plan, password, contactemail, quota, …
remove(string $user, bool $keepDns = false)removeacctDestructive
suspend(string $user, string $reason = '') / unsuspend(string $user)suspendacct / unsuspendacct
changePassword(string $user, string $password)passwd
modify(string $user, array $options)modifyacct
changePlan(string $user, string $plan)changepackage
domainOwner(string $domain)domainuserdata
createUserSession(string $user, string $service = 'cpaneld')create_user_sessionOne-click SSO URL into the user's cPanel
bandwidth(?string $user = null, ?string $month = null, ?string $year = null)showbwBandwidth usage, optionally filtered
limitBandwidth(string $user, int $limitMb)limitbw
$session = $whm->accounts()->createUserSession('customer1');
$redirectUrl = $session->data('url');

WHM — Resellers

$whm->resellers() — the full reseller lifecycle for multi-tier hosting.

MethodUnderlying functionNotes
list()listresellers
stats(string $reseller)resellerstatsDisk/bandwidth totals across owned accounts
accounts(string $reseller)acctcountsUsed/limit account counts
promote(string $user, bool $ownsSelf = false)setupresellerTurns an existing account into a reseller
demote(string $user)unsetupreseller
setLimits(string $user, array $limits)setresellerlimitse.g. enable_account_limit, account_limit, diskspace_limit
setPackageLimit(string $user, string $package, bool $allowed, ?int $number = null)setresellerpackagelimitRestrict which plans a reseller may sell
setAcls(string $reseller, array $acls)setaclsFine-grained privilege grants
setMainIp(string $user, string $ip)setresellermainip
setNameservers(string $user, array $nameservers)setresellernameservers
suspendAccounts(string $reseller) / unsuspendAccounts(string $reseller)suspendreseller / unsuspendresellerSuspends the reseller and all owned accounts
$whm->resellers()->promote('reseller1');
$whm->resellers()->setLimits('reseller1', ['enable_account_limit' => 1, 'account_limit' => 30]);
$whm->resellers()->setPackageLimit('reseller1', 'starter', allowed: true, number: 20);

WHM — Packages

$whm->packages()list(), create(), update(), delete() around listpkgs / addpkg / editpkg / killpkg.

$whm->packages()->create('starter', ['quota' => 5120, 'bwlimit' => 51200, 'maxaddons' => 1]);

WHM — DNS zones

$whm->dnsZones() — full zone lifecycle: list(), dump(), create(), delete(), addRecord(), editRecord(), removeRecord(), reset().

$whm->dnsZones()->create('customer1.com', '203.0.113.10');
$whm->dnsZones()->addRecord('customer1.com', [
'name' => 'mail',
'type' => 'A',
'address' => '203.0.113.10',
'ttl' => 3600,
]);

WHM — IP addresses

$whm->ips() — IP pool management for dedicated-IP offers.

MethodUnderlying function
list()listips
add(string $ip, string $netmask)addips
delete(string $ip)delip
assignToSite(string $domain, string $ip) / assignToUser(string $user, string $ip)setsiteip
usage()get_shared_ip
$whm->ips()->add('203.0.113.25', '255.255.255.0');
$whm->ips()->assignToSite('customer1.com', '203.0.113.25');

WHM — Security (cPHulk)

$whm->security() — brute-force protection management, the bread and butter of hosting support.

MethodUnderlying function
enableCphulk() / disableCphulk()enable_cphulk / disable_cphulk
whitelist(string $ip, string $comment = '') / blacklist(...)create_cphulk_record
listWhitelist() / listBlacklist()read_cphulk_records
removeFromWhitelist(string $ip) / removeFromBlacklist(string $ip)delete_cphulk_record
unblockBrute(string $ip)flush_cphulk_login_history_for_ips
flushLoginHistory()flush_cphulk_login_history
$whm->security()->unblockBrute('198.51.100.7');
$whm->security()->whitelist('203.0.113.50', 'office VPN');

WHM — Backups & restores

$whm->backups() — backup configuration and the account restore queue.

MethodUnderlying functionNotes
config() / setConfig(array $settings)backup_config_get / backup_config_set
users()backup_user_listUsers with backup metadata
dates()backup_date_listAvailable restore points
userBackups(string $user)backup_set_list
queueRestore(string $user, string $restorePoint, array $options = [])restore_queue_add_taskDefaults: keep IP, restore MySQL/subdomains/mail config
activateRestoreQueue()restore_queue_activateStarts processing queued restores
restoreQueueState()restore_queue_statePoll for progress
clearCompletedRestores()restore_queue_clear_completed_tasks
$whm->backups()->queueRestore('customer1', '2026-07-20');
$whm->backups()->activateRestoreQueue();

WHM — PHP versions

$whm->php() — MultiPHP management per virtual host.

MethodUnderlying function
installedVersions()php_get_installed_versions
systemDefault() / setSystemDefault(string $version)php_get_system_default_version / php_set_system_default_version
vhostVersions(string ...$vhosts)php_get_vhost_versions
setVhostVersion(string $version, string ...$vhosts)php_set_vhost_versions
handlers(string $version) / setHandler(string $version, string $handler)php_get_handlers / php_set_handler

Versions use EasyApache identifiers (ea-php83), not bare numbers.

$whm->php()->setVhostVersion('ea-php83', 'example.com', 'shop.example.com');

WHM — SSL & AutoSSL

$whm->autoSsl() — server-wide certificate automation plus manual installs with root privileges.

MethodUnderlying function
providers() / setProvider(string $provider)get_autossl_providers / set_autossl_provider
checkAllUsers()start_autossl_check_for_all_users
checkUser(string $user)start_autossl_check_for_one_user
enableForUser(string $user) / disableForUser(string $user)set_autossl_feature_for_users
installCertificate(string $domain, string $cert, string $key, ?string $caBundle = null)installssl
certificateInfo(string $domain)fetch_ssl_vhosts
$whm->autoSsl()->setProvider('LetsEncrypt');
$whm->autoSsl()->checkUser('customer1');

WHM — Server configuration

$whm->config() — Tweak Settings and global server preferences.

MethodUnderlying function
tweakSetting(string $key, string $module = 'Main')get_tweaksetting
setTweakSetting(string $key, string|int $value, string $module = 'Main')set_tweaksetting
updatePreferences() / setUpdatePreferences(array $settings)get_update_config / update_updateconf
hostname() / setHostname(string $hostname)gethostname / sethostname
nameserverConfig()nameserverconfig
$whm->config()->setTweakSetting('maxemailsperhour', 200);

WHM — Server

$whm->server()version(), hostname(), loadAverage(), serviceStatus(), restartService().

$whm->server()->serviceStatus('httpd');
$whm->server()->restartService('exim');

Responses

All calls return an immutable ApiResponse that normalizes the three wire formats (UAPI envelope, API2 cpanelresult, WHM metadata):

$response = $cpanel->mysql()->databases();
$response->success; // bool$response->data; // mixed — the payload's data section$response->data('acct'); // keyed access with optional default$response->errors; // list<string>$response->messages; // list<string>$response->warnings; // list<string>$response->raw; // the complete decoded JSON payload

data() is null-safe: it returns the default when the payload has no such key or when data is not an array.

Error Handling

All SDK exceptions implement CpanelSdkExceptionInterface, so a single catch covers everything:

ExceptionThrown whenExtras
ApiExceptionThe API answered but reported a failure (module methods validate automatically)getErrors(): array, getRaw(): array
AuthenticationExceptionCredentials are missing, or the server answered HTTP 401/403thrown before any request when credentials are empty
TransportExceptionNetwork error, TLS failure, timeout, or a non-JSON response bodywraps the underlying symfony/http-client exception
useChuckBartowski\CpanelSdk\Exception\ApiException;
useChuckBartowski\CpanelSdk\Exception\CpanelSdkExceptionInterface;
try {
$cpanel->email()->create('support', 'example.com', $password);
} catch (ApiException$e) {
$this->logger->warning('cPanel rejected the mailbox', ['errors' => $e->getErrors()]);
} catch (CpanelSdkExceptionInterface$e) {
thrownewProvisioningUnavailableException(previous: $e);
}

To inspect a failed response without exceptions, use the client directly — client-level methods return the response as-is:

$response = $cpanel->client()->uapi('Email', 'add_pop', $params, 'POST');
if (!$response->success) {
// $response->errors, $response->raw
}

Testing

The suite runs entirely offline against MockHttpClient:

composer install
vendor/bin/phpunit

To test your own services, inject a CpanelClient/WhmClient built with a mock:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Cpanel;
useSymfony\Component\HttpClient\MockHttpClient;
useSymfony\Component\HttpClient\Response\JsonMockResponse;
$http = newMockHttpClient(newJsonMockResponse(['status' => 1, 'data' => []]));
$cpanel = newCpanel(newCpanelClient('host', 'user', 'token', 2083, true, 30.0, $http));

Security Notes

  • API tokens are passed with #[\SensitiveParameter], so they never appear in stack traces.
  • Keep tokens in .env.local or your secret vault — never commit them.
  • Scope WHM tokens to the minimal privilege set in WHM » Manage API Tokens (e.g. deny Everything, allow only account functions).
  • Leave verify_ssl: true in production; the option exists solely for self-signed development servers.
  • removeacct and delete_ftp destroy=1 are irreversible — gate them behind confirmation flows in your application.

WHMCS module

A ready-to-use WHMCS provisioning module ships in whmcs/modules/servers/cpanelsdk/. It automates cPanel account provisioning through WHM using this SDK — create, suspend, unsuspend, terminate, change password, change package, and one-click SSO into cPanel.

Install

  1. composer require chuckbartowski/cpanel-sdk in your WHMCS root (so the SDK is autoloaded).
  2. Copy the cpanelsdk folder into <whmcs>/modules/servers/.
  3. In WHMCS, add a server (System Settings » Servers) with Type: cPanel (SDK), the WHM hostname, username root, and your WHM API token in the Access Hash field.
  4. Point a product at the server and set the Package config option to the WHM plan name.
OperationWHM function used
Create / Suspend / Unsuspend / Terminatecreateacct / suspendacct / unsuspendacct / removeacct
Change password / packagepasswd / changepackage
One-click logincreate_user_session

License

MIT

About

PHP SDK for the cPanel & WHM APIs (UAPI, API2, WHM API 1).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

cPanel & WHM SDK for PHP

🖥 cPanel & WHM SDK for PHP

A modern, fully typed PHP SDK for driving cPanel (UAPI + API2) and WHM (API 1).

PHP VersionSymfonyTestsPackagistLicense

Email · DNS · MySQL · SSL · FTP · Files · Accounts · Resellers · Packages · AutoSSL · Backups

Installation · Quick Start · API Reference · Error Handling


$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);
$whm->accounts()->createUserSession('customer1');

Framework-agnostic core — usable from any PHP project, script, or worker — with an optional bundle for first-class Symfony integration. Authenticated with API tokens, typed exceptions, and a comment-free, strictly typed codebase (PHP 8.2+, declare(strict_types=1) everywhere).


Table of Contents


Features

  • Full coverage of the three cPanel API surfaces: UAPI (the modern cPanel API), API2 (legacy but still required for zone editing, subdomains, addon domains, file operations), and WHM API 1 (server administration).
  • Framework-agnostic: two plain facades (Cpanel, Whm) you can instantiate anywhere; only hard dependency is symfony/http-client, a standalone component that works in any PHP project.
  • Token authentication only — no passwords, no sessions, no cookies. Uses the official Authorization: cpanel user:token / Authorization: whm user:token schemes.
  • High-level, discoverable modules grouped by domain: email, MySQL, DNS, SSL, files, accounts, resellers, backups, PHP versions, security…
  • A single normalized response object (ApiResponse) regardless of which underlying API answered — you never parse cpanelresult or metadata envelopes yourself.
  • Typed exception hierarchy under one marker interface, so you can catch narrowly or broadly.
  • Escape hatches everywhere: any endpoint not wrapped by a module remains one method call away.
  • Optional Symfony bundle with semantic configuration and autowirable services.
  • Fully unit-tested against MockHttpClient (no network required).

Requirements

DependencyVersion
PHP>= 8.2
cPanel/WHMany version supporting API tokens (v64+)
Symfony6.4 LTS or 7.x — optional, only for the bundle integration

You will need at least one of:

  • a cPanel API token — created in cPanel » Security » Manage API Tokens
  • a WHM API token — created in WHM » Development » Manage API Tokens

Installation

The package is published on Packagist:

composer require chuckbartowski/cpanel-sdk

Quick Start (plain PHP)

No framework required — build the clients and go:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Client\WhmClient;
useChuckBartowski\CpanelSdk\Cpanel;
useChuckBartowski\CpanelSdk\Whm;
$cpanel = newCpanel(newCpanelClient(
host: 'server.example.com',
username: 'myaccount',
token: getenv('CPANEL_API_TOKEN'),
port: 2083,
));
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10');
$whm = newWhm(newWhmClient(
host: 'server.example.com',
username: 'root',
token: getenv('WHM_API_TOKEN'),
port: 2087,
));
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);

Client constructor signature (identical for both clients):

newCpanelClient(
string $host,
string $username,
string $token,
int $port, // 2083 for cPanel, 2087 for WHM
bool $verifySsl = true,
float $timeout = 30.0,
?HttpClientInterface $httpClient = null, // inject your own (retries, proxy, mock…)
);

Symfony Integration (optional)

A ready-made bundle wires everything into the container. Register it:

// config/bundles.phpreturn [
ChuckBartowski\CpanelSdk\CpanelSdkBundle::class => ['all' => true],
];

Then create config/packages/cpanel_sdk.yaml:

cpanel_sdk:
host: '%env(CPANEL_HOST)%'verify_ssl: truetimeout: 30cpanel:
username: '%env(CPANEL_USERNAME)%'token: '%env(CPANEL_API_TOKEN)%'port: 2083whm:
username: '%env(WHM_USERNAME)%'token: '%env(WHM_API_TOKEN)%'port: 2087

And the matching environment variables:

# .env.localCPANEL_HOST=server.example.comCPANEL_USERNAME=myaccountCPANEL_API_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWHM_USERNAME=rootWHM_API_TOKEN=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

Configuration reference

KeyTypeDefaultDescription
hoststringrequiredHostname of the cPanel/WHM server (no scheme, no port)
verify_sslbooltrueTLS peer/host verification; disable only for self-signed dev servers
timeoutfloat30.0Per-request timeout in seconds
cpanel.usernamestring''cPanel account name
cpanel.tokenstring''cPanel API token
cpanel.portint2083cPanel TLS port
whm.usernamestring''WHM user (usually root or a reseller)
whm.tokenstring''WHM API token
whm.portint2087WHM TLS port

The cpanel and whm sections are independent — configure only the side you need. Calling a client with missing credentials throws an AuthenticationException immediately, before any network request is made.

The bundle reuses your application's http_client service when available (so scoped clients, retry strategies, and profiler integration all apply), and falls back to a native client otherwise.

Architecture

src/
├── CpanelSdkBundle.php Symfony bundle: config tree + service wiring
├── Cpanel.php Facade: entry point for cPanel-level modules
├── Whm.php Facade: entry point for WHM-level modules
├── Client/
│ ├── AbstractClient.php Shared HTTP transport, auth header, error mapping
│ ├── CpanelClient.php uapi() and api2() generic executors
│ └── WhmClient.php call() (WHM API 1) and cpanelUapi() (root proxy)
├── Response/
│ └── ApiResponse.php Immutable, normalized response for all 3 API formats
├── Exception/
│ ├── CpanelSdkExceptionInterface.php
│ ├── ApiException.php API answered but reported a failure
│ ├── AuthenticationException.php
│ └── TransportException.php Network / TLS / timeout / invalid JSON
└── Api/
├── Cpanel/ EmailApi, DomainApi, MysqlApi, FtpApi,
│ SslApi, FileApi, DnsApi, StatsApi
└── Whm/ AccountApi, ResellerApi, PackageApi, DnsZoneApi,
IpApi, SecurityApi, BackupApi, PhpApi,
AutoSslApi, ConfigApi, ServerApi

Design decisions:

  • Facade + lazy modules: Cpanel/Whm instantiate each module on first use and cache it, so the DI container only carries four services.
  • Modules always validate: every module method calls ensureSuccess() internally and throws ApiException on failure. If you need to inspect a failed response without an exception, drop down to the client level.
  • Nothing is sealed off: the clients' generic methods accept any module/function/parameter combination, so a cPanel endpoint added tomorrow is usable today.

Usage

Standalone, instantiate the facades as shown in the Quick Start. In Symfony, both facades are autowirable in controllers, services, commands, and message handlers.

The Cpanel facade

useChuckBartowski\CpanelSdk\Cpanel;
finalclass MailboxProvisioner
{
publicfunction__construct(privatereadonlyCpanel$cpanel)
{
}
publicfunctionprovision(string$localPart, string$domain, string$password): void
{
$this->cpanel->email()->create($localPart, $domain, $password, quotaMb: 512);
}
}

The Whm facade

useChuckBartowski\CpanelSdk\Whm;
finalclass HostingAccountManager
{
publicfunction__construct(privatereadonlyWhm$whm)
{
}
publicfunctionopen(string$username, string$domain): void
{
$this->whm->accounts()->create($username, $domain, [
'plan' => 'starter',
'contactemail' => 'billing@example.com',
]);
}
publicfunctionsuspendForNonPayment(string$username): void
{
$this->whm->accounts()->suspend($username, 'unpaid invoice');
}
}

Generic calls (escape hatch)

Any endpoint not covered by a module remains reachable:

$cpanel->client()->uapi('Batch', 'strict', ['command' => $commands], 'POST');
$cpanel->client()->api2('Cron', 'listcron');
$whm->client()->call('sethostname', ['hostname' => 'srv2.example.com'], 'POST');
$whm->client()->cpanelUapi('customer1', 'Email', 'list_pops');

cpanelUapi() runs a UAPI function as any cPanel account through the WHM token — the standard pattern for hosting control panels where only the root/reseller token is stored.

API Reference

Every method returns an ApiResponse and throws on failure (see Error Handling). Named arguments are shown where they improve readability.

Email

$cpanel->email() — UAPI Email module.

MethodUnderlying functionNotes
accounts(?string $domain = null)list_pops_with_diskIncludes disk usage per mailbox
create(string $localPart, string $domain, string $password, int $quotaMb = 0)add_pop0 = unlimited quota
delete(string $localPart, string $domain)delete_pop
changePassword(string $localPart, string $domain, string $password)passwd_pop
setQuota(string $localPart, string $domain, int $quotaMb)edit_pop_quota
forwarders(?string $domain = null)list_forwarders
addForwarder(string $domain, string $localPart, string $destination)add_forwarder
deleteForwarder(string $address, string $forwarder)delete_forwarder
mailDirUsage(string $localPart, string $domain)get_pop_quota
$cpanel->email()->accounts('example.com');
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->email()->addForwarder('example.com', 'contact', 'inbox@elsewhere.com');

Domains

$cpanel->domains() — UAPI DomainInfo + API2 SubDomain / AddonDomain / Park.

MethodUnderlying function
list()UAPI DomainInfo::list_domains
data(?string $domain = null)UAPI domains_data / single_domain_data
addSubdomain(string $subdomain, string $rootDomain, ?string $documentRoot = null)API2 SubDomain::addsubdomain
deleteSubdomain(string $subdomain, string $rootDomain)API2 SubDomain::delsubdomain
addAddonDomain(string $newDomain, string $subdomain, string $documentRoot)API2 AddonDomain::addaddondomain
deleteAddonDomain(string $domain, string $subdomain)API2 AddonDomain::deladdondomain
park(string $domain) / unpark(string $domain)API2 Park
$cpanel->domains()->addSubdomain('api', 'example.com', 'public_html/api');

MySQL

$cpanel->mysql() — UAPI Mysql module.

MethodUnderlying function
databases() / users()list_databases / list_users
createDatabase(string $name) / deleteDatabase(string $name)create_database / delete_database
renameDatabase(string $oldName, string $newName)rename_database
createUser(string $name, string $password) / deleteUser(string $name)create_user / delete_user
setPassword(string $user, string $password)set_password
grant(string $user, string $database, string $privileges = 'ALL PRIVILEGES')set_privileges_on_database
revoke(string $user, string $database)revoke_access_to_database
addHost(string $host)add_host

Remember that cPanel prefixes database and user names with the account name (myaccount_app).

$cpanel->mysql()->createDatabase('myaccount_app');
$cpanel->mysql()->createUser('myaccount_app', 'S3cret!');
$cpanel->mysql()->grant('myaccount_app', 'myaccount_app');

FTP

$cpanel->ftp() — UAPI Ftp module: accounts(), create(), delete() (with optional home-dir destruction), changePassword(), setQuota(), setHomeDir().

$cpanel->ftp()->create('deploy', 'S3cret!', homeDir: 'public_html', quotaMb: 0);
$cpanel->ftp()->delete('deploy', destroyHomeDir: false);

SSL

$cpanel->ssl() — UAPI SSL module: certificates(), installedHosts(), install(), delete(), generateKey(), generateCsr().

$cpanel->ssl()->install('example.com', $certificatePem, $keyPem, $caBundlePem);

Files

$cpanel->files() — UAPI Fileman for content, API2 Fileman::fileop for filesystem operations: list(), read(), write(), info(), mkdir(), delete(), copy(), move(), chmod(), extract(), emptyTrash().

$cpanel->files()->write('public_html', '.htaccess', $rules);
$cpanel->files()->extract('backup.tar.gz', 'public_html');
$cpanel->files()->chmod('public_html/config.php', '0600');

DNS (cPanel zone editor)

$cpanel->dns() — API2 ZoneEdit module. addRecord() automatically maps the value to the right parameter name for the record type (address for A/AAAA, cname for CNAME, txtdata for TXT, exchange for MX…).

$cpanel->dns()->records('example.com', ['type' => 'A']);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10', ttl: 3600);
$cpanel->dns()->editRecord('example.com', line: 22, params: ['address' => '203.0.113.11']);
$cpanel->dns()->removeRecord('example.com', line: 22);

API2 zone records are addressed by line number in the zone file; always re-fetch records after a mutation before addressing another line.

Stats & quotas

$cpanel->stats()quota() (UAPI Quota), bars() (UAPI StatsBar, configurable display list), bandwidth() (API2 Stats::getmonthlybandwidth).

WHM — Accounts

$whm->accounts() — the account lifecycle, WHM API 1.

MethodUnderlying functionNotes
list(?string $search = null, string $searchType = 'user')listacctssearchType: user, domain, owner, ip, package
summary(string $user)accountsummary
create(string $username, string $domain, array $options = [])createacctoptions: plan, password, contactemail, quota, …
remove(string $user, bool $keepDns = false)removeacctDestructive
suspend(string $user, string $reason = '') / unsuspend(string $user)suspendacct / unsuspendacct
changePassword(string $user, string $password)passwd
modify(string $user, array $options)modifyacct
changePlan(string $user, string $plan)changepackage
domainOwner(string $domain)domainuserdata
createUserSession(string $user, string $service = 'cpaneld')create_user_sessionOne-click SSO URL into the user's cPanel
bandwidth(?string $user = null, ?string $month = null, ?string $year = null)showbwBandwidth usage, optionally filtered
limitBandwidth(string $user, int $limitMb)limitbw
$session = $whm->accounts()->createUserSession('customer1');
$redirectUrl = $session->data('url');

WHM — Resellers

$whm->resellers() — the full reseller lifecycle for multi-tier hosting.

MethodUnderlying functionNotes
list()listresellers
stats(string $reseller)resellerstatsDisk/bandwidth totals across owned accounts
accounts(string $reseller)acctcountsUsed/limit account counts
promote(string $user, bool $ownsSelf = false)setupresellerTurns an existing account into a reseller
demote(string $user)unsetupreseller
setLimits(string $user, array $limits)setresellerlimitse.g. enable_account_limit, account_limit, diskspace_limit
setPackageLimit(string $user, string $package, bool $allowed, ?int $number = null)setresellerpackagelimitRestrict which plans a reseller may sell
setAcls(string $reseller, array $acls)setaclsFine-grained privilege grants
setMainIp(string $user, string $ip)setresellermainip
setNameservers(string $user, array $nameservers)setresellernameservers
suspendAccounts(string $reseller) / unsuspendAccounts(string $reseller)suspendreseller / unsuspendresellerSuspends the reseller and all owned accounts
$whm->resellers()->promote('reseller1');
$whm->resellers()->setLimits('reseller1', ['enable_account_limit' => 1, 'account_limit' => 30]);
$whm->resellers()->setPackageLimit('reseller1', 'starter', allowed: true, number: 20);

WHM — Packages

$whm->packages()list(), create(), update(), delete() around listpkgs / addpkg / editpkg / killpkg.

$whm->packages()->create('starter', ['quota' => 5120, 'bwlimit' => 51200, 'maxaddons' => 1]);

WHM — DNS zones

$whm->dnsZones() — full zone lifecycle: list(), dump(), create(), delete(), addRecord(), editRecord(), removeRecord(), reset().

$whm->dnsZones()->create('customer1.com', '203.0.113.10');
$whm->dnsZones()->addRecord('customer1.com', [
'name' => 'mail',
'type' => 'A',
'address' => '203.0.113.10',
'ttl' => 3600,
]);

WHM — IP addresses

$whm->ips() — IP pool management for dedicated-IP offers.

MethodUnderlying function
list()listips
add(string $ip, string $netmask)addips
delete(string $ip)delip
assignToSite(string $domain, string $ip) / assignToUser(string $user, string $ip)setsiteip
usage()get_shared_ip
$whm->ips()->add('203.0.113.25', '255.255.255.0');
$whm->ips()->assignToSite('customer1.com', '203.0.113.25');

WHM — Security (cPHulk)

$whm->security() — brute-force protection management, the bread and butter of hosting support.

MethodUnderlying function
enableCphulk() / disableCphulk()enable_cphulk / disable_cphulk
whitelist(string $ip, string $comment = '') / blacklist(...)create_cphulk_record
listWhitelist() / listBlacklist()read_cphulk_records
removeFromWhitelist(string $ip) / removeFromBlacklist(string $ip)delete_cphulk_record
unblockBrute(string $ip)flush_cphulk_login_history_for_ips
flushLoginHistory()flush_cphulk_login_history
$whm->security()->unblockBrute('198.51.100.7');
$whm->security()->whitelist('203.0.113.50', 'office VPN');

WHM — Backups & restores

$whm->backups() — backup configuration and the account restore queue.

MethodUnderlying functionNotes
config() / setConfig(array $settings)backup_config_get / backup_config_set
users()backup_user_listUsers with backup metadata
dates()backup_date_listAvailable restore points
userBackups(string $user)backup_set_list
queueRestore(string $user, string $restorePoint, array $options = [])restore_queue_add_taskDefaults: keep IP, restore MySQL/subdomains/mail config
activateRestoreQueue()restore_queue_activateStarts processing queued restores
restoreQueueState()restore_queue_statePoll for progress
clearCompletedRestores()restore_queue_clear_completed_tasks
$whm->backups()->queueRestore('customer1', '2026-07-20');
$whm->backups()->activateRestoreQueue();

WHM — PHP versions

$whm->php() — MultiPHP management per virtual host.

MethodUnderlying function
installedVersions()php_get_installed_versions
systemDefault() / setSystemDefault(string $version)php_get_system_default_version / php_set_system_default_version
vhostVersions(string ...$vhosts)php_get_vhost_versions
setVhostVersion(string $version, string ...$vhosts)php_set_vhost_versions
handlers(string $version) / setHandler(string $version, string $handler)php_get_handlers / php_set_handler

Versions use EasyApache identifiers (ea-php83), not bare numbers.

$whm->php()->setVhostVersion('ea-php83', 'example.com', 'shop.example.com');

WHM — SSL & AutoSSL

$whm->autoSsl() — server-wide certificate automation plus manual installs with root privileges.

MethodUnderlying function
providers() / setProvider(string $provider)get_autossl_providers / set_autossl_provider
checkAllUsers()start_autossl_check_for_all_users
checkUser(string $user)start_autossl_check_for_one_user
enableForUser(string $user) / disableForUser(string $user)set_autossl_feature_for_users
installCertificate(string $domain, string $cert, string $key, ?string $caBundle = null)installssl
certificateInfo(string $domain)fetch_ssl_vhosts
$whm->autoSsl()->setProvider('LetsEncrypt');
$whm->autoSsl()->checkUser('customer1');

WHM — Server configuration

$whm->config() — Tweak Settings and global server preferences.

MethodUnderlying function
tweakSetting(string $key, string $module = 'Main')get_tweaksetting
setTweakSetting(string $key, string|int $value, string $module = 'Main')set_tweaksetting
updatePreferences() / setUpdatePreferences(array $settings)get_update_config / update_updateconf
hostname() / setHostname(string $hostname)gethostname / sethostname
nameserverConfig()nameserverconfig
$whm->config()->setTweakSetting('maxemailsperhour', 200);

WHM — Server

$whm->server()version(), hostname(), loadAverage(), serviceStatus(), restartService().

$whm->server()->serviceStatus('httpd');
$whm->server()->restartService('exim');

Responses

All calls return an immutable ApiResponse that normalizes the three wire formats (UAPI envelope, API2 cpanelresult, WHM metadata):

$response = $cpanel->mysql()->databases();
$response->success; // bool$response->data; // mixed — the payload's data section$response->data('acct'); // keyed access with optional default$response->errors; // list<string>$response->messages; // list<string>$response->warnings; // list<string>$response->raw; // the complete decoded JSON payload

data() is null-safe: it returns the default when the payload has no such key or when data is not an array.

Error Handling

All SDK exceptions implement CpanelSdkExceptionInterface, so a single catch covers everything:

ExceptionThrown whenExtras
ApiExceptionThe API answered but reported a failure (module methods validate automatically)getErrors(): array, getRaw(): array
AuthenticationExceptionCredentials are missing, or the server answered HTTP 401/403thrown before any request when credentials are empty
TransportExceptionNetwork error, TLS failure, timeout, or a non-JSON response bodywraps the underlying symfony/http-client exception
useChuckBartowski\CpanelSdk\Exception\ApiException;
useChuckBartowski\CpanelSdk\Exception\CpanelSdkExceptionInterface;
try {
$cpanel->email()->create('support', 'example.com', $password);
} catch (ApiException$e) {
$this->logger->warning('cPanel rejected the mailbox', ['errors' => $e->getErrors()]);
} catch (CpanelSdkExceptionInterface$e) {
thrownewProvisioningUnavailableException(previous: $e);
}

To inspect a failed response without exceptions, use the client directly — client-level methods return the response as-is:

$response = $cpanel->client()->uapi('Email', 'add_pop', $params, 'POST');
if (!$response->success) {
// $response->errors, $response->raw
}

Testing

The suite runs entirely offline against MockHttpClient:

composer install
vendor/bin/phpunit

To test your own services, inject a CpanelClient/WhmClient built with a mock:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Cpanel;
useSymfony\Component\HttpClient\MockHttpClient;
useSymfony\Component\HttpClient\Response\JsonMockResponse;
$http = newMockHttpClient(newJsonMockResponse(['status' => 1, 'data' => []]));
$cpanel = newCpanel(newCpanelClient('host', 'user', 'token', 2083, true, 30.0, $http));

Security Notes

  • API tokens are passed with #[\SensitiveParameter], so they never appear in stack traces.
  • Keep tokens in .env.local or your secret vault — never commit them.
  • Scope WHM tokens to the minimal privilege set in WHM » Manage API Tokens (e.g. deny Everything, allow only account functions).
  • Leave verify_ssl: true in production; the option exists solely for self-signed development servers.
  • removeacct and delete_ftp destroy=1 are irreversible — gate them behind confirmation flows in your application.

WHMCS module

A ready-to-use WHMCS provisioning module ships in whmcs/modules/servers/cpanelsdk/. It automates cPanel account provisioning through WHM using this SDK — create, suspend, unsuspend, terminate, change password, change package, and one-click SSO into cPanel.

Install

  1. composer require chuckbartowski/cpanel-sdk in your WHMCS root (so the SDK is autoloaded).
  2. Copy the cpanelsdk folder into <whmcs>/modules/servers/.
  3. In WHMCS, add a server (System Settings » Servers) with Type: cPanel (SDK), the WHM hostname, username root, and your WHM API token in the Access Hash field.
  4. Point a product at the server and set the Package config option to the WHM plan name.
OperationWHM function used
Create / Suspend / Unsuspend / Terminatecreateacct / suspendacct / unsuspendacct / removeacct
Change password / packagepasswd / changepackage
One-click logincreate_user_session

License

MIT

About

PHP SDK for the cPanel & WHM APIs (UAPI, API2, WHM API 1).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

cPanel & WHM SDK for PHP

🖥 cPanel & WHM SDK for PHP

A modern, fully typed PHP SDK for driving cPanel (UAPI + API2) and WHM (API 1).

PHP VersionSymfonyTestsPackagistLicense

Email · DNS · MySQL · SSL · FTP · Files · Accounts · Resellers · Packages · AutoSSL · Backups

Installation · Quick Start · API Reference · Error Handling


$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);
$whm->accounts()->createUserSession('customer1');

Framework-agnostic core — usable from any PHP project, script, or worker — with an optional bundle for first-class Symfony integration. Authenticated with API tokens, typed exceptions, and a comment-free, strictly typed codebase (PHP 8.2+, declare(strict_types=1) everywhere).


Table of Contents


Features

  • Full coverage of the three cPanel API surfaces: UAPI (the modern cPanel API), API2 (legacy but still required for zone editing, subdomains, addon domains, file operations), and WHM API 1 (server administration).
  • Framework-agnostic: two plain facades (Cpanel, Whm) you can instantiate anywhere; only hard dependency is symfony/http-client, a standalone component that works in any PHP project.
  • Token authentication only — no passwords, no sessions, no cookies. Uses the official Authorization: cpanel user:token / Authorization: whm user:token schemes.
  • High-level, discoverable modules grouped by domain: email, MySQL, DNS, SSL, files, accounts, resellers, backups, PHP versions, security…
  • A single normalized response object (ApiResponse) regardless of which underlying API answered — you never parse cpanelresult or metadata envelopes yourself.
  • Typed exception hierarchy under one marker interface, so you can catch narrowly or broadly.
  • Escape hatches everywhere: any endpoint not wrapped by a module remains one method call away.
  • Optional Symfony bundle with semantic configuration and autowirable services.
  • Fully unit-tested against MockHttpClient (no network required).

Requirements

DependencyVersion
PHP>= 8.2
cPanel/WHMany version supporting API tokens (v64+)
Symfony6.4 LTS or 7.x — optional, only for the bundle integration

You will need at least one of:

  • a cPanel API token — created in cPanel » Security » Manage API Tokens
  • a WHM API token — created in WHM » Development » Manage API Tokens

Installation

The package is published on Packagist:

composer require chuckbartowski/cpanel-sdk

Quick Start (plain PHP)

No framework required — build the clients and go:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Client\WhmClient;
useChuckBartowski\CpanelSdk\Cpanel;
useChuckBartowski\CpanelSdk\Whm;
$cpanel = newCpanel(newCpanelClient(
host: 'server.example.com',
username: 'myaccount',
token: getenv('CPANEL_API_TOKEN'),
port: 2083,
));
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10');
$whm = newWhm(newWhmClient(
host: 'server.example.com',
username: 'root',
token: getenv('WHM_API_TOKEN'),
port: 2087,
));
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);

Client constructor signature (identical for both clients):

newCpanelClient(
string $host,
string $username,
string $token,
int $port, // 2083 for cPanel, 2087 for WHM
bool $verifySsl = true,
float $timeout = 30.0,
?HttpClientInterface $httpClient = null, // inject your own (retries, proxy, mock…)
);

Symfony Integration (optional)

A ready-made bundle wires everything into the container. Register it:

// config/bundles.phpreturn [
ChuckBartowski\CpanelSdk\CpanelSdkBundle::class => ['all' => true],
];

Then create config/packages/cpanel_sdk.yaml:

cpanel_sdk:
host: '%env(CPANEL_HOST)%'verify_ssl: truetimeout: 30cpanel:
username: '%env(CPANEL_USERNAME)%'token: '%env(CPANEL_API_TOKEN)%'port: 2083whm:
username: '%env(WHM_USERNAME)%'token: '%env(WHM_API_TOKEN)%'port: 2087

And the matching environment variables:

# .env.localCPANEL_HOST=server.example.comCPANEL_USERNAME=myaccountCPANEL_API_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWHM_USERNAME=rootWHM_API_TOKEN=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

Configuration reference

KeyTypeDefaultDescription
hoststringrequiredHostname of the cPanel/WHM server (no scheme, no port)
verify_sslbooltrueTLS peer/host verification; disable only for self-signed dev servers
timeoutfloat30.0Per-request timeout in seconds
cpanel.usernamestring''cPanel account name
cpanel.tokenstring''cPanel API token
cpanel.portint2083cPanel TLS port
whm.usernamestring''WHM user (usually root or a reseller)
whm.tokenstring''WHM API token
whm.portint2087WHM TLS port

The cpanel and whm sections are independent — configure only the side you need. Calling a client with missing credentials throws an AuthenticationException immediately, before any network request is made.

The bundle reuses your application's http_client service when available (so scoped clients, retry strategies, and profiler integration all apply), and falls back to a native client otherwise.

Architecture

src/
├── CpanelSdkBundle.php Symfony bundle: config tree + service wiring
├── Cpanel.php Facade: entry point for cPanel-level modules
├── Whm.php Facade: entry point for WHM-level modules
├── Client/
│ ├── AbstractClient.php Shared HTTP transport, auth header, error mapping
│ ├── CpanelClient.php uapi() and api2() generic executors
│ └── WhmClient.php call() (WHM API 1) and cpanelUapi() (root proxy)
├── Response/
│ └── ApiResponse.php Immutable, normalized response for all 3 API formats
├── Exception/
│ ├── CpanelSdkExceptionInterface.php
│ ├── ApiException.php API answered but reported a failure
│ ├── AuthenticationException.php
│ └── TransportException.php Network / TLS / timeout / invalid JSON
└── Api/
├── Cpanel/ EmailApi, DomainApi, MysqlApi, FtpApi,
│ SslApi, FileApi, DnsApi, StatsApi
└── Whm/ AccountApi, ResellerApi, PackageApi, DnsZoneApi,
IpApi, SecurityApi, BackupApi, PhpApi,
AutoSslApi, ConfigApi, ServerApi

Design decisions:

  • Facade + lazy modules: Cpanel/Whm instantiate each module on first use and cache it, so the DI container only carries four services.
  • Modules always validate: every module method calls ensureSuccess() internally and throws ApiException on failure. If you need to inspect a failed response without an exception, drop down to the client level.
  • Nothing is sealed off: the clients' generic methods accept any module/function/parameter combination, so a cPanel endpoint added tomorrow is usable today.

Usage

Standalone, instantiate the facades as shown in the Quick Start. In Symfony, both facades are autowirable in controllers, services, commands, and message handlers.

The Cpanel facade

useChuckBartowski\CpanelSdk\Cpanel;
finalclass MailboxProvisioner
{
publicfunction__construct(privatereadonlyCpanel$cpanel)
{
}
publicfunctionprovision(string$localPart, string$domain, string$password): void
{
$this->cpanel->email()->create($localPart, $domain, $password, quotaMb: 512);
}
}

The Whm facade

useChuckBartowski\CpanelSdk\Whm;
finalclass HostingAccountManager
{
publicfunction__construct(privatereadonlyWhm$whm)
{
}
publicfunctionopen(string$username, string$domain): void
{
$this->whm->accounts()->create($username, $domain, [
'plan' => 'starter',
'contactemail' => 'billing@example.com',
]);
}
publicfunctionsuspendForNonPayment(string$username): void
{
$this->whm->accounts()->suspend($username, 'unpaid invoice');
}
}

Generic calls (escape hatch)

Any endpoint not covered by a module remains reachable:

$cpanel->client()->uapi('Batch', 'strict', ['command' => $commands], 'POST');
$cpanel->client()->api2('Cron', 'listcron');
$whm->client()->call('sethostname', ['hostname' => 'srv2.example.com'], 'POST');
$whm->client()->cpanelUapi('customer1', 'Email', 'list_pops');

cpanelUapi() runs a UAPI function as any cPanel account through the WHM token — the standard pattern for hosting control panels where only the root/reseller token is stored.

API Reference

Every method returns an ApiResponse and throws on failure (see Error Handling). Named arguments are shown where they improve readability.

Email

$cpanel->email() — UAPI Email module.

MethodUnderlying functionNotes
accounts(?string $domain = null)list_pops_with_diskIncludes disk usage per mailbox
create(string $localPart, string $domain, string $password, int $quotaMb = 0)add_pop0 = unlimited quota
delete(string $localPart, string $domain)delete_pop
changePassword(string $localPart, string $domain, string $password)passwd_pop
setQuota(string $localPart, string $domain, int $quotaMb)edit_pop_quota
forwarders(?string $domain = null)list_forwarders
addForwarder(string $domain, string $localPart, string $destination)add_forwarder
deleteForwarder(string $address, string $forwarder)delete_forwarder
mailDirUsage(string $localPart, string $domain)get_pop_quota
$cpanel->email()->accounts('example.com');
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->email()->addForwarder('example.com', 'contact', 'inbox@elsewhere.com');

Domains

$cpanel->domains() — UAPI DomainInfo + API2 SubDomain / AddonDomain / Park.

MethodUnderlying function
list()UAPI DomainInfo::list_domains
data(?string $domain = null)UAPI domains_data / single_domain_data
addSubdomain(string $subdomain, string $rootDomain, ?string $documentRoot = null)API2 SubDomain::addsubdomain
deleteSubdomain(string $subdomain, string $rootDomain)API2 SubDomain::delsubdomain
addAddonDomain(string $newDomain, string $subdomain, string $documentRoot)API2 AddonDomain::addaddondomain
deleteAddonDomain(string $domain, string $subdomain)API2 AddonDomain::deladdondomain
park(string $domain) / unpark(string $domain)API2 Park
$cpanel->domains()->addSubdomain('api', 'example.com', 'public_html/api');

MySQL

$cpanel->mysql() — UAPI Mysql module.

MethodUnderlying function
databases() / users()list_databases / list_users
createDatabase(string $name) / deleteDatabase(string $name)create_database / delete_database
renameDatabase(string $oldName, string $newName)rename_database
createUser(string $name, string $password) / deleteUser(string $name)create_user / delete_user
setPassword(string $user, string $password)set_password
grant(string $user, string $database, string $privileges = 'ALL PRIVILEGES')set_privileges_on_database
revoke(string $user, string $database)revoke_access_to_database
addHost(string $host)add_host

Remember that cPanel prefixes database and user names with the account name (myaccount_app).

$cpanel->mysql()->createDatabase('myaccount_app');
$cpanel->mysql()->createUser('myaccount_app', 'S3cret!');
$cpanel->mysql()->grant('myaccount_app', 'myaccount_app');

FTP

$cpanel->ftp() — UAPI Ftp module: accounts(), create(), delete() (with optional home-dir destruction), changePassword(), setQuota(), setHomeDir().

$cpanel->ftp()->create('deploy', 'S3cret!', homeDir: 'public_html', quotaMb: 0);
$cpanel->ftp()->delete('deploy', destroyHomeDir: false);

SSL

$cpanel->ssl() — UAPI SSL module: certificates(), installedHosts(), install(), delete(), generateKey(), generateCsr().

$cpanel->ssl()->install('example.com', $certificatePem, $keyPem, $caBundlePem);

Files

$cpanel->files() — UAPI Fileman for content, API2 Fileman::fileop for filesystem operations: list(), read(), write(), info(), mkdir(), delete(), copy(), move(), chmod(), extract(), emptyTrash().

$cpanel->files()->write('public_html', '.htaccess', $rules);
$cpanel->files()->extract('backup.tar.gz', 'public_html');
$cpanel->files()->chmod('public_html/config.php', '0600');

DNS (cPanel zone editor)

$cpanel->dns() — API2 ZoneEdit module. addRecord() automatically maps the value to the right parameter name for the record type (address for A/AAAA, cname for CNAME, txtdata for TXT, exchange for MX…).

$cpanel->dns()->records('example.com', ['type' => 'A']);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10', ttl: 3600);
$cpanel->dns()->editRecord('example.com', line: 22, params: ['address' => '203.0.113.11']);
$cpanel->dns()->removeRecord('example.com', line: 22);

API2 zone records are addressed by line number in the zone file; always re-fetch records after a mutation before addressing another line.

Stats & quotas

$cpanel->stats()quota() (UAPI Quota), bars() (UAPI StatsBar, configurable display list), bandwidth() (API2 Stats::getmonthlybandwidth).

WHM — Accounts

$whm->accounts() — the account lifecycle, WHM API 1.

MethodUnderlying functionNotes
list(?string $search = null, string $searchType = 'user')listacctssearchType: user, domain, owner, ip, package
summary(string $user)accountsummary
create(string $username, string $domain, array $options = [])createacctoptions: plan, password, contactemail, quota, …
remove(string $user, bool $keepDns = false)removeacctDestructive
suspend(string $user, string $reason = '') / unsuspend(string $user)suspendacct / unsuspendacct
changePassword(string $user, string $password)passwd
modify(string $user, array $options)modifyacct
changePlan(string $user, string $plan)changepackage
domainOwner(string $domain)domainuserdata
createUserSession(string $user, string $service = 'cpaneld')create_user_sessionOne-click SSO URL into the user's cPanel
bandwidth(?string $user = null, ?string $month = null, ?string $year = null)showbwBandwidth usage, optionally filtered
limitBandwidth(string $user, int $limitMb)limitbw
$session = $whm->accounts()->createUserSession('customer1');
$redirectUrl = $session->data('url');

WHM — Resellers

$whm->resellers() — the full reseller lifecycle for multi-tier hosting.

MethodUnderlying functionNotes
list()listresellers
stats(string $reseller)resellerstatsDisk/bandwidth totals across owned accounts
accounts(string $reseller)acctcountsUsed/limit account counts
promote(string $user, bool $ownsSelf = false)setupresellerTurns an existing account into a reseller
demote(string $user)unsetupreseller
setLimits(string $user, array $limits)setresellerlimitse.g. enable_account_limit, account_limit, diskspace_limit
setPackageLimit(string $user, string $package, bool $allowed, ?int $number = null)setresellerpackagelimitRestrict which plans a reseller may sell
setAcls(string $reseller, array $acls)setaclsFine-grained privilege grants
setMainIp(string $user, string $ip)setresellermainip
setNameservers(string $user, array $nameservers)setresellernameservers
suspendAccounts(string $reseller) / unsuspendAccounts(string $reseller)suspendreseller / unsuspendresellerSuspends the reseller and all owned accounts
$whm->resellers()->promote('reseller1');
$whm->resellers()->setLimits('reseller1', ['enable_account_limit' => 1, 'account_limit' => 30]);
$whm->resellers()->setPackageLimit('reseller1', 'starter', allowed: true, number: 20);

WHM — Packages

$whm->packages()list(), create(), update(), delete() around listpkgs / addpkg / editpkg / killpkg.

$whm->packages()->create('starter', ['quota' => 5120, 'bwlimit' => 51200, 'maxaddons' => 1]);

WHM — DNS zones

$whm->dnsZones() — full zone lifecycle: list(), dump(), create(), delete(), addRecord(), editRecord(), removeRecord(), reset().

$whm->dnsZones()->create('customer1.com', '203.0.113.10');
$whm->dnsZones()->addRecord('customer1.com', [
'name' => 'mail',
'type' => 'A',
'address' => '203.0.113.10',
'ttl' => 3600,
]);

WHM — IP addresses

$whm->ips() — IP pool management for dedicated-IP offers.

MethodUnderlying function
list()listips
add(string $ip, string $netmask)addips
delete(string $ip)delip
assignToSite(string $domain, string $ip) / assignToUser(string $user, string $ip)setsiteip
usage()get_shared_ip
$whm->ips()->add('203.0.113.25', '255.255.255.0');
$whm->ips()->assignToSite('customer1.com', '203.0.113.25');

WHM — Security (cPHulk)

$whm->security() — brute-force protection management, the bread and butter of hosting support.

MethodUnderlying function
enableCphulk() / disableCphulk()enable_cphulk / disable_cphulk
whitelist(string $ip, string $comment = '') / blacklist(...)create_cphulk_record
listWhitelist() / listBlacklist()read_cphulk_records
removeFromWhitelist(string $ip) / removeFromBlacklist(string $ip)delete_cphulk_record
unblockBrute(string $ip)flush_cphulk_login_history_for_ips
flushLoginHistory()flush_cphulk_login_history
$whm->security()->unblockBrute('198.51.100.7');
$whm->security()->whitelist('203.0.113.50', 'office VPN');

WHM — Backups & restores

$whm->backups() — backup configuration and the account restore queue.

MethodUnderlying functionNotes
config() / setConfig(array $settings)backup_config_get / backup_config_set
users()backup_user_listUsers with backup metadata
dates()backup_date_listAvailable restore points
userBackups(string $user)backup_set_list
queueRestore(string $user, string $restorePoint, array $options = [])restore_queue_add_taskDefaults: keep IP, restore MySQL/subdomains/mail config
activateRestoreQueue()restore_queue_activateStarts processing queued restores
restoreQueueState()restore_queue_statePoll for progress
clearCompletedRestores()restore_queue_clear_completed_tasks
$whm->backups()->queueRestore('customer1', '2026-07-20');
$whm->backups()->activateRestoreQueue();

WHM — PHP versions

$whm->php() — MultiPHP management per virtual host.

MethodUnderlying function
installedVersions()php_get_installed_versions
systemDefault() / setSystemDefault(string $version)php_get_system_default_version / php_set_system_default_version
vhostVersions(string ...$vhosts)php_get_vhost_versions
setVhostVersion(string $version, string ...$vhosts)php_set_vhost_versions
handlers(string $version) / setHandler(string $version, string $handler)php_get_handlers / php_set_handler

Versions use EasyApache identifiers (ea-php83), not bare numbers.

$whm->php()->setVhostVersion('ea-php83', 'example.com', 'shop.example.com');

WHM — SSL & AutoSSL

$whm->autoSsl() — server-wide certificate automation plus manual installs with root privileges.

MethodUnderlying function
providers() / setProvider(string $provider)get_autossl_providers / set_autossl_provider
checkAllUsers()start_autossl_check_for_all_users
checkUser(string $user)start_autossl_check_for_one_user
enableForUser(string $user) / disableForUser(string $user)set_autossl_feature_for_users
installCertificate(string $domain, string $cert, string $key, ?string $caBundle = null)installssl
certificateInfo(string $domain)fetch_ssl_vhosts
$whm->autoSsl()->setProvider('LetsEncrypt');
$whm->autoSsl()->checkUser('customer1');

WHM — Server configuration

$whm->config() — Tweak Settings and global server preferences.

MethodUnderlying function
tweakSetting(string $key, string $module = 'Main')get_tweaksetting
setTweakSetting(string $key, string|int $value, string $module = 'Main')set_tweaksetting
updatePreferences() / setUpdatePreferences(array $settings)get_update_config / update_updateconf
hostname() / setHostname(string $hostname)gethostname / sethostname
nameserverConfig()nameserverconfig
$whm->config()->setTweakSetting('maxemailsperhour', 200);

WHM — Server

$whm->server()version(), hostname(), loadAverage(), serviceStatus(), restartService().

$whm->server()->serviceStatus('httpd');
$whm->server()->restartService('exim');

Responses

All calls return an immutable ApiResponse that normalizes the three wire formats (UAPI envelope, API2 cpanelresult, WHM metadata):

$response = $cpanel->mysql()->databases();
$response->success; // bool$response->data; // mixed — the payload's data section$response->data('acct'); // keyed access with optional default$response->errors; // list<string>$response->messages; // list<string>$response->warnings; // list<string>$response->raw; // the complete decoded JSON payload

data() is null-safe: it returns the default when the payload has no such key or when data is not an array.

Error Handling

All SDK exceptions implement CpanelSdkExceptionInterface, so a single catch covers everything:

ExceptionThrown whenExtras
ApiExceptionThe API answered but reported a failure (module methods validate automatically)getErrors(): array, getRaw(): array
AuthenticationExceptionCredentials are missing, or the server answered HTTP 401/403thrown before any request when credentials are empty
TransportExceptionNetwork error, TLS failure, timeout, or a non-JSON response bodywraps the underlying symfony/http-client exception
useChuckBartowski\CpanelSdk\Exception\ApiException;
useChuckBartowski\CpanelSdk\Exception\CpanelSdkExceptionInterface;
try {
$cpanel->email()->create('support', 'example.com', $password);
} catch (ApiException$e) {
$this->logger->warning('cPanel rejected the mailbox', ['errors' => $e->getErrors()]);
} catch (CpanelSdkExceptionInterface$e) {
thrownewProvisioningUnavailableException(previous: $e);
}

To inspect a failed response without exceptions, use the client directly — client-level methods return the response as-is:

$response = $cpanel->client()->uapi('Email', 'add_pop', $params, 'POST');
if (!$response->success) {
// $response->errors, $response->raw
}

Testing

The suite runs entirely offline against MockHttpClient:

composer install
vendor/bin/phpunit

To test your own services, inject a CpanelClient/WhmClient built with a mock:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Cpanel;
useSymfony\Component\HttpClient\MockHttpClient;
useSymfony\Component\HttpClient\Response\JsonMockResponse;
$http = newMockHttpClient(newJsonMockResponse(['status' => 1, 'data' => []]));
$cpanel = newCpanel(newCpanelClient('host', 'user', 'token', 2083, true, 30.0, $http));

Security Notes

  • API tokens are passed with #[\SensitiveParameter], so they never appear in stack traces.
  • Keep tokens in .env.local or your secret vault — never commit them.
  • Scope WHM tokens to the minimal privilege set in WHM » Manage API Tokens (e.g. deny Everything, allow only account functions).
  • Leave verify_ssl: true in production; the option exists solely for self-signed development servers.
  • removeacct and delete_ftp destroy=1 are irreversible — gate them behind confirmation flows in your application.

WHMCS module

A ready-to-use WHMCS provisioning module ships in whmcs/modules/servers/cpanelsdk/. It automates cPanel account provisioning through WHM using this SDK — create, suspend, unsuspend, terminate, change password, change package, and one-click SSO into cPanel.

Install

  1. composer require chuckbartowski/cpanel-sdk in your WHMCS root (so the SDK is autoloaded).
  2. Copy the cpanelsdk folder into <whmcs>/modules/servers/.
  3. In WHMCS, add a server (System Settings » Servers) with Type: cPanel (SDK), the WHM hostname, username root, and your WHM API token in the Access Hash field.
  4. Point a product at the server and set the Package config option to the WHM plan name.
OperationWHM function used
Create / Suspend / Unsuspend / Terminatecreateacct / suspendacct / unsuspendacct / removeacct
Change password / packagepasswd / changepackage
One-click logincreate_user_session

License

MIT

About

PHP SDK for the cPanel & WHM APIs (UAPI, API2, WHM API 1).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

cPanel & WHM SDK for PHP

🖥 cPanel & WHM SDK for PHP

A modern, fully typed PHP SDK for driving cPanel (UAPI + API2) and WHM (API 1).

PHP VersionSymfonyTestsPackagistLicense

Email · DNS · MySQL · SSL · FTP · Files · Accounts · Resellers · Packages · AutoSSL · Backups

Installation · Quick Start · API Reference · Error Handling


$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);
$whm->accounts()->createUserSession('customer1');

Framework-agnostic core — usable from any PHP project, script, or worker — with an optional bundle for first-class Symfony integration. Authenticated with API tokens, typed exceptions, and a comment-free, strictly typed codebase (PHP 8.2+, declare(strict_types=1) everywhere).


Table of Contents


Features

  • Full coverage of the three cPanel API surfaces: UAPI (the modern cPanel API), API2 (legacy but still required for zone editing, subdomains, addon domains, file operations), and WHM API 1 (server administration).
  • Framework-agnostic: two plain facades (Cpanel, Whm) you can instantiate anywhere; only hard dependency is symfony/http-client, a standalone component that works in any PHP project.
  • Token authentication only — no passwords, no sessions, no cookies. Uses the official Authorization: cpanel user:token / Authorization: whm user:token schemes.
  • High-level, discoverable modules grouped by domain: email, MySQL, DNS, SSL, files, accounts, resellers, backups, PHP versions, security…
  • A single normalized response object (ApiResponse) regardless of which underlying API answered — you never parse cpanelresult or metadata envelopes yourself.
  • Typed exception hierarchy under one marker interface, so you can catch narrowly or broadly.
  • Escape hatches everywhere: any endpoint not wrapped by a module remains one method call away.
  • Optional Symfony bundle with semantic configuration and autowirable services.
  • Fully unit-tested against MockHttpClient (no network required).

Requirements

DependencyVersion
PHP>= 8.2
cPanel/WHMany version supporting API tokens (v64+)
Symfony6.4 LTS or 7.x — optional, only for the bundle integration

You will need at least one of:

  • a cPanel API token — created in cPanel » Security » Manage API Tokens
  • a WHM API token — created in WHM » Development » Manage API Tokens

Installation

The package is published on Packagist:

composer require chuckbartowski/cpanel-sdk

Quick Start (plain PHP)

No framework required — build the clients and go:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Client\WhmClient;
useChuckBartowski\CpanelSdk\Cpanel;
useChuckBartowski\CpanelSdk\Whm;
$cpanel = newCpanel(newCpanelClient(
host: 'server.example.com',
username: 'myaccount',
token: getenv('CPANEL_API_TOKEN'),
port: 2083,
));
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10');
$whm = newWhm(newWhmClient(
host: 'server.example.com',
username: 'root',
token: getenv('WHM_API_TOKEN'),
port: 2087,
));
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);

Client constructor signature (identical for both clients):

newCpanelClient(
string $host,
string $username,
string $token,
int $port, // 2083 for cPanel, 2087 for WHM
bool $verifySsl = true,
float $timeout = 30.0,
?HttpClientInterface $httpClient = null, // inject your own (retries, proxy, mock…)
);

Symfony Integration (optional)

A ready-made bundle wires everything into the container. Register it:

// config/bundles.phpreturn [
ChuckBartowski\CpanelSdk\CpanelSdkBundle::class => ['all' => true],
];

Then create config/packages/cpanel_sdk.yaml:

cpanel_sdk:
host: '%env(CPANEL_HOST)%'verify_ssl: truetimeout: 30cpanel:
username: '%env(CPANEL_USERNAME)%'token: '%env(CPANEL_API_TOKEN)%'port: 2083whm:
username: '%env(WHM_USERNAME)%'token: '%env(WHM_API_TOKEN)%'port: 2087

And the matching environment variables:

# .env.localCPANEL_HOST=server.example.comCPANEL_USERNAME=myaccountCPANEL_API_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWHM_USERNAME=rootWHM_API_TOKEN=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

Configuration reference

KeyTypeDefaultDescription
hoststringrequiredHostname of the cPanel/WHM server (no scheme, no port)
verify_sslbooltrueTLS peer/host verification; disable only for self-signed dev servers
timeoutfloat30.0Per-request timeout in seconds
cpanel.usernamestring''cPanel account name
cpanel.tokenstring''cPanel API token
cpanel.portint2083cPanel TLS port
whm.usernamestring''WHM user (usually root or a reseller)
whm.tokenstring''WHM API token
whm.portint2087WHM TLS port

The cpanel and whm sections are independent — configure only the side you need. Calling a client with missing credentials throws an AuthenticationException immediately, before any network request is made.

The bundle reuses your application's http_client service when available (so scoped clients, retry strategies, and profiler integration all apply), and falls back to a native client otherwise.

Architecture

src/
├── CpanelSdkBundle.php Symfony bundle: config tree + service wiring
├── Cpanel.php Facade: entry point for cPanel-level modules
├── Whm.php Facade: entry point for WHM-level modules
├── Client/
│ ├── AbstractClient.php Shared HTTP transport, auth header, error mapping
│ ├── CpanelClient.php uapi() and api2() generic executors
│ └── WhmClient.php call() (WHM API 1) and cpanelUapi() (root proxy)
├── Response/
│ └── ApiResponse.php Immutable, normalized response for all 3 API formats
├── Exception/
│ ├── CpanelSdkExceptionInterface.php
│ ├── ApiException.php API answered but reported a failure
│ ├── AuthenticationException.php
│ └── TransportException.php Network / TLS / timeout / invalid JSON
└── Api/
├── Cpanel/ EmailApi, DomainApi, MysqlApi, FtpApi,
│ SslApi, FileApi, DnsApi, StatsApi
└── Whm/ AccountApi, ResellerApi, PackageApi, DnsZoneApi,
IpApi, SecurityApi, BackupApi, PhpApi,
AutoSslApi, ConfigApi, ServerApi

Design decisions:

  • Facade + lazy modules: Cpanel/Whm instantiate each module on first use and cache it, so the DI container only carries four services.
  • Modules always validate: every module method calls ensureSuccess() internally and throws ApiException on failure. If you need to inspect a failed response without an exception, drop down to the client level.
  • Nothing is sealed off: the clients' generic methods accept any module/function/parameter combination, so a cPanel endpoint added tomorrow is usable today.

Usage

Standalone, instantiate the facades as shown in the Quick Start. In Symfony, both facades are autowirable in controllers, services, commands, and message handlers.

The Cpanel facade

useChuckBartowski\CpanelSdk\Cpanel;
finalclass MailboxProvisioner
{
publicfunction__construct(privatereadonlyCpanel$cpanel)
{
}
publicfunctionprovision(string$localPart, string$domain, string$password): void
{
$this->cpanel->email()->create($localPart, $domain, $password, quotaMb: 512);
}
}

The Whm facade

useChuckBartowski\CpanelSdk\Whm;
finalclass HostingAccountManager
{
publicfunction__construct(privatereadonlyWhm$whm)
{
}
publicfunctionopen(string$username, string$domain): void
{
$this->whm->accounts()->create($username, $domain, [
'plan' => 'starter',
'contactemail' => 'billing@example.com',
]);
}
publicfunctionsuspendForNonPayment(string$username): void
{
$this->whm->accounts()->suspend($username, 'unpaid invoice');
}
}

Generic calls (escape hatch)

Any endpoint not covered by a module remains reachable:

$cpanel->client()->uapi('Batch', 'strict', ['command' => $commands], 'POST');
$cpanel->client()->api2('Cron', 'listcron');
$whm->client()->call('sethostname', ['hostname' => 'srv2.example.com'], 'POST');
$whm->client()->cpanelUapi('customer1', 'Email', 'list_pops');

cpanelUapi() runs a UAPI function as any cPanel account through the WHM token — the standard pattern for hosting control panels where only the root/reseller token is stored.

API Reference

Every method returns an ApiResponse and throws on failure (see Error Handling). Named arguments are shown where they improve readability.

Email

$cpanel->email() — UAPI Email module.

MethodUnderlying functionNotes
accounts(?string $domain = null)list_pops_with_diskIncludes disk usage per mailbox
create(string $localPart, string $domain, string $password, int $quotaMb = 0)add_pop0 = unlimited quota
delete(string $localPart, string $domain)delete_pop
changePassword(string $localPart, string $domain, string $password)passwd_pop
setQuota(string $localPart, string $domain, int $quotaMb)edit_pop_quota
forwarders(?string $domain = null)list_forwarders
addForwarder(string $domain, string $localPart, string $destination)add_forwarder
deleteForwarder(string $address, string $forwarder)delete_forwarder
mailDirUsage(string $localPart, string $domain)get_pop_quota
$cpanel->email()->accounts('example.com');
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->email()->addForwarder('example.com', 'contact', 'inbox@elsewhere.com');

Domains

$cpanel->domains() — UAPI DomainInfo + API2 SubDomain / AddonDomain / Park.

MethodUnderlying function
list()UAPI DomainInfo::list_domains
data(?string $domain = null)UAPI domains_data / single_domain_data
addSubdomain(string $subdomain, string $rootDomain, ?string $documentRoot = null)API2 SubDomain::addsubdomain
deleteSubdomain(string $subdomain, string $rootDomain)API2 SubDomain::delsubdomain
addAddonDomain(string $newDomain, string $subdomain, string $documentRoot)API2 AddonDomain::addaddondomain
deleteAddonDomain(string $domain, string $subdomain)API2 AddonDomain::deladdondomain
park(string $domain) / unpark(string $domain)API2 Park
$cpanel->domains()->addSubdomain('api', 'example.com', 'public_html/api');

MySQL

$cpanel->mysql() — UAPI Mysql module.

MethodUnderlying function
databases() / users()list_databases / list_users
createDatabase(string $name) / deleteDatabase(string $name)create_database / delete_database
renameDatabase(string $oldName, string $newName)rename_database
createUser(string $name, string $password) / deleteUser(string $name)create_user / delete_user
setPassword(string $user, string $password)set_password
grant(string $user, string $database, string $privileges = 'ALL PRIVILEGES')set_privileges_on_database
revoke(string $user, string $database)revoke_access_to_database
addHost(string $host)add_host

Remember that cPanel prefixes database and user names with the account name (myaccount_app).

$cpanel->mysql()->createDatabase('myaccount_app');
$cpanel->mysql()->createUser('myaccount_app', 'S3cret!');
$cpanel->mysql()->grant('myaccount_app', 'myaccount_app');

FTP

$cpanel->ftp() — UAPI Ftp module: accounts(), create(), delete() (with optional home-dir destruction), changePassword(), setQuota(), setHomeDir().

$cpanel->ftp()->create('deploy', 'S3cret!', homeDir: 'public_html', quotaMb: 0);
$cpanel->ftp()->delete('deploy', destroyHomeDir: false);

SSL

$cpanel->ssl() — UAPI SSL module: certificates(), installedHosts(), install(), delete(), generateKey(), generateCsr().

$cpanel->ssl()->install('example.com', $certificatePem, $keyPem, $caBundlePem);

Files

$cpanel->files() — UAPI Fileman for content, API2 Fileman::fileop for filesystem operations: list(), read(), write(), info(), mkdir(), delete(), copy(), move(), chmod(), extract(), emptyTrash().

$cpanel->files()->write('public_html', '.htaccess', $rules);
$cpanel->files()->extract('backup.tar.gz', 'public_html');
$cpanel->files()->chmod('public_html/config.php', '0600');

DNS (cPanel zone editor)

$cpanel->dns() — API2 ZoneEdit module. addRecord() automatically maps the value to the right parameter name for the record type (address for A/AAAA, cname for CNAME, txtdata for TXT, exchange for MX…).

$cpanel->dns()->records('example.com', ['type' => 'A']);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10', ttl: 3600);
$cpanel->dns()->editRecord('example.com', line: 22, params: ['address' => '203.0.113.11']);
$cpanel->dns()->removeRecord('example.com', line: 22);

API2 zone records are addressed by line number in the zone file; always re-fetch records after a mutation before addressing another line.

Stats & quotas

$cpanel->stats()quota() (UAPI Quota), bars() (UAPI StatsBar, configurable display list), bandwidth() (API2 Stats::getmonthlybandwidth).

WHM — Accounts

$whm->accounts() — the account lifecycle, WHM API 1.

MethodUnderlying functionNotes
list(?string $search = null, string $searchType = 'user')listacctssearchType: user, domain, owner, ip, package
summary(string $user)accountsummary
create(string $username, string $domain, array $options = [])createacctoptions: plan, password, contactemail, quota, …
remove(string $user, bool $keepDns = false)removeacctDestructive
suspend(string $user, string $reason = '') / unsuspend(string $user)suspendacct / unsuspendacct
changePassword(string $user, string $password)passwd
modify(string $user, array $options)modifyacct
changePlan(string $user, string $plan)changepackage
domainOwner(string $domain)domainuserdata
createUserSession(string $user, string $service = 'cpaneld')create_user_sessionOne-click SSO URL into the user's cPanel
bandwidth(?string $user = null, ?string $month = null, ?string $year = null)showbwBandwidth usage, optionally filtered
limitBandwidth(string $user, int $limitMb)limitbw
$session = $whm->accounts()->createUserSession('customer1');
$redirectUrl = $session->data('url');

WHM — Resellers

$whm->resellers() — the full reseller lifecycle for multi-tier hosting.

MethodUnderlying functionNotes
list()listresellers
stats(string $reseller)resellerstatsDisk/bandwidth totals across owned accounts
accounts(string $reseller)acctcountsUsed/limit account counts
promote(string $user, bool $ownsSelf = false)setupresellerTurns an existing account into a reseller
demote(string $user)unsetupreseller
setLimits(string $user, array $limits)setresellerlimitse.g. enable_account_limit, account_limit, diskspace_limit
setPackageLimit(string $user, string $package, bool $allowed, ?int $number = null)setresellerpackagelimitRestrict which plans a reseller may sell
setAcls(string $reseller, array $acls)setaclsFine-grained privilege grants
setMainIp(string $user, string $ip)setresellermainip
setNameservers(string $user, array $nameservers)setresellernameservers
suspendAccounts(string $reseller) / unsuspendAccounts(string $reseller)suspendreseller / unsuspendresellerSuspends the reseller and all owned accounts
$whm->resellers()->promote('reseller1');
$whm->resellers()->setLimits('reseller1', ['enable_account_limit' => 1, 'account_limit' => 30]);
$whm->resellers()->setPackageLimit('reseller1', 'starter', allowed: true, number: 20);

WHM — Packages

$whm->packages()list(), create(), update(), delete() around listpkgs / addpkg / editpkg / killpkg.

$whm->packages()->create('starter', ['quota' => 5120, 'bwlimit' => 51200, 'maxaddons' => 1]);

WHM — DNS zones

$whm->dnsZones() — full zone lifecycle: list(), dump(), create(), delete(), addRecord(), editRecord(), removeRecord(), reset().

$whm->dnsZones()->create('customer1.com', '203.0.113.10');
$whm->dnsZones()->addRecord('customer1.com', [
'name' => 'mail',
'type' => 'A',
'address' => '203.0.113.10',
'ttl' => 3600,
]);

WHM — IP addresses

$whm->ips() — IP pool management for dedicated-IP offers.

MethodUnderlying function
list()listips
add(string $ip, string $netmask)addips
delete(string $ip)delip
assignToSite(string $domain, string $ip) / assignToUser(string $user, string $ip)setsiteip
usage()get_shared_ip
$whm->ips()->add('203.0.113.25', '255.255.255.0');
$whm->ips()->assignToSite('customer1.com', '203.0.113.25');

WHM — Security (cPHulk)

$whm->security() — brute-force protection management, the bread and butter of hosting support.

MethodUnderlying function
enableCphulk() / disableCphulk()enable_cphulk / disable_cphulk
whitelist(string $ip, string $comment = '') / blacklist(...)create_cphulk_record
listWhitelist() / listBlacklist()read_cphulk_records
removeFromWhitelist(string $ip) / removeFromBlacklist(string $ip)delete_cphulk_record
unblockBrute(string $ip)flush_cphulk_login_history_for_ips
flushLoginHistory()flush_cphulk_login_history
$whm->security()->unblockBrute('198.51.100.7');
$whm->security()->whitelist('203.0.113.50', 'office VPN');

WHM — Backups & restores

$whm->backups() — backup configuration and the account restore queue.

MethodUnderlying functionNotes
config() / setConfig(array $settings)backup_config_get / backup_config_set
users()backup_user_listUsers with backup metadata
dates()backup_date_listAvailable restore points
userBackups(string $user)backup_set_list
queueRestore(string $user, string $restorePoint, array $options = [])restore_queue_add_taskDefaults: keep IP, restore MySQL/subdomains/mail config
activateRestoreQueue()restore_queue_activateStarts processing queued restores
restoreQueueState()restore_queue_statePoll for progress
clearCompletedRestores()restore_queue_clear_completed_tasks
$whm->backups()->queueRestore('customer1', '2026-07-20');
$whm->backups()->activateRestoreQueue();

WHM — PHP versions

$whm->php() — MultiPHP management per virtual host.

MethodUnderlying function
installedVersions()php_get_installed_versions
systemDefault() / setSystemDefault(string $version)php_get_system_default_version / php_set_system_default_version
vhostVersions(string ...$vhosts)php_get_vhost_versions
setVhostVersion(string $version, string ...$vhosts)php_set_vhost_versions
handlers(string $version) / setHandler(string $version, string $handler)php_get_handlers / php_set_handler

Versions use EasyApache identifiers (ea-php83), not bare numbers.

$whm->php()->setVhostVersion('ea-php83', 'example.com', 'shop.example.com');

WHM — SSL & AutoSSL

$whm->autoSsl() — server-wide certificate automation plus manual installs with root privileges.

MethodUnderlying function
providers() / setProvider(string $provider)get_autossl_providers / set_autossl_provider
checkAllUsers()start_autossl_check_for_all_users
checkUser(string $user)start_autossl_check_for_one_user
enableForUser(string $user) / disableForUser(string $user)set_autossl_feature_for_users
installCertificate(string $domain, string $cert, string $key, ?string $caBundle = null)installssl
certificateInfo(string $domain)fetch_ssl_vhosts
$whm->autoSsl()->setProvider('LetsEncrypt');
$whm->autoSsl()->checkUser('customer1');

WHM — Server configuration

$whm->config() — Tweak Settings and global server preferences.

MethodUnderlying function
tweakSetting(string $key, string $module = 'Main')get_tweaksetting
setTweakSetting(string $key, string|int $value, string $module = 'Main')set_tweaksetting
updatePreferences() / setUpdatePreferences(array $settings)get_update_config / update_updateconf
hostname() / setHostname(string $hostname)gethostname / sethostname
nameserverConfig()nameserverconfig
$whm->config()->setTweakSetting('maxemailsperhour', 200);

WHM — Server

$whm->server()version(), hostname(), loadAverage(), serviceStatus(), restartService().

$whm->server()->serviceStatus('httpd');
$whm->server()->restartService('exim');

Responses

All calls return an immutable ApiResponse that normalizes the three wire formats (UAPI envelope, API2 cpanelresult, WHM metadata):

$response = $cpanel->mysql()->databases();
$response->success; // bool$response->data; // mixed — the payload's data section$response->data('acct'); // keyed access with optional default$response->errors; // list<string>$response->messages; // list<string>$response->warnings; // list<string>$response->raw; // the complete decoded JSON payload

data() is null-safe: it returns the default when the payload has no such key or when data is not an array.

Error Handling

All SDK exceptions implement CpanelSdkExceptionInterface, so a single catch covers everything:

ExceptionThrown whenExtras
ApiExceptionThe API answered but reported a failure (module methods validate automatically)getErrors(): array, getRaw(): array
AuthenticationExceptionCredentials are missing, or the server answered HTTP 401/403thrown before any request when credentials are empty
TransportExceptionNetwork error, TLS failure, timeout, or a non-JSON response bodywraps the underlying symfony/http-client exception
useChuckBartowski\CpanelSdk\Exception\ApiException;
useChuckBartowski\CpanelSdk\Exception\CpanelSdkExceptionInterface;
try {
$cpanel->email()->create('support', 'example.com', $password);
} catch (ApiException$e) {
$this->logger->warning('cPanel rejected the mailbox', ['errors' => $e->getErrors()]);
} catch (CpanelSdkExceptionInterface$e) {
thrownewProvisioningUnavailableException(previous: $e);
}

To inspect a failed response without exceptions, use the client directly — client-level methods return the response as-is:

$response = $cpanel->client()->uapi('Email', 'add_pop', $params, 'POST');
if (!$response->success) {
// $response->errors, $response->raw
}

Testing

The suite runs entirely offline against MockHttpClient:

composer install
vendor/bin/phpunit

To test your own services, inject a CpanelClient/WhmClient built with a mock:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Cpanel;
useSymfony\Component\HttpClient\MockHttpClient;
useSymfony\Component\HttpClient\Response\JsonMockResponse;
$http = newMockHttpClient(newJsonMockResponse(['status' => 1, 'data' => []]));
$cpanel = newCpanel(newCpanelClient('host', 'user', 'token', 2083, true, 30.0, $http));

Security Notes

  • API tokens are passed with #[\SensitiveParameter], so they never appear in stack traces.
  • Keep tokens in .env.local or your secret vault — never commit them.
  • Scope WHM tokens to the minimal privilege set in WHM » Manage API Tokens (e.g. deny Everything, allow only account functions).
  • Leave verify_ssl: true in production; the option exists solely for self-signed development servers.
  • removeacct and delete_ftp destroy=1 are irreversible — gate them behind confirmation flows in your application.

WHMCS module

A ready-to-use WHMCS provisioning module ships in whmcs/modules/servers/cpanelsdk/. It automates cPanel account provisioning through WHM using this SDK — create, suspend, unsuspend, terminate, change password, change package, and one-click SSO into cPanel.

Install

  1. composer require chuckbartowski/cpanel-sdk in your WHMCS root (so the SDK is autoloaded).
  2. Copy the cpanelsdk folder into <whmcs>/modules/servers/.
  3. In WHMCS, add a server (System Settings » Servers) with Type: cPanel (SDK), the WHM hostname, username root, and your WHM API token in the Access Hash field.
  4. Point a product at the server and set the Package config option to the WHM plan name.
OperationWHM function used
Create / Suspend / Unsuspend / Terminatecreateacct / suspendacct / unsuspendacct / removeacct
Change password / packagepasswd / changepackage
One-click logincreate_user_session

License

MIT

About

PHP SDK for the cPanel & WHM APIs (UAPI, API2, WHM API 1).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

cPanel & WHM SDK for PHP

🖥 cPanel & WHM SDK for PHP

A modern, fully typed PHP SDK for driving cPanel (UAPI + API2) and WHM (API 1).

PHP VersionSymfonyTestsPackagistLicense

Email · DNS · MySQL · SSL · FTP · Files · Accounts · Resellers · Packages · AutoSSL · Backups

Installation · Quick Start · API Reference · Error Handling


$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);
$whm->accounts()->createUserSession('customer1');

Framework-agnostic core — usable from any PHP project, script, or worker — with an optional bundle for first-class Symfony integration. Authenticated with API tokens, typed exceptions, and a comment-free, strictly typed codebase (PHP 8.2+, declare(strict_types=1) everywhere).


Table of Contents


Features

  • Full coverage of the three cPanel API surfaces: UAPI (the modern cPanel API), API2 (legacy but still required for zone editing, subdomains, addon domains, file operations), and WHM API 1 (server administration).
  • Framework-agnostic: two plain facades (Cpanel, Whm) you can instantiate anywhere; only hard dependency is symfony/http-client, a standalone component that works in any PHP project.
  • Token authentication only — no passwords, no sessions, no cookies. Uses the official Authorization: cpanel user:token / Authorization: whm user:token schemes.
  • High-level, discoverable modules grouped by domain: email, MySQL, DNS, SSL, files, accounts, resellers, backups, PHP versions, security…
  • A single normalized response object (ApiResponse) regardless of which underlying API answered — you never parse cpanelresult or metadata envelopes yourself.
  • Typed exception hierarchy under one marker interface, so you can catch narrowly or broadly.
  • Escape hatches everywhere: any endpoint not wrapped by a module remains one method call away.
  • Optional Symfony bundle with semantic configuration and autowirable services.
  • Fully unit-tested against MockHttpClient (no network required).

Requirements

DependencyVersion
PHP>= 8.2
cPanel/WHMany version supporting API tokens (v64+)
Symfony6.4 LTS or 7.x — optional, only for the bundle integration

You will need at least one of:

  • a cPanel API token — created in cPanel » Security » Manage API Tokens
  • a WHM API token — created in WHM » Development » Manage API Tokens

Installation

The package is published on Packagist:

composer require chuckbartowski/cpanel-sdk

Quick Start (plain PHP)

No framework required — build the clients and go:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Client\WhmClient;
useChuckBartowski\CpanelSdk\Cpanel;
useChuckBartowski\CpanelSdk\Whm;
$cpanel = newCpanel(newCpanelClient(
host: 'server.example.com',
username: 'myaccount',
token: getenv('CPANEL_API_TOKEN'),
port: 2083,
));
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10');
$whm = newWhm(newWhmClient(
host: 'server.example.com',
username: 'root',
token: getenv('WHM_API_TOKEN'),
port: 2087,
));
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);

Client constructor signature (identical for both clients):

newCpanelClient(
string $host,
string $username,
string $token,
int $port, // 2083 for cPanel, 2087 for WHM
bool $verifySsl = true,
float $timeout = 30.0,
?HttpClientInterface $httpClient = null, // inject your own (retries, proxy, mock…)
);

Symfony Integration (optional)

A ready-made bundle wires everything into the container. Register it:

// config/bundles.phpreturn [
ChuckBartowski\CpanelSdk\CpanelSdkBundle::class => ['all' => true],
];

Then create config/packages/cpanel_sdk.yaml:

cpanel_sdk:
host: '%env(CPANEL_HOST)%'verify_ssl: truetimeout: 30cpanel:
username: '%env(CPANEL_USERNAME)%'token: '%env(CPANEL_API_TOKEN)%'port: 2083whm:
username: '%env(WHM_USERNAME)%'token: '%env(WHM_API_TOKEN)%'port: 2087

And the matching environment variables:

# .env.localCPANEL_HOST=server.example.comCPANEL_USERNAME=myaccountCPANEL_API_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWHM_USERNAME=rootWHM_API_TOKEN=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

Configuration reference

KeyTypeDefaultDescription
hoststringrequiredHostname of the cPanel/WHM server (no scheme, no port)
verify_sslbooltrueTLS peer/host verification; disable only for self-signed dev servers
timeoutfloat30.0Per-request timeout in seconds
cpanel.usernamestring''cPanel account name
cpanel.tokenstring''cPanel API token
cpanel.portint2083cPanel TLS port
whm.usernamestring''WHM user (usually root or a reseller)
whm.tokenstring''WHM API token
whm.portint2087WHM TLS port

The cpanel and whm sections are independent — configure only the side you need. Calling a client with missing credentials throws an AuthenticationException immediately, before any network request is made.

The bundle reuses your application's http_client service when available (so scoped clients, retry strategies, and profiler integration all apply), and falls back to a native client otherwise.

Architecture

src/
├── CpanelSdkBundle.php Symfony bundle: config tree + service wiring
├── Cpanel.php Facade: entry point for cPanel-level modules
├── Whm.php Facade: entry point for WHM-level modules
├── Client/
│ ├── AbstractClient.php Shared HTTP transport, auth header, error mapping
│ ├── CpanelClient.php uapi() and api2() generic executors
│ └── WhmClient.php call() (WHM API 1) and cpanelUapi() (root proxy)
├── Response/
│ └── ApiResponse.php Immutable, normalized response for all 3 API formats
├── Exception/
│ ├── CpanelSdkExceptionInterface.php
│ ├── ApiException.php API answered but reported a failure
│ ├── AuthenticationException.php
│ └── TransportException.php Network / TLS / timeout / invalid JSON
└── Api/
├── Cpanel/ EmailApi, DomainApi, MysqlApi, FtpApi,
│ SslApi, FileApi, DnsApi, StatsApi
└── Whm/ AccountApi, ResellerApi, PackageApi, DnsZoneApi,
IpApi, SecurityApi, BackupApi, PhpApi,
AutoSslApi, ConfigApi, ServerApi

Design decisions:

  • Facade + lazy modules: Cpanel/Whm instantiate each module on first use and cache it, so the DI container only carries four services.
  • Modules always validate: every module method calls ensureSuccess() internally and throws ApiException on failure. If you need to inspect a failed response without an exception, drop down to the client level.
  • Nothing is sealed off: the clients' generic methods accept any module/function/parameter combination, so a cPanel endpoint added tomorrow is usable today.

Usage

Standalone, instantiate the facades as shown in the Quick Start. In Symfony, both facades are autowirable in controllers, services, commands, and message handlers.

The Cpanel facade

useChuckBartowski\CpanelSdk\Cpanel;
finalclass MailboxProvisioner
{
publicfunction__construct(privatereadonlyCpanel$cpanel)
{
}
publicfunctionprovision(string$localPart, string$domain, string$password): void
{
$this->cpanel->email()->create($localPart, $domain, $password, quotaMb: 512);
}
}

The Whm facade

useChuckBartowski\CpanelSdk\Whm;
finalclass HostingAccountManager
{
publicfunction__construct(privatereadonlyWhm$whm)
{
}
publicfunctionopen(string$username, string$domain): void
{
$this->whm->accounts()->create($username, $domain, [
'plan' => 'starter',
'contactemail' => 'billing@example.com',
]);
}
publicfunctionsuspendForNonPayment(string$username): void
{
$this->whm->accounts()->suspend($username, 'unpaid invoice');
}
}

Generic calls (escape hatch)

Any endpoint not covered by a module remains reachable:

$cpanel->client()->uapi('Batch', 'strict', ['command' => $commands], 'POST');
$cpanel->client()->api2('Cron', 'listcron');
$whm->client()->call('sethostname', ['hostname' => 'srv2.example.com'], 'POST');
$whm->client()->cpanelUapi('customer1', 'Email', 'list_pops');

cpanelUapi() runs a UAPI function as any cPanel account through the WHM token — the standard pattern for hosting control panels where only the root/reseller token is stored.

API Reference

Every method returns an ApiResponse and throws on failure (see Error Handling). Named arguments are shown where they improve readability.

Email

$cpanel->email() — UAPI Email module.

MethodUnderlying functionNotes
accounts(?string $domain = null)list_pops_with_diskIncludes disk usage per mailbox
create(string $localPart, string $domain, string $password, int $quotaMb = 0)add_pop0 = unlimited quota
delete(string $localPart, string $domain)delete_pop
changePassword(string $localPart, string $domain, string $password)passwd_pop
setQuota(string $localPart, string $domain, int $quotaMb)edit_pop_quota
forwarders(?string $domain = null)list_forwarders
addForwarder(string $domain, string $localPart, string $destination)add_forwarder
deleteForwarder(string $address, string $forwarder)delete_forwarder
mailDirUsage(string $localPart, string $domain)get_pop_quota
$cpanel->email()->accounts('example.com');
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->email()->addForwarder('example.com', 'contact', 'inbox@elsewhere.com');

Domains

$cpanel->domains() — UAPI DomainInfo + API2 SubDomain / AddonDomain / Park.

MethodUnderlying function
list()UAPI DomainInfo::list_domains
data(?string $domain = null)UAPI domains_data / single_domain_data
addSubdomain(string $subdomain, string $rootDomain, ?string $documentRoot = null)API2 SubDomain::addsubdomain
deleteSubdomain(string $subdomain, string $rootDomain)API2 SubDomain::delsubdomain
addAddonDomain(string $newDomain, string $subdomain, string $documentRoot)API2 AddonDomain::addaddondomain
deleteAddonDomain(string $domain, string $subdomain)API2 AddonDomain::deladdondomain
park(string $domain) / unpark(string $domain)API2 Park
$cpanel->domains()->addSubdomain('api', 'example.com', 'public_html/api');

MySQL

$cpanel->mysql() — UAPI Mysql module.

MethodUnderlying function
databases() / users()list_databases / list_users
createDatabase(string $name) / deleteDatabase(string $name)create_database / delete_database
renameDatabase(string $oldName, string $newName)rename_database
createUser(string $name, string $password) / deleteUser(string $name)create_user / delete_user
setPassword(string $user, string $password)set_password
grant(string $user, string $database, string $privileges = 'ALL PRIVILEGES')set_privileges_on_database
revoke(string $user, string $database)revoke_access_to_database
addHost(string $host)add_host

Remember that cPanel prefixes database and user names with the account name (myaccount_app).

$cpanel->mysql()->createDatabase('myaccount_app');
$cpanel->mysql()->createUser('myaccount_app', 'S3cret!');
$cpanel->mysql()->grant('myaccount_app', 'myaccount_app');

FTP

$cpanel->ftp() — UAPI Ftp module: accounts(), create(), delete() (with optional home-dir destruction), changePassword(), setQuota(), setHomeDir().

$cpanel->ftp()->create('deploy', 'S3cret!', homeDir: 'public_html', quotaMb: 0);
$cpanel->ftp()->delete('deploy', destroyHomeDir: false);

SSL

$cpanel->ssl() — UAPI SSL module: certificates(), installedHosts(), install(), delete(), generateKey(), generateCsr().

$cpanel->ssl()->install('example.com', $certificatePem, $keyPem, $caBundlePem);

Files

$cpanel->files() — UAPI Fileman for content, API2 Fileman::fileop for filesystem operations: list(), read(), write(), info(), mkdir(), delete(), copy(), move(), chmod(), extract(), emptyTrash().

$cpanel->files()->write('public_html', '.htaccess', $rules);
$cpanel->files()->extract('backup.tar.gz', 'public_html');
$cpanel->files()->chmod('public_html/config.php', '0600');

DNS (cPanel zone editor)

$cpanel->dns() — API2 ZoneEdit module. addRecord() automatically maps the value to the right parameter name for the record type (address for A/AAAA, cname for CNAME, txtdata for TXT, exchange for MX…).

$cpanel->dns()->records('example.com', ['type' => 'A']);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10', ttl: 3600);
$cpanel->dns()->editRecord('example.com', line: 22, params: ['address' => '203.0.113.11']);
$cpanel->dns()->removeRecord('example.com', line: 22);

API2 zone records are addressed by line number in the zone file; always re-fetch records after a mutation before addressing another line.

Stats & quotas

$cpanel->stats()quota() (UAPI Quota), bars() (UAPI StatsBar, configurable display list), bandwidth() (API2 Stats::getmonthlybandwidth).

WHM — Accounts

$whm->accounts() — the account lifecycle, WHM API 1.

MethodUnderlying functionNotes
list(?string $search = null, string $searchType = 'user')listacctssearchType: user, domain, owner, ip, package
summary(string $user)accountsummary
create(string $username, string $domain, array $options = [])createacctoptions: plan, password, contactemail, quota, …
remove(string $user, bool $keepDns = false)removeacctDestructive
suspend(string $user, string $reason = '') / unsuspend(string $user)suspendacct / unsuspendacct
changePassword(string $user, string $password)passwd
modify(string $user, array $options)modifyacct
changePlan(string $user, string $plan)changepackage
domainOwner(string $domain)domainuserdata
createUserSession(string $user, string $service = 'cpaneld')create_user_sessionOne-click SSO URL into the user's cPanel
bandwidth(?string $user = null, ?string $month = null, ?string $year = null)showbwBandwidth usage, optionally filtered
limitBandwidth(string $user, int $limitMb)limitbw
$session = $whm->accounts()->createUserSession('customer1');
$redirectUrl = $session->data('url');

WHM — Resellers

$whm->resellers() — the full reseller lifecycle for multi-tier hosting.

MethodUnderlying functionNotes
list()listresellers
stats(string $reseller)resellerstatsDisk/bandwidth totals across owned accounts
accounts(string $reseller)acctcountsUsed/limit account counts
promote(string $user, bool $ownsSelf = false)setupresellerTurns an existing account into a reseller
demote(string $user)unsetupreseller
setLimits(string $user, array $limits)setresellerlimitse.g. enable_account_limit, account_limit, diskspace_limit
setPackageLimit(string $user, string $package, bool $allowed, ?int $number = null)setresellerpackagelimitRestrict which plans a reseller may sell
setAcls(string $reseller, array $acls)setaclsFine-grained privilege grants
setMainIp(string $user, string $ip)setresellermainip
setNameservers(string $user, array $nameservers)setresellernameservers
suspendAccounts(string $reseller) / unsuspendAccounts(string $reseller)suspendreseller / unsuspendresellerSuspends the reseller and all owned accounts
$whm->resellers()->promote('reseller1');
$whm->resellers()->setLimits('reseller1', ['enable_account_limit' => 1, 'account_limit' => 30]);
$whm->resellers()->setPackageLimit('reseller1', 'starter', allowed: true, number: 20);

WHM — Packages

$whm->packages()list(), create(), update(), delete() around listpkgs / addpkg / editpkg / killpkg.

$whm->packages()->create('starter', ['quota' => 5120, 'bwlimit' => 51200, 'maxaddons' => 1]);

WHM — DNS zones

$whm->dnsZones() — full zone lifecycle: list(), dump(), create(), delete(), addRecord(), editRecord(), removeRecord(), reset().

$whm->dnsZones()->create('customer1.com', '203.0.113.10');
$whm->dnsZones()->addRecord('customer1.com', [
'name' => 'mail',
'type' => 'A',
'address' => '203.0.113.10',
'ttl' => 3600,
]);

WHM — IP addresses

$whm->ips() — IP pool management for dedicated-IP offers.

MethodUnderlying function
list()listips
add(string $ip, string $netmask)addips
delete(string $ip)delip
assignToSite(string $domain, string $ip) / assignToUser(string $user, string $ip)setsiteip
usage()get_shared_ip
$whm->ips()->add('203.0.113.25', '255.255.255.0');
$whm->ips()->assignToSite('customer1.com', '203.0.113.25');

WHM — Security (cPHulk)

$whm->security() — brute-force protection management, the bread and butter of hosting support.

MethodUnderlying function
enableCphulk() / disableCphulk()enable_cphulk / disable_cphulk
whitelist(string $ip, string $comment = '') / blacklist(...)create_cphulk_record
listWhitelist() / listBlacklist()read_cphulk_records
removeFromWhitelist(string $ip) / removeFromBlacklist(string $ip)delete_cphulk_record
unblockBrute(string $ip)flush_cphulk_login_history_for_ips
flushLoginHistory()flush_cphulk_login_history
$whm->security()->unblockBrute('198.51.100.7');
$whm->security()->whitelist('203.0.113.50', 'office VPN');

WHM — Backups & restores

$whm->backups() — backup configuration and the account restore queue.

MethodUnderlying functionNotes
config() / setConfig(array $settings)backup_config_get / backup_config_set
users()backup_user_listUsers with backup metadata
dates()backup_date_listAvailable restore points
userBackups(string $user)backup_set_list
queueRestore(string $user, string $restorePoint, array $options = [])restore_queue_add_taskDefaults: keep IP, restore MySQL/subdomains/mail config
activateRestoreQueue()restore_queue_activateStarts processing queued restores
restoreQueueState()restore_queue_statePoll for progress
clearCompletedRestores()restore_queue_clear_completed_tasks
$whm->backups()->queueRestore('customer1', '2026-07-20');
$whm->backups()->activateRestoreQueue();

WHM — PHP versions

$whm->php() — MultiPHP management per virtual host.

MethodUnderlying function
installedVersions()php_get_installed_versions
systemDefault() / setSystemDefault(string $version)php_get_system_default_version / php_set_system_default_version
vhostVersions(string ...$vhosts)php_get_vhost_versions
setVhostVersion(string $version, string ...$vhosts)php_set_vhost_versions
handlers(string $version) / setHandler(string $version, string $handler)php_get_handlers / php_set_handler

Versions use EasyApache identifiers (ea-php83), not bare numbers.

$whm->php()->setVhostVersion('ea-php83', 'example.com', 'shop.example.com');

WHM — SSL & AutoSSL

$whm->autoSsl() — server-wide certificate automation plus manual installs with root privileges.

MethodUnderlying function
providers() / setProvider(string $provider)get_autossl_providers / set_autossl_provider
checkAllUsers()start_autossl_check_for_all_users
checkUser(string $user)start_autossl_check_for_one_user
enableForUser(string $user) / disableForUser(string $user)set_autossl_feature_for_users
installCertificate(string $domain, string $cert, string $key, ?string $caBundle = null)installssl
certificateInfo(string $domain)fetch_ssl_vhosts
$whm->autoSsl()->setProvider('LetsEncrypt');
$whm->autoSsl()->checkUser('customer1');

WHM — Server configuration

$whm->config() — Tweak Settings and global server preferences.

MethodUnderlying function
tweakSetting(string $key, string $module = 'Main')get_tweaksetting
setTweakSetting(string $key, string|int $value, string $module = 'Main')set_tweaksetting
updatePreferences() / setUpdatePreferences(array $settings)get_update_config / update_updateconf
hostname() / setHostname(string $hostname)gethostname / sethostname
nameserverConfig()nameserverconfig
$whm->config()->setTweakSetting('maxemailsperhour', 200);

WHM — Server

$whm->server()version(), hostname(), loadAverage(), serviceStatus(), restartService().

$whm->server()->serviceStatus('httpd');
$whm->server()->restartService('exim');

Responses

All calls return an immutable ApiResponse that normalizes the three wire formats (UAPI envelope, API2 cpanelresult, WHM metadata):

$response = $cpanel->mysql()->databases();
$response->success; // bool$response->data; // mixed — the payload's data section$response->data('acct'); // keyed access with optional default$response->errors; // list<string>$response->messages; // list<string>$response->warnings; // list<string>$response->raw; // the complete decoded JSON payload

data() is null-safe: it returns the default when the payload has no such key or when data is not an array.

Error Handling

All SDK exceptions implement CpanelSdkExceptionInterface, so a single catch covers everything:

ExceptionThrown whenExtras
ApiExceptionThe API answered but reported a failure (module methods validate automatically)getErrors(): array, getRaw(): array
AuthenticationExceptionCredentials are missing, or the server answered HTTP 401/403thrown before any request when credentials are empty
TransportExceptionNetwork error, TLS failure, timeout, or a non-JSON response bodywraps the underlying symfony/http-client exception
useChuckBartowski\CpanelSdk\Exception\ApiException;
useChuckBartowski\CpanelSdk\Exception\CpanelSdkExceptionInterface;
try {
$cpanel->email()->create('support', 'example.com', $password);
} catch (ApiException$e) {
$this->logger->warning('cPanel rejected the mailbox', ['errors' => $e->getErrors()]);
} catch (CpanelSdkExceptionInterface$e) {
thrownewProvisioningUnavailableException(previous: $e);
}

To inspect a failed response without exceptions, use the client directly — client-level methods return the response as-is:

$response = $cpanel->client()->uapi('Email', 'add_pop', $params, 'POST');
if (!$response->success) {
// $response->errors, $response->raw
}

Testing

The suite runs entirely offline against MockHttpClient:

composer install
vendor/bin/phpunit

To test your own services, inject a CpanelClient/WhmClient built with a mock:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Cpanel;
useSymfony\Component\HttpClient\MockHttpClient;
useSymfony\Component\HttpClient\Response\JsonMockResponse;
$http = newMockHttpClient(newJsonMockResponse(['status' => 1, 'data' => []]));
$cpanel = newCpanel(newCpanelClient('host', 'user', 'token', 2083, true, 30.0, $http));

Security Notes

  • API tokens are passed with #[\SensitiveParameter], so they never appear in stack traces.
  • Keep tokens in .env.local or your secret vault — never commit them.
  • Scope WHM tokens to the minimal privilege set in WHM » Manage API Tokens (e.g. deny Everything, allow only account functions).
  • Leave verify_ssl: true in production; the option exists solely for self-signed development servers.
  • removeacct and delete_ftp destroy=1 are irreversible — gate them behind confirmation flows in your application.

WHMCS module

A ready-to-use WHMCS provisioning module ships in whmcs/modules/servers/cpanelsdk/. It automates cPanel account provisioning through WHM using this SDK — create, suspend, unsuspend, terminate, change password, change package, and one-click SSO into cPanel.

Install

  1. composer require chuckbartowski/cpanel-sdk in your WHMCS root (so the SDK is autoloaded).
  2. Copy the cpanelsdk folder into <whmcs>/modules/servers/.
  3. In WHMCS, add a server (System Settings » Servers) with Type: cPanel (SDK), the WHM hostname, username root, and your WHM API token in the Access Hash field.
  4. Point a product at the server and set the Package config option to the WHM plan name.
OperationWHM function used
Create / Suspend / Unsuspend / Terminatecreateacct / suspendacct / unsuspendacct / removeacct
Change password / packagepasswd / changepackage
One-click logincreate_user_session

License

MIT

About

PHP SDK for the cPanel & WHM APIs (UAPI, API2, WHM API 1).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

cPanel & WHM SDK for PHP

🖥 cPanel & WHM SDK for PHP

A modern, fully typed PHP SDK for driving cPanel (UAPI + API2) and WHM (API 1).

PHP VersionSymfonyTestsPackagistLicense

Email · DNS · MySQL · SSL · FTP · Files · Accounts · Resellers · Packages · AutoSSL · Backups

Installation · Quick Start · API Reference · Error Handling


$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);
$whm->accounts()->createUserSession('customer1');

Framework-agnostic core — usable from any PHP project, script, or worker — with an optional bundle for first-class Symfony integration. Authenticated with API tokens, typed exceptions, and a comment-free, strictly typed codebase (PHP 8.2+, declare(strict_types=1) everywhere).


Table of Contents


Features

  • Full coverage of the three cPanel API surfaces: UAPI (the modern cPanel API), API2 (legacy but still required for zone editing, subdomains, addon domains, file operations), and WHM API 1 (server administration).
  • Framework-agnostic: two plain facades (Cpanel, Whm) you can instantiate anywhere; only hard dependency is symfony/http-client, a standalone component that works in any PHP project.
  • Token authentication only — no passwords, no sessions, no cookies. Uses the official Authorization: cpanel user:token / Authorization: whm user:token schemes.
  • High-level, discoverable modules grouped by domain: email, MySQL, DNS, SSL, files, accounts, resellers, backups, PHP versions, security…
  • A single normalized response object (ApiResponse) regardless of which underlying API answered — you never parse cpanelresult or metadata envelopes yourself.
  • Typed exception hierarchy under one marker interface, so you can catch narrowly or broadly.
  • Escape hatches everywhere: any endpoint not wrapped by a module remains one method call away.
  • Optional Symfony bundle with semantic configuration and autowirable services.
  • Fully unit-tested against MockHttpClient (no network required).

Requirements

DependencyVersion
PHP>= 8.2
cPanel/WHMany version supporting API tokens (v64+)
Symfony6.4 LTS or 7.x — optional, only for the bundle integration

You will need at least one of:

  • a cPanel API token — created in cPanel » Security » Manage API Tokens
  • a WHM API token — created in WHM » Development » Manage API Tokens

Installation

The package is published on Packagist:

composer require chuckbartowski/cpanel-sdk

Quick Start (plain PHP)

No framework required — build the clients and go:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Client\WhmClient;
useChuckBartowski\CpanelSdk\Cpanel;
useChuckBartowski\CpanelSdk\Whm;
$cpanel = newCpanel(newCpanelClient(
host: 'server.example.com',
username: 'myaccount',
token: getenv('CPANEL_API_TOKEN'),
port: 2083,
));
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10');
$whm = newWhm(newWhmClient(
host: 'server.example.com',
username: 'root',
token: getenv('WHM_API_TOKEN'),
port: 2087,
));
$whm->accounts()->create('customer1', 'customer1.com', ['plan' => 'starter']);

Client constructor signature (identical for both clients):

newCpanelClient(
string $host,
string $username,
string $token,
int $port, // 2083 for cPanel, 2087 for WHM
bool $verifySsl = true,
float $timeout = 30.0,
?HttpClientInterface $httpClient = null, // inject your own (retries, proxy, mock…)
);

Symfony Integration (optional)

A ready-made bundle wires everything into the container. Register it:

// config/bundles.phpreturn [
ChuckBartowski\CpanelSdk\CpanelSdkBundle::class => ['all' => true],
];

Then create config/packages/cpanel_sdk.yaml:

cpanel_sdk:
host: '%env(CPANEL_HOST)%'verify_ssl: truetimeout: 30cpanel:
username: '%env(CPANEL_USERNAME)%'token: '%env(CPANEL_API_TOKEN)%'port: 2083whm:
username: '%env(WHM_USERNAME)%'token: '%env(WHM_API_TOKEN)%'port: 2087

And the matching environment variables:

# .env.localCPANEL_HOST=server.example.comCPANEL_USERNAME=myaccountCPANEL_API_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWHM_USERNAME=rootWHM_API_TOKEN=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

Configuration reference

KeyTypeDefaultDescription
hoststringrequiredHostname of the cPanel/WHM server (no scheme, no port)
verify_sslbooltrueTLS peer/host verification; disable only for self-signed dev servers
timeoutfloat30.0Per-request timeout in seconds
cpanel.usernamestring''cPanel account name
cpanel.tokenstring''cPanel API token
cpanel.portint2083cPanel TLS port
whm.usernamestring''WHM user (usually root or a reseller)
whm.tokenstring''WHM API token
whm.portint2087WHM TLS port

The cpanel and whm sections are independent — configure only the side you need. Calling a client with missing credentials throws an AuthenticationException immediately, before any network request is made.

The bundle reuses your application's http_client service when available (so scoped clients, retry strategies, and profiler integration all apply), and falls back to a native client otherwise.

Architecture

src/
├── CpanelSdkBundle.php Symfony bundle: config tree + service wiring
├── Cpanel.php Facade: entry point for cPanel-level modules
├── Whm.php Facade: entry point for WHM-level modules
├── Client/
│ ├── AbstractClient.php Shared HTTP transport, auth header, error mapping
│ ├── CpanelClient.php uapi() and api2() generic executors
│ └── WhmClient.php call() (WHM API 1) and cpanelUapi() (root proxy)
├── Response/
│ └── ApiResponse.php Immutable, normalized response for all 3 API formats
├── Exception/
│ ├── CpanelSdkExceptionInterface.php
│ ├── ApiException.php API answered but reported a failure
│ ├── AuthenticationException.php
│ └── TransportException.php Network / TLS / timeout / invalid JSON
└── Api/
├── Cpanel/ EmailApi, DomainApi, MysqlApi, FtpApi,
│ SslApi, FileApi, DnsApi, StatsApi
└── Whm/ AccountApi, ResellerApi, PackageApi, DnsZoneApi,
IpApi, SecurityApi, BackupApi, PhpApi,
AutoSslApi, ConfigApi, ServerApi

Design decisions:

  • Facade + lazy modules: Cpanel/Whm instantiate each module on first use and cache it, so the DI container only carries four services.
  • Modules always validate: every module method calls ensureSuccess() internally and throws ApiException on failure. If you need to inspect a failed response without an exception, drop down to the client level.
  • Nothing is sealed off: the clients' generic methods accept any module/function/parameter combination, so a cPanel endpoint added tomorrow is usable today.

Usage

Standalone, instantiate the facades as shown in the Quick Start. In Symfony, both facades are autowirable in controllers, services, commands, and message handlers.

The Cpanel facade

useChuckBartowski\CpanelSdk\Cpanel;
finalclass MailboxProvisioner
{
publicfunction__construct(privatereadonlyCpanel$cpanel)
{
}
publicfunctionprovision(string$localPart, string$domain, string$password): void
{
$this->cpanel->email()->create($localPart, $domain, $password, quotaMb: 512);
}
}

The Whm facade

useChuckBartowski\CpanelSdk\Whm;
finalclass HostingAccountManager
{
publicfunction__construct(privatereadonlyWhm$whm)
{
}
publicfunctionopen(string$username, string$domain): void
{
$this->whm->accounts()->create($username, $domain, [
'plan' => 'starter',
'contactemail' => 'billing@example.com',
]);
}
publicfunctionsuspendForNonPayment(string$username): void
{
$this->whm->accounts()->suspend($username, 'unpaid invoice');
}
}

Generic calls (escape hatch)

Any endpoint not covered by a module remains reachable:

$cpanel->client()->uapi('Batch', 'strict', ['command' => $commands], 'POST');
$cpanel->client()->api2('Cron', 'listcron');
$whm->client()->call('sethostname', ['hostname' => 'srv2.example.com'], 'POST');
$whm->client()->cpanelUapi('customer1', 'Email', 'list_pops');

cpanelUapi() runs a UAPI function as any cPanel account through the WHM token — the standard pattern for hosting control panels where only the root/reseller token is stored.

API Reference

Every method returns an ApiResponse and throws on failure (see Error Handling). Named arguments are shown where they improve readability.

Email

$cpanel->email() — UAPI Email module.

MethodUnderlying functionNotes
accounts(?string $domain = null)list_pops_with_diskIncludes disk usage per mailbox
create(string $localPart, string $domain, string $password, int $quotaMb = 0)add_pop0 = unlimited quota
delete(string $localPart, string $domain)delete_pop
changePassword(string $localPart, string $domain, string $password)passwd_pop
setQuota(string $localPart, string $domain, int $quotaMb)edit_pop_quota
forwarders(?string $domain = null)list_forwarders
addForwarder(string $domain, string $localPart, string $destination)add_forwarder
deleteForwarder(string $address, string $forwarder)delete_forwarder
mailDirUsage(string $localPart, string $domain)get_pop_quota
$cpanel->email()->accounts('example.com');
$cpanel->email()->create('support', 'example.com', 'S3cure!Pass', quotaMb: 250);
$cpanel->email()->addForwarder('example.com', 'contact', 'inbox@elsewhere.com');

Domains

$cpanel->domains() — UAPI DomainInfo + API2 SubDomain / AddonDomain / Park.

MethodUnderlying function
list()UAPI DomainInfo::list_domains
data(?string $domain = null)UAPI domains_data / single_domain_data
addSubdomain(string $subdomain, string $rootDomain, ?string $documentRoot = null)API2 SubDomain::addsubdomain
deleteSubdomain(string $subdomain, string $rootDomain)API2 SubDomain::delsubdomain
addAddonDomain(string $newDomain, string $subdomain, string $documentRoot)API2 AddonDomain::addaddondomain
deleteAddonDomain(string $domain, string $subdomain)API2 AddonDomain::deladdondomain
park(string $domain) / unpark(string $domain)API2 Park
$cpanel->domains()->addSubdomain('api', 'example.com', 'public_html/api');

MySQL

$cpanel->mysql() — UAPI Mysql module.

MethodUnderlying function
databases() / users()list_databases / list_users
createDatabase(string $name) / deleteDatabase(string $name)create_database / delete_database
renameDatabase(string $oldName, string $newName)rename_database
createUser(string $name, string $password) / deleteUser(string $name)create_user / delete_user
setPassword(string $user, string $password)set_password
grant(string $user, string $database, string $privileges = 'ALL PRIVILEGES')set_privileges_on_database
revoke(string $user, string $database)revoke_access_to_database
addHost(string $host)add_host

Remember that cPanel prefixes database and user names with the account name (myaccount_app).

$cpanel->mysql()->createDatabase('myaccount_app');
$cpanel->mysql()->createUser('myaccount_app', 'S3cret!');
$cpanel->mysql()->grant('myaccount_app', 'myaccount_app');

FTP

$cpanel->ftp() — UAPI Ftp module: accounts(), create(), delete() (with optional home-dir destruction), changePassword(), setQuota(), setHomeDir().

$cpanel->ftp()->create('deploy', 'S3cret!', homeDir: 'public_html', quotaMb: 0);
$cpanel->ftp()->delete('deploy', destroyHomeDir: false);

SSL

$cpanel->ssl() — UAPI SSL module: certificates(), installedHosts(), install(), delete(), generateKey(), generateCsr().

$cpanel->ssl()->install('example.com', $certificatePem, $keyPem, $caBundlePem);

Files

$cpanel->files() — UAPI Fileman for content, API2 Fileman::fileop for filesystem operations: list(), read(), write(), info(), mkdir(), delete(), copy(), move(), chmod(), extract(), emptyTrash().

$cpanel->files()->write('public_html', '.htaccess', $rules);
$cpanel->files()->extract('backup.tar.gz', 'public_html');
$cpanel->files()->chmod('public_html/config.php', '0600');

DNS (cPanel zone editor)

$cpanel->dns() — API2 ZoneEdit module. addRecord() automatically maps the value to the right parameter name for the record type (address for A/AAAA, cname for CNAME, txtdata for TXT, exchange for MX…).

$cpanel->dns()->records('example.com', ['type' => 'A']);
$cpanel->dns()->addRecord('example.com', 'www', 'A', '203.0.113.10', ttl: 3600);
$cpanel->dns()->editRecord('example.com', line: 22, params: ['address' => '203.0.113.11']);
$cpanel->dns()->removeRecord('example.com', line: 22);

API2 zone records are addressed by line number in the zone file; always re-fetch records after a mutation before addressing another line.

Stats & quotas

$cpanel->stats()quota() (UAPI Quota), bars() (UAPI StatsBar, configurable display list), bandwidth() (API2 Stats::getmonthlybandwidth).

WHM — Accounts

$whm->accounts() — the account lifecycle, WHM API 1.

MethodUnderlying functionNotes
list(?string $search = null, string $searchType = 'user')listacctssearchType: user, domain, owner, ip, package
summary(string $user)accountsummary
create(string $username, string $domain, array $options = [])createacctoptions: plan, password, contactemail, quota, …
remove(string $user, bool $keepDns = false)removeacctDestructive
suspend(string $user, string $reason = '') / unsuspend(string $user)suspendacct / unsuspendacct
changePassword(string $user, string $password)passwd
modify(string $user, array $options)modifyacct
changePlan(string $user, string $plan)changepackage
domainOwner(string $domain)domainuserdata
createUserSession(string $user, string $service = 'cpaneld')create_user_sessionOne-click SSO URL into the user's cPanel
bandwidth(?string $user = null, ?string $month = null, ?string $year = null)showbwBandwidth usage, optionally filtered
limitBandwidth(string $user, int $limitMb)limitbw
$session = $whm->accounts()->createUserSession('customer1');
$redirectUrl = $session->data('url');

WHM — Resellers

$whm->resellers() — the full reseller lifecycle for multi-tier hosting.

MethodUnderlying functionNotes
list()listresellers
stats(string $reseller)resellerstatsDisk/bandwidth totals across owned accounts
accounts(string $reseller)acctcountsUsed/limit account counts
promote(string $user, bool $ownsSelf = false)setupresellerTurns an existing account into a reseller
demote(string $user)unsetupreseller
setLimits(string $user, array $limits)setresellerlimitse.g. enable_account_limit, account_limit, diskspace_limit
setPackageLimit(string $user, string $package, bool $allowed, ?int $number = null)setresellerpackagelimitRestrict which plans a reseller may sell
setAcls(string $reseller, array $acls)setaclsFine-grained privilege grants
setMainIp(string $user, string $ip)setresellermainip
setNameservers(string $user, array $nameservers)setresellernameservers
suspendAccounts(string $reseller) / unsuspendAccounts(string $reseller)suspendreseller / unsuspendresellerSuspends the reseller and all owned accounts
$whm->resellers()->promote('reseller1');
$whm->resellers()->setLimits('reseller1', ['enable_account_limit' => 1, 'account_limit' => 30]);
$whm->resellers()->setPackageLimit('reseller1', 'starter', allowed: true, number: 20);

WHM — Packages

$whm->packages()list(), create(), update(), delete() around listpkgs / addpkg / editpkg / killpkg.

$whm->packages()->create('starter', ['quota' => 5120, 'bwlimit' => 51200, 'maxaddons' => 1]);

WHM — DNS zones

$whm->dnsZones() — full zone lifecycle: list(), dump(), create(), delete(), addRecord(), editRecord(), removeRecord(), reset().

$whm->dnsZones()->create('customer1.com', '203.0.113.10');
$whm->dnsZones()->addRecord('customer1.com', [
'name' => 'mail',
'type' => 'A',
'address' => '203.0.113.10',
'ttl' => 3600,
]);

WHM — IP addresses

$whm->ips() — IP pool management for dedicated-IP offers.

MethodUnderlying function
list()listips
add(string $ip, string $netmask)addips
delete(string $ip)delip
assignToSite(string $domain, string $ip) / assignToUser(string $user, string $ip)setsiteip
usage()get_shared_ip
$whm->ips()->add('203.0.113.25', '255.255.255.0');
$whm->ips()->assignToSite('customer1.com', '203.0.113.25');

WHM — Security (cPHulk)

$whm->security() — brute-force protection management, the bread and butter of hosting support.

MethodUnderlying function
enableCphulk() / disableCphulk()enable_cphulk / disable_cphulk
whitelist(string $ip, string $comment = '') / blacklist(...)create_cphulk_record
listWhitelist() / listBlacklist()read_cphulk_records
removeFromWhitelist(string $ip) / removeFromBlacklist(string $ip)delete_cphulk_record
unblockBrute(string $ip)flush_cphulk_login_history_for_ips
flushLoginHistory()flush_cphulk_login_history
$whm->security()->unblockBrute('198.51.100.7');
$whm->security()->whitelist('203.0.113.50', 'office VPN');

WHM — Backups & restores

$whm->backups() — backup configuration and the account restore queue.

MethodUnderlying functionNotes
config() / setConfig(array $settings)backup_config_get / backup_config_set
users()backup_user_listUsers with backup metadata
dates()backup_date_listAvailable restore points
userBackups(string $user)backup_set_list
queueRestore(string $user, string $restorePoint, array $options = [])restore_queue_add_taskDefaults: keep IP, restore MySQL/subdomains/mail config
activateRestoreQueue()restore_queue_activateStarts processing queued restores
restoreQueueState()restore_queue_statePoll for progress
clearCompletedRestores()restore_queue_clear_completed_tasks
$whm->backups()->queueRestore('customer1', '2026-07-20');
$whm->backups()->activateRestoreQueue();

WHM — PHP versions

$whm->php() — MultiPHP management per virtual host.

MethodUnderlying function
installedVersions()php_get_installed_versions
systemDefault() / setSystemDefault(string $version)php_get_system_default_version / php_set_system_default_version
vhostVersions(string ...$vhosts)php_get_vhost_versions
setVhostVersion(string $version, string ...$vhosts)php_set_vhost_versions
handlers(string $version) / setHandler(string $version, string $handler)php_get_handlers / php_set_handler

Versions use EasyApache identifiers (ea-php83), not bare numbers.

$whm->php()->setVhostVersion('ea-php83', 'example.com', 'shop.example.com');

WHM — SSL & AutoSSL

$whm->autoSsl() — server-wide certificate automation plus manual installs with root privileges.

MethodUnderlying function
providers() / setProvider(string $provider)get_autossl_providers / set_autossl_provider
checkAllUsers()start_autossl_check_for_all_users
checkUser(string $user)start_autossl_check_for_one_user
enableForUser(string $user) / disableForUser(string $user)set_autossl_feature_for_users
installCertificate(string $domain, string $cert, string $key, ?string $caBundle = null)installssl
certificateInfo(string $domain)fetch_ssl_vhosts
$whm->autoSsl()->setProvider('LetsEncrypt');
$whm->autoSsl()->checkUser('customer1');

WHM — Server configuration

$whm->config() — Tweak Settings and global server preferences.

MethodUnderlying function
tweakSetting(string $key, string $module = 'Main')get_tweaksetting
setTweakSetting(string $key, string|int $value, string $module = 'Main')set_tweaksetting
updatePreferences() / setUpdatePreferences(array $settings)get_update_config / update_updateconf
hostname() / setHostname(string $hostname)gethostname / sethostname
nameserverConfig()nameserverconfig
$whm->config()->setTweakSetting('maxemailsperhour', 200);

WHM — Server

$whm->server()version(), hostname(), loadAverage(), serviceStatus(), restartService().

$whm->server()->serviceStatus('httpd');
$whm->server()->restartService('exim');

Responses

All calls return an immutable ApiResponse that normalizes the three wire formats (UAPI envelope, API2 cpanelresult, WHM metadata):

$response = $cpanel->mysql()->databases();
$response->success; // bool$response->data; // mixed — the payload's data section$response->data('acct'); // keyed access with optional default$response->errors; // list<string>$response->messages; // list<string>$response->warnings; // list<string>$response->raw; // the complete decoded JSON payload

data() is null-safe: it returns the default when the payload has no such key or when data is not an array.

Error Handling

All SDK exceptions implement CpanelSdkExceptionInterface, so a single catch covers everything:

ExceptionThrown whenExtras
ApiExceptionThe API answered but reported a failure (module methods validate automatically)getErrors(): array, getRaw(): array
AuthenticationExceptionCredentials are missing, or the server answered HTTP 401/403thrown before any request when credentials are empty
TransportExceptionNetwork error, TLS failure, timeout, or a non-JSON response bodywraps the underlying symfony/http-client exception
useChuckBartowski\CpanelSdk\Exception\ApiException;
useChuckBartowski\CpanelSdk\Exception\CpanelSdkExceptionInterface;
try {
$cpanel->email()->create('support', 'example.com', $password);
} catch (ApiException$e) {
$this->logger->warning('cPanel rejected the mailbox', ['errors' => $e->getErrors()]);
} catch (CpanelSdkExceptionInterface$e) {
thrownewProvisioningUnavailableException(previous: $e);
}

To inspect a failed response without exceptions, use the client directly — client-level methods return the response as-is:

$response = $cpanel->client()->uapi('Email', 'add_pop', $params, 'POST');
if (!$response->success) {
// $response->errors, $response->raw
}

Testing

The suite runs entirely offline against MockHttpClient:

composer install
vendor/bin/phpunit

To test your own services, inject a CpanelClient/WhmClient built with a mock:

useChuckBartowski\CpanelSdk\Client\CpanelClient;
useChuckBartowski\CpanelSdk\Cpanel;
useSymfony\Component\HttpClient\MockHttpClient;
useSymfony\Component\HttpClient\Response\JsonMockResponse;
$http = newMockHttpClient(newJsonMockResponse(['status' => 1, 'data' => []]));
$cpanel = newCpanel(newCpanelClient('host', 'user', 'token', 2083, true, 30.0, $http));

Security Notes

  • API tokens are passed with #[\SensitiveParameter], so they never appear in stack traces.
  • Keep tokens in .env.local or your secret vault — never commit them.
  • Scope WHM tokens to the minimal privilege set in WHM » Manage API Tokens (e.g. deny Everything, allow only account functions).
  • Leave verify_ssl: true in production; the option exists solely for self-signed development servers.
  • removeacct and delete_ftp destroy=1 are irreversible — gate them behind confirmation flows in your application.

WHMCS module

A ready-to-use WHMCS provisioning module ships in whmcs/modules/servers/cpanelsdk/. It automates cPanel account provisioning through WHM using this SDK — create, suspend, unsuspend, terminate, change password, change package, and one-click SSO into cPanel.

Install

  1. composer require chuckbartowski/cpanel-sdk in your WHMCS root (so the SDK is autoloaded).
  2. Copy the cpanelsdk folder into <whmcs>/modules/servers/.
  3. In WHMCS, add a server (System Settings » Servers) with Type: cPanel (SDK), the WHM hostname, username root, and your WHM API token in the Access Hash field.
  4. Point a product at the server and set the Package config option to the WHM plan name.
OperationWHM function used
Create / Suspend / Unsuspend / Terminatecreateacct / suspendacct / unsuspendacct / removeacct
Change password / packagepasswd / changepackage
One-click logincreate_user_session

License

MIT

About

PHP SDK for the cPanel & WHM APIs (UAPI, API2, WHM API 1).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages