chuckbartowski/cpanel-sdk

PHP SDK for the cPanel and WHM APIs (UAPI, API2, WHM API 1) with typed clients and high-level modules

Maintainers

Package info

github.com/ChuckBartowski11/cPanel

pkg:composer/chuckbartowski/cpanel-sdk

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-07-21 15:24 UTC

This package is auto-updated.

Last update: 2026-07-22 11:40:26 UTC


README

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 Version Symfony Tests Packagist License

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

Dependency Version
PHP >= 8.2
cPanel/WHM any version supporting API tokens (v64+)
Symfony 6.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:

use ChuckBartowski\CpanelSdk\Client\CpanelClient;
use ChuckBartowski\CpanelSdk\Client\WhmClient;
use ChuckBartowski\CpanelSdk\Cpanel;
use ChuckBartowski\CpanelSdk\Whm;

$cpanel = new Cpanel(new CpanelClient(
    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 = new Whm(new WhmClient(
    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):

new CpanelClient(
    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.php
return [
    ChuckBartowski\CpanelSdk\CpanelSdkBundle::class => ['all' => true],
];

Then create config/packages/cpanel_sdk.yaml:

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

And the matching environment variables:

# .env.local
CPANEL_HOST=server.example.com
CPANEL_USERNAME=myaccount
CPANEL_API_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
WHM_USERNAME=root
WHM_API_TOKEN=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

Configuration reference

Key Type Default Description
host string required Hostname of the cPanel/WHM server (no scheme, no port)
verify_ssl bool true TLS peer/host verification; disable only for self-signed dev servers
timeout float 30.0 Per-request timeout in seconds
cpanel.username string '' cPanel account name
cpanel.token string '' cPanel API token
cpanel.port int 2083 cPanel TLS port
whm.username string '' WHM user (usually root or a reseller)
whm.token string '' WHM API token
whm.port int 2087 WHM 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

use ChuckBartowski\CpanelSdk\Cpanel;

final class MailboxProvisioner
{
    public function __construct(private readonly Cpanel $cpanel)
    {
    }

    public function provision(string $localPart, string $domain, string $password): void
    {
        $this->cpanel->email()->create($localPart, $domain, $password, quotaMb: 512);
    }
}

The Whm facade

use ChuckBartowski\CpanelSdk\Whm;

final class HostingAccountManager
{
    public function __construct(private readonly Whm $whm)
    {
    }

    public function open(string $username, string $domain): void
    {
        $this->whm->accounts()->create($username, $domain, [
            'plan' => 'starter',
            'contactemail' => 'billing@example.com',
        ]);
    }

    public function suspendForNonPayment(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.

Method Underlying function Notes
accounts(?string $domain = null) list_pops_with_disk Includes disk usage per mailbox
create(string $localPart, string $domain, string $password, int $quotaMb = 0) add_pop 0 = 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.

Method Underlying 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.

Method Underlying 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.

Method Underlying function Notes
list(?string $search = null, string $searchType = 'user') listaccts searchType: user, domain, owner, ip, package
summary(string $user) accountsummary
create(string $username, string $domain, array $options = []) createacct options: plan, password, contactemail, quota, …
remove(string $user, bool $keepDns = false) removeacct Destructive
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_session One-click SSO URL into the user's cPanel
bandwidth(?string $user = null, ?string $month = null, ?string $year = null) showbw Bandwidth 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.

Method Underlying function Notes
list() listresellers
stats(string $reseller) resellerstats Disk/bandwidth totals across owned accounts
accounts(string $reseller) acctcounts Used/limit account counts
promote(string $user, bool $ownsSelf = false) setupreseller Turns an existing account into a reseller
demote(string $user) unsetupreseller
setLimits(string $user, array $limits) setresellerlimits e.g. enable_account_limit, account_limit, diskspace_limit
setPackageLimit(string $user, string $package, bool $allowed, ?int $number = null) setresellerpackagelimit Restrict which plans a reseller may sell
setAcls(string $reseller, array $acls) setacls Fine-grained privilege grants
setMainIp(string $user, string $ip) setresellermainip
setNameservers(string $user, array $nameservers) setresellernameservers
suspendAccounts(string $reseller) / unsuspendAccounts(string $reseller) suspendreseller / unsuspendreseller Suspends 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.

Method Underlying 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.

Method Underlying 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.

Method Underlying function Notes
config() / setConfig(array $settings) backup_config_get / backup_config_set
users() backup_user_list Users with backup metadata
dates() backup_date_list Available restore points
userBackups(string $user) backup_set_list
queueRestore(string $user, string $restorePoint, array $options = []) restore_queue_add_task Defaults: keep IP, restore MySQL/subdomains/mail config
activateRestoreQueue() restore_queue_activate Starts processing queued restores
restoreQueueState() restore_queue_state Poll 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.

Method Underlying 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.

Method Underlying 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.

Method Underlying 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:

Exception Thrown when Extras
ApiException The API answered but reported a failure (module methods validate automatically) getErrors(): array, getRaw(): array
AuthenticationException Credentials are missing, or the server answered HTTP 401/403 thrown before any request when credentials are empty
TransportException Network error, TLS failure, timeout, or a non-JSON response body wraps the underlying symfony/http-client exception
use ChuckBartowski\CpanelSdk\Exception\ApiException;
use ChuckBartowski\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) {
    throw new ProvisioningUnavailableException(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:

use ChuckBartowski\CpanelSdk\Client\CpanelClient;
use ChuckBartowski\CpanelSdk\Cpanel;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\JsonMockResponse;

$http = new MockHttpClient(new JsonMockResponse(['status' => 1, 'data' => []]));
$cpanel = new Cpanel(new CpanelClient('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.
Operation WHM function used
Create / Suspend / Unsuspend / Terminate createacct / suspendacct / unsuspendacct / removeacct
Change password / package passwd / changepackage
One-click login create_user_session

License

MIT