A typed, fluent PHP library for registering WordPress Customizer panels, sections, fields and controls.
Write sections and fields against this API, then call
Customizer::make()->...->register() once from your theme or plugin's setup
code.
- Documentation
- Requirements
- Installation
- Quick start
- Fields
- Storage
- Conditions
- Partial refresh
- Migrating from Kirki
- Contributing
- FAQ
- Feedback
- Security
- Changelog
- Credits
- License
- Installation, asset URLs and symlinks
- Fields in depth: the catalogue, stored value shapes, sanitize callbacks, partial refresh, assets
- Conditional visibility:
visibleWhen(),active_callback, the operator table - Conditionally required values:
requiredWhen(), server-sidevalue_requiredvalidation - Migrating from Kirki: the partial-migration strategy
- Out of scope and known limitations
- Testing: unit vs integration suites
- Releasing: maintainers only
- PHP
^8.4 - WordPress
>= 7.0
cd web/app/themes/your-theme
composer require itinerisltd/wpc-builderThe package must end up somewhere under WP_CONTENT_DIR: a theme, child
theme, plugin or mu-plugin all qualify. Asset URLs are derived from its
filesystem position, so installing outside that tree (e.g. a Bedrock root
composer.json) ships no CSS or JS and says so via _doing_it_wrong().
Symlinked installs have sharp edges, covered in
installation.
examples/ is a working reference.
namespace App;
use Itineris\WpcBuilder\Customizer;
Customizer::make()
->addSections([
Sections\Footer::class,
Sections\SiteIdentity::class,
])
->register();Everything hooks onto customize_register (priority 20) and
customize_controls_enqueue_scripts; nothing runs at class-load time.
A section declares $id and $title as property defaults and returns its
fields:
namespace App\Sections;
use Itineris\WpcBuilder\Fields\Editor;
use Itineris\WpcBuilder\Fields\Image;
use Itineris\WpcBuilder\Sections\AbstractSection;
final class Footer extends AbstractSection
{
protected string $id = 'footer';
protected ?string $title = 'Footer';
protected function fields(): array
{
return [
Editor::make('footer_logo_text')
->setLabel('Footer Logo Text')
->setPartialRefresh(
'#footer .footer-logo a',
static function (): string {
$value = get_theme_mod('footer_logo_text', '');
return is_string($value) ? $value : '';
},
),
Image::make('footer_logo_image')
->setLabel('Footer Logo Image'),
];
}
}To extend a WordPress core section such as Site Identity, set $id to the
core section's id (title_tagline). Existing sections are detected and
add_section() is skipped.
24 field classes: Text, Textarea, Editor, Url, Link, Number,
Slider, Select, PostSelect, Radio, RadioButtonset,
DropdownPages, Multicheck, Checkbox, CheckboxToggle,
CheckboxSwitch, Toggle, Image, Color, ColorPalette, Dimensions,
Custom, Repeater and Tabs.
Stored value shapes vary per field, so check fields.md before relying on one.
Settings are theme mods by default. Pass a Config to store them in
wp_options instead:
use Itineris\WpcBuilder\Config;
use Itineris\WpcBuilder\Customizer;
use Itineris\WpcBuilder\Enums\OptionType;
Customizer::make()
->setConfig(new Config(optionType: OptionType::OPTION, optionName: 'my_prefix'))
->register();Setting ids then become "my_prefix[fieldId]". capability, optionType
and optionName are all overridable per field via setCapability(),
setOptionType() and setOptionName(), so one section can mix storages.
use Itineris\WpcBuilder\Fields\Text;
Text::make('alert_message')
->setVisibleWhen([
['setting' => 'my_prefix[alert_enabled]', 'operator' => '==', 'value' => true],
]);setRequiredWhen() takes the same condition rows but is independent of
setVisibleWhen(): it hides nothing, and instead fails the changeset save
with a value_required WP_Error when the conditions pass and the value is
blank. The asterisk and required attribute on the control are a hint;
server-side validation is the enforcement.
Operators, nesting and the fail-open rule: conditional-visibility.md and conditionally-required.md.
setPartialRefresh($selector, $renderCallback) registers a selective-refresh
partial and forces the field's transport to postMessage, which the partial
needs to be reachable. See fields.md.
Migrating section by section is safe, but Kirki must stay active until every
Kirki::get_option() call site is rewritten. Read
migrating-from-kirki.md before starting.
composer style:check # PHPCS
composer stan # PHPStan, level max, zero baseline entries
composer test:unit # Pest, brain/monkey-mocked WordPress
npm run lint # stylelint (assets/src/css) + eslint (assets/src/js)
npm test # Vitest (assets/src/js/**/*.test.js)
npm run build # Vite: assets/src/{js,css}/ -> dist/{js,css}/
shellcheck scripts/*.sh # release and packaging scripts
composer test:integration # Pest, real WordPress + database; see docs/testing.mdThe first seven commands are the commit gate. dist/ is generated and never
committed; sources live in assets/src/.
- Articles on Itineris' blog
- More projects on Itineris' GitHub profile
- Follow @itineris_ltd on Twitter
- Hire Itineris to build your next awesome site
Thanks! Glad you like it. It's important to let people know somebody is using this project. Consider:
- tweeting something good with a mention of @itineris_ltd
- starring this GitHub repo
- watching this GitHub repo
- submitting pull requests
- hiring Itineris
Please provide feedback! We want to make this library useful in as many projects as possible. Please submit an issue and point out what you do and don't like, or fork the project and make suggestions. No issue is too small.
If you discover any security related issues, please email hello@itineris.co.uk instead of using the issue tracker.
Please see CHANGELOG for more information on what has changed recently.
wpc-builder is an Itineris Limited project created by Lee Hanbury-Pickett.
Full list of contributors can be found here.
wpc-builder is released under the MIT License.