A child theme structure, the template markup you must keep, and hook-based customisation without touching the core, so your custom theme survives WHMCS updates.
Hosting companies share a common nightmare: a WHMCS update comes out, it gets installed, and the custom client area suddenly reverts or breaks somewhere. The cause is usually the same: changes were made to files the update overwrites.
In this post I cover how to customise WHMCS and keep update day calm.
Where does a theme live?
In WHMCS, client-area themes live in the templates/ folder, and cart (order form) templates live under templates/orderforms/. With WHMCS 9 the default theme became Nexus; in 8.x it was Twenty-One. Each theme’s identity is the theme.yaml file in its folder.
Child theme: carry only what you change
Copying the default theme’s folder and working on it looks like the fastest start, but updating that copy by hand in the next release is on you. A child theme contains only the files you change; the rest comes from the parent.
# templates/mybrand/theme.yaml
name: "My Brand"
description: "Client area for My Brand"
config:
parent: twenty-one
parent is the parent theme’s folder name. Every file you add to the child theme replaces the same file in the parent; the ones you don’t add refresh themselves with updates.
When a full theme? If the design departs so far from stock that most templates will be rewritten anyway, a standalone theme is the more honest choice. You merge updates yourself, but you get a self-contained, auditable theme that doesn’t shift under you between releases.
Markup you must keep
WHMCS’s JavaScript and addons rely on certain elements in the templates. For example, the {$headoutput} variable in head.tpl is where addons inject code into the page. A theme that deletes it will one day wonder why a payment addon doesn’t work. Change the design, keep these connection points.
Hooks instead of the core
Adding a link to the menu, passing a variable to a page or writing a tag into <head> doesn’t require touching core files. A small PHP file in includes/hooks/ is enough:
<?php
// includes/hooks/brand-menu.php
use WHMCS\View\Menu\Item as MenuItem;
add_hook('ClientAreaPrimaryNavbar', 1, function (MenuItem $navbar) {
$navbar->addChild('Status Page', [
'uri' => 'https://status.example.com',
'order' => 90,
]);
});
Hooks have one golden rule: don’t throw. An uncaught error in a hook that runs on every page can take down the whole panel. If you call an outside service, keep the timeout short and catch and log the error.
Update day checklist
- Update a copy environment first.
- Compare the files in your child theme with the parent’s new version.
- Walk through the cart, payment and domain search flows end to end.
- Check the error logs of your hook files.
The Nova, Neva and NetYuva themes in the portfolio were built with this approach.