Skip to content
Shahid Malla

How Do You Build a Custom WHMCS Provisioning Module?

A checked guide to building a WHMCS provisioning module: where the files go, the naming rule, eleven functions and when WHMCS calls them, the $params array, a minimal _CreateAccount, logging, testing and limits.

· Published · Updated · 8 min read
On this page (8 sections)

A custom WHMCS provisioning module is a PHP file at modules/servers/yourmodule/yourmodule.php whose functions WHMCS calls to create, suspend, unsuspend and terminate a service on another system. Each function is named yourmodule_FunctionName and receives a $params array. The core functions must return the word success or an error message. Only _ConfigOptions is required.

I have built 200+ custom WHMCS modules. I checked every function name, parameter and menu path below against the official WHMCS developer documentation and sample module on 10 October 2026.

What does a WHMCS provisioning module do?

A WHMCS provisioning module, also called a server or product module, carries out the lifecycle of a service on a remote system when billing events happen. The official introduction names the events: payment of a new order, an item becoming overdue, payment of an overdue invoice, and a cancellation request.

That is a different job from the two neighboring tools. A hook reacts to an event with a side effect, which I cover in my WHMCS hooks guide. The API lets other software call WHMCS, covered in my WHMCS API guide. A provisioning module is the code that talks to the remote system: a control panel, a cloud platform, a license server or your own application.

Where do the module files go, and how must they be named?

The files go in a folder of their own under /modules/servers/, and the main PHP file carries the same name as the folder. The getting started page of the documentation gives the naming rule: one word, only lowercase letters and numbers, starting with a letter, and unique. So acmepanel follows the rule, and acme_panel or AcmePanel does not.

The official sample module on GitHub recommends this layout:

modules/servers/acmepanel/
    acmepanel.php    the module functions
    hooks.php        optional hooks that ship with the module
    lib/             your own classes, such as an API client
    templates/       Smarty templates for client area output
    logo.png

Every function in acmepanel.php starts with the file name and an underscore, for example acmepanel_CreateAccount. If the remote system cannot do something, leave that function out, as the sample module advises.

Which functions can a WHMCS provisioning module contain?

The documentation lists seven core functions (Create, Suspend, Unsuspend, Terminate, Renew, ChangePassword and ChangePackage) plus setup and optional functions. This table has the eleven I use most, with the suffix that follows your module name.

FunctionWhen WHMCS calls itResult of a success
_MetaDataNot tied to a service. It describes the module.Returns an array with keys such as DisplayName, APIVersion (use 1.1) and RequiresServer.
_ConfigOptionsNot tied to a service. It defines the product's module settings.Returns up to 24 per-product fields: text, password, yes/no, dropdown, radio or textarea.
_CreateAccountWhen a new product provisions: at checkout or on payment, or when an admin runs it.Status becomes Active and the Product Welcome Email is sent.
_SuspendAccountBy the cron when a product becomes overdue, or by an admin.Status becomes Suspended.
_UnsuspendAccountOn payment of an overdue invoice for the product.Status becomes Active.
_TerminateAccountBy the cron for long-overdue products when termination is enabled in the automation settings, or by an admin.Status becomes Terminated.
_RenewEach time a renewal invoice for the product is paid.No status change is documented.
_ChangePasswordWhen a client or admin asks for a password change on an active product.The password is updated in the WHMCS database.
_ChangePackageWhen an upgrade or downgrade order is paid, for products and for configurable options.No status change is documented.
_TestConnectionWhen staff click Test Connection while adding or editing a server.Returns an array with success and error.
_ServiceSingleSignOnWhen a client or admin asks to log in to the service's control panel.Returns an array with success and redirectTo, or errorMsg.

The full list, including _ClientArea, _AdminCustomButtonArray and _UsageUpdate, is on the supported functions page.

What does WHMCS pass in the $params array?

WHMCS passes the same $params array to every module function except _ConfigOptions, and it describes the service, the product settings, the client and the server. The keys I use in almost every module are these.

  • The service: serviceid, userid, pid, domain, username and password.
  • The product's module settings: configoption1 to configoption24, numbered in the order you declare them in _ConfigOptions.
  • What the customer chose: customfields and configoptions, both keyed by name, for example $params['configoptions']['Disk Space'].
  • The client: clientsdetails, an array with the name, email, address and country.
  • The server: serverhostname, serverip, serverusername, serverpassword, serveraccesshash, serversecure and serverport.

Do not confuse configoptionN with configoptions. The first holds the settings staff entered on the product. The second holds the product's configurable options, which the customer picks when ordering. The module parameters page has every key.

Most remote systems return an ID of their own. Store it with service properties: $params['model']->serviceProperties->save(['Remote ID' => $id]) writes to a custom field with that name and creates the field if it is missing.

What does a minimal _CreateAccount function look like?

A minimal module needs the direct-access guard, _MetaData, _ConfigOptions and one core function that returns success or an error string. acmepanel_request() below stands for your own HTTP client, which throws an exception when the panel rejects the request.

<?php
if (!defined('WHMCS')) {
    die('This file cannot be accessed directly');
}

function acmepanel_MetaData()
{
    return ['DisplayName' => 'Acme Panel', 'APIVersion' => '1.1', 'RequiresServer' => true];
}

function acmepanel_ConfigOptions()
{
    return [
        'plan' => ['FriendlyName' => 'Plan', 'Type' => 'dropdown', 'Options' => 'starter,business'],
    ];
}

function acmepanel_CreateAccount(array $params)
{
    $request = [
        'username' => $params['username'],
        'password' => $params['password'],
        'email'    => $params['clientsdetails']['email'],
        'plan'     => $params['configoption1'],
    ];
    $hide = [$params['password'], $params['serverpassword']];

    try {
        $response = acmepanel_request($params, '/accounts', $request);
        logModuleCall('acmepanel', __FUNCTION__, $request, $response, null, $hide);
    } catch (Exception $e) {
        logModuleCall('acmepanel', __FUNCTION__, $request, $e->getMessage(), null, $hide);

        return $e->getMessage();
    }

    return 'success';
}

The return value is the contract. The documentation says the function must return the word success, and that anything else should be an error message a person can understand, because it is shown to staff. Errors from _ChangePassword are also shown to the client, so never return a raw API reply that contains credentials.

How do you log and debug module calls?

Call logModuleCall() around every request to the remote system, then read the Module Log at Configuration > System Logs. Its arguments are the module name, the action, the request, the response, the processed data and an array of strings to scrub from the entry, which is where passwords and tokens go.

Two things surprise people. The Module Log records nothing until you set Module Logging to On, and the logs documentation says the admin role needs the View Module Debug Log permission. And when an automated action fails, it appears at Utilities > Module Queue, where the documentation is blunt: WHMCS will not retry it automatically. Someone has to click Retry.

How do you test a provisioning module before customers use it?

Test it on a staging copy of WHMCS against a test account on the remote system, in this order.

  1. Add the server at Configuration > System Settings > Servers and click Test Connection, which runs your _TestConnection function.
  2. Create a product at Configuration > System Settings > Products/Services. On the Module Settings tab, choose your module under Module Name and select "Do not automatically setup this product".
  3. Order the product for a test client. Run the create, suspend, unsuspend and terminate module commands from the service in the client's profile, and compare each Module Log entry with the remote system.
  4. Run every command a second time. Staff will do that in real life, so a create on an existing account and a terminate on a missing one must not cause damage.
  5. Switch the product to automatic setup on first payment, pay a test invoice, and confirm that the welcome email arrives only after the account works.

When should you not build a custom provisioning module?

Do not build one when WHMCS already has what you need, or when there is nothing to provision.

  • A module already exists. WHMCS ships server modules for common control panels. Check those, and ask the vendor of the remote system, before paying for custom work.
  • Staff deliver the product by hand. The product documentation describes an Auto Release module that simulates activation and sends a welcome email.
  • You only need a side effect. Adding a DNS record or a chat message after provisioning is a hook on AfterModuleCreate, not a module.

A custom module also has limits you should know before you start. It can do only what the remote system's API allows. A failed automated action waits in the Module Queue until a person retries it. And a success from _CreateAccount marks the service Active and sends the welcome email, so a module that reports success too early tells customers about accounts that do not exist yet.

If you want one built, scope and limits are on my WHMCS custom module development page and my post on module cost explains what drives the hours, with ongoing updates covered by WHMCS support and maintenance; the scoping call of about 30 minutes is free and a written quote follows within one business day.

Last reviewed 10 October 2026. Disclosure: I am a freelance WHMCS developer and I sell custom provisioning modules at $55-$65 per hour or a fixed quote, so I am not neutral. I am CTO at ElySpace, a freelance developer for Marmaids LLC, and founder of WHMCSPilot and MagizAI.

Share this article

Shahid Malla

Written by

Shahid Malla

Freelance WHMCS developer and web designer with 13+ years of client work. CTO at ElySpace, freelance developer for Marmaids LLC, and founder of WHMCSPilot and MagizAI.

Got a project like this?

Tell me what you need and I will send a written quote within one business day.