ACF Fieldsmith GitLab self-updates
A modular set of custom field types and tools for Advanced Custom Fields. Each module can be switched on or off under ACF > Fieldsmith.
by Viktor Kovalenko · gitlab.com/v_kovalenko/acf-fieldsmith · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://gitlab.com/v_kovalenko/acf-fieldsmith/-/archive/master/acf-fieldsmith-master.zipShips its own WordPress updater (built-in updater), so new versions show up under Dashboard → Updates.
A modular set of custom field types and tools for Advanced Custom Fields. Every module can be switched on or off under ACF > Fieldsmith, and modules with options of their own get a Settings dialog on their card.
Requirements
| WordPress | 6.5+ |
| PHP | 8.1+ |
| ACF | 6.0+ (free or PRO). Some modules need more; their card says so. |
Activation is refused (with a message) when ACF 6.0+ isn't active. If ACF is deactivated later, an admin notice says so and all modules stay idle.
Modules
| Module | Version | Field type | Ported from |
|---|---|---|---|
| Predefined Color Picker | 1.0.4 | palette_color_picker |
ACF Predefined Color Picker Field 1.0.4 |
| Multi Column | 1.0.1 | multi_column |
Advanced Custom Fields: Multi Column Field 1.0.1 |
| Extended Range Selector | 1.0.2 | extended_range_selector |
ACF Extended Range Selector Field 1.0.2 |
| Iconic Radio Group | 1.2.0 | iconic_radio_group |
ACF Iconic Radio Group 1.2.0 |
| Icon Picker | 2.0.2 | acf_icon_picker |
Advanced Custom Fields: Icon Picker 2.0.2 |
| Clone Defaults | 1.0.0 | — ("Defaults" tab on the Clone field, ACF PRO 6.1+) | ACF Extended Clone Field 1.0.0 |
A module's version starts at the version of the standalone plugin it was ported from and is shown on its card and in its details dialog; the dialog's Overview also links to the standalone plugin's repository. The plugin version (title bar) is separate.
Field type names are the same as in the standalone plugins, so existing field groups, acf-json and saved values work without any migration.
Settings page
ACF > Fieldsmith is laid out like a dashboard:
- Title bar: plugin name and the version badge.
- Summary tiles: modules installed, active, inactive, and fields of Fieldsmith types in use (with the number of field groups).
- Category filters (All / Field types / Tools) as a segmented control.
- One card per module: icon, name, category, description, field type names, a Usage row, status badge, an on/off switch and, for modules with options, a Settings button. Flipping a switch saves straight away.
Click a card (anywhere but its switch and buttons), or its title, to open the module's details dialog: topics in a side list (Overview with the key facts, then the module's own topics such as Features, Field settings, Usage examples and Developer, and Where used). Modules describe their topics in details(); see EXTENDING.md.
The Usage row shows "Not in use" or a link such as "3 fields in 2 groups". The link opens the details dialog on Where used, listing per field group, every field of the module's types: label, name, location (e.g. "Sections › Text section" for a field inside a flexible content layout) and type. Field groups stored in the database link to their edit screen (when the user may edit them); groups registered in PHP or loaded from acf-json are tagged "PHP" / "JSON". Fields pulled in by a Clone field are counted in their own group.
Statuses:
| Status | Meaning |
|---|---|
| Active | Running. |
| Off | Switched off. Its fields are hidden in the editor; saved values stay in the database. |
| Standalone plugin active | The plugin the module was ported from is active, so the module stays off and nothing is registered twice. Deactivate that plugin to use the module. |
| Requires … | The site doesn't meet the module's ACF / ACF PRO requirement. |
The page sits under Settings instead when ACF is inactive or its admin menu is hidden (show_admin). It uses ACF's capability setting (default manage_options) and saves through the Settings API.
Adding a module
Full guide: EXTENDING.md — field types, tool modules, module settings, card icons, modules from other plugins, porting a standalone plugin, the OWASP checklist and the release checklist.
In short: create modules/<id>/module.php that returns a module instance. A field type module:
<?php
declare(strict_types=1);
namespace CDL\AcfFieldsmith\Modules\MyField;
use CDL\AcfFieldsmith\FieldModule;
defined('ABSPATH') || exit;
final class Module extends FieldModule {
public function id(): string { return 'my-field'; } // lowercase, digits, dashes
public function title(): string { return __('My Field', 'acf-fieldsmith'); }
public function description(): string { return __('What it does, in one sentence.', 'acf-fieldsmith'); }
// Optional
public function icon(): string { return 'dashicons-admin-generic'; } // or 'assets/images/icon.svg', '<svg …>', 'data:image/svg+xml;base64,…'
public function field_types(): array { return array('my_field'); } // for the Usage row and dialog
public function requires_acf(): string { return '6.0'; }
public function requires_acf_pro(): bool { return false; }
public function standalone_constant(): string { return 'MY_STANDALONE_PLUGIN_VERSION'; }
protected function field_class(): string {
require_once __DIR__ . '/src/Field.php';
return Field::class;
}
}
return new Module(__DIR__);
The field class extends CDL\AcfFieldsmith\BaseField (which extends acf_field) and reaches its module through $this->module:
$this->preview_image = $this->module->url('assets/images/preview.svg');
wp_enqueue_script($this->module->handle(), $this->module->url('assets/js/input.js'), array('acf-input'), $this->module->version(), array('in_footer' => true));
A module that is not a field type (a tweak to an existing ACF field, say) extends CDL\AcfFieldsmith\Module and adds its hooks in boot().
Card icon
icon() takes the same kinds of value as the icon of add_menu_page(): a Dashicons class, an SVG file in the module folder, SVG markup or a base64 SVG data URI. SVG icons are drawn like Dashicons: one colour (the card's accent, grey when the module is off), shape only. Other image files in the module folder (PNG, WebP) are shown as they are. Anything unusable falls back to a generic icon. Details in EXTENDING.md.
Module settings
Return definitions from settings() and the card gets a Settings button that opens a dialog:
public function settings(): array {
return array(
'default_palette' => array(
'type' => 'textarea', // text | textarea | number | checkbox | select
'label' => __('Default palette', 'acf-fieldsmith'),
'description' => __('Shown under the input.', 'acf-fieldsmith'),
'default' => '',
// select: 'choices' => array('value' => 'Label')
// number: 'min', 'max', 'step'
),
);
}
Read a value with $this->setting('default_palette') (on or after init). Values are sanitized by type; override sanitize_settings(array $values): array for extra validation.
Modules from another plugin
add_action('acf_fieldsmith/register_modules', function (\CDL\AcfFieldsmith\Registry $registry): void {
$registry->add(new My_Module(__DIR__));
});
The action fires on plugins_loaded, so it must be added from a plugin or a must-use plugin, not from a theme.
Hooks
| Hook | Type | |
|---|---|---|
acf_fieldsmith/register_modules |
action | (Registry $registry) Add modules. |
acf_fieldsmith/module_enabled |
filter | (bool $on, string $id, Module $module) Force a module on or off in code, e.g. per environment. The card then shows "(code)" and its switch is locked. |
Filters of ported modules keep their original names, for example palette_color_picker_args (JS, Predefined Color Picker) and cdl_extended_range_selector_accent_color (PHP, Extended Range Selector: (string $color, array $field), the result must pass sanitize_hex_color()) and cdl_iconic_radio_group_icon_folder (PHP, Iconic Radio Group: (string $folder, array $field), a path relative to the theme root; .. segments are refused). Icon Picker keeps ACF_ICON_PICKER_folder (theme folder, default dist/img/icons/), ACF_ICON_PICKER_custom_location (array('path' => ..., 'url' => ...)) and the deprecated acf_icon_path_suffix, plus the theme helpers \CDL\AcfIconPicker\get_icon(), get_icon_uri() and get_icon_path(), which are defined while the module runs. Clone Defaults keeps acf_extended_clone_field/is_supported ((bool $supported, array $field), opt a cloned field type in or out) and acf_extended_clone_field/sanitize_value/type={type} ((null $sanitized, mixed $value, array $field), return the clean override or null to reject it).
Icon Picker's Icon return format and get_icon() print the SVG file unchanged, scripts included, exactly like the standalone plugin. Keep the icon folder (and ACF_ICON_PICKER_custom_location) in a place only developers can write to, never the uploads folder.
The standalone Icon Picker plugin defines the same helper functions without a guard, so it can't be activated while the Icon Picker module is on (WordPress refuses with a fatal error message). To go back to the standalone plugin, switch the module off first, then activate the plugin.
Storage
| Option | Content |
|---|---|
acf_fieldsmith_modules |
array('color-picker' => true, ...). A module not listed is on. |
acf_fieldsmith_module_settings |
array('color-picker' => array('default_palette' => '...'), ...) |
Both are removed when the plugin is deleted. Field values are never touched.
Development
composer install
composer check # phpcs (WPCS security, PHPCompatibility) + PHPStan level 8 + tests
composer test # core + Clone Defaults tests (WP_CORE_DIR=/path/to/wordpress if the plugin isn't inside a WordPress install)
composer pot # regenerate languages/acf-fieldsmith.pot (needs WP-CLI as `wp`)
Translations: languages/acf-fieldsmith.pot is the template; put acf-fieldsmith-<locale>.po/.mo (for example acf-fieldsmith-pl_PL.mo) next to it, or in wp-content/languages/plugins/, which wins when both exist. Regenerate the template whenever translatable strings change.
tests/ (test suites, PHPStan bootstrap and stubs) stays in the repository so the checks can run before a release, but it is not in the release zip: .gitattributes marks it export-ignore, like composer.json, phpcs.xml.dist, phpstan.neon.dist, .gitlab-ci.yml and the git files. The zip holds the plugin code plus README.md, EXTENDING.md and readme.txt.
Releases: push a vX.Y.Z tag; GitLab CI builds acf-fieldsmith.zip, uploads it to the project's package registry and publishes the release with the GitLab CLI (glab release create, pinned image registry.gitlab.com/gitlab-org/cli:v1.120.0), and sites get the update through includes/class-cdl-gitlab-updater.php. Release notes come from the readme.txt changelog. The job signs in with the CI job token (GLAB_ENABLE_CI_AUTOLOGIN); no access token or CI variable is needed. Don't set GITLAB_TOKEN in the job: glab would use it instead of the job token.
Layout
acf-fieldsmith.php Bootstrap: header, constants, updater, boot on plugins_loaded
uninstall.php Deletes the two options
src/Plugin.php Boots modules that may run; category label; ACF notice
src/Registry.php Finds modules/*/module.php and external modules
src/Module.php Module base class
src/FieldModule.php Base for field type modules
src/BaseField.php Base for module field classes (acf_field + $module)
src/Settings.php Options, sanitizers
src/SettingsPage.php ACF > Fieldsmith
src/ModuleDetails.php Details dialog of a module (topics, where used)
assets/admin/ Settings page CSS and JS (no build step)
modules/<id>/ One folder per module
includes/ Shared GitLab updater
languages/ Translation template (acf-fieldsmith.pot)
tests/ PHPStan bootstrap and stubs; core and module test suites (not in the release zip)
EXTENDING.md How to add or extend modules
License
GPLv2 or later.