pinkcrab / perique-settings-page
Settings page extension for the Perique Admin Menu system.
Requires
- php: >=8.0.0
- pinkcrab/collection: ^0.2.0
- pinkcrab/form-components: 2.1.*
- pinkcrab/perique-admin-menu: 2.1.*
- pinkcrab/perique-framework-core: 2.1.*
- respect/validation: ^2.0
Requires (Dev)
- dealerdirect/phpcodesniffer-composer-installer: *
- gin0115/wpunit-helpers: ~1
- php-stubs/wordpress-stubs: 6.9.*
- phpstan/phpstan: ^1.0
- phpunit/phpunit: ^8.0 || ^9.0
- roots/wordpress: 6.9.*
- symfony/var-dumper: *
- szepeviktor/phpstan-wordpress: ^1.0
- vlucas/phpdotenv: ^5.4
- wp-coding-standards/wpcs: *
- wp-phpunit/wp-phpunit: 6.9.*
- yoast/phpunit-polyfills: ^1.0.0 || ^2.0.0
This package is auto-updated.
Last update: 2026-04-17 22:44:54 UTC
README
Build admin settings pages for the Perique Framework with a fluent PHP API. Define fields, layouts, themes and validation in pure PHP; the library handles persistence, sanitisation and rendering via Form Components.
Requires Perique Framework 2.1.* or newer. Not compatible with older versions.
Setup
$ composer require pinkcrab/perique-settings-page
Register the module in your Perique bootstrap:
use PinkCrab\Perique\Application\App_Factory; use PinkCrab\Perique_Admin_Menu\Module\Perique_Admin_Menu; use PinkCrab\Form_Components\Module\Form_Components; use PinkCrab\Perique_Settings_Page\Registration\Settings_Page_Module; ( new App_Factory() ) ->default_setup() ->module( Form_Components::class ) ->module( Perique_Admin_Menu::class ) ->module( Settings_Page_Module::class ) ->boot();
Depends on
- PinkCrab Perique Framework
2.1.* - PinkCrab Perique Admin Menu
2.1.* - PinkCrab Form Components
2.1.*
Quick Start
Two classes are required: an Abstract_Settings subclass holding the field definitions, and a Settings_Page subclass that renders the admin page.
Settings class:
use PinkCrab\Perique_Settings_Page\Setting\Abstract_Settings; use PinkCrab\Perique_Settings_Page\Setting\Setting_Collection; use PinkCrab\Perique_Settings_Page\Setting\Field\Text; use PinkCrab\Perique_Settings_Page\Setting\Field\Select; class Acme_Settings extends Abstract_Settings { protected function is_grouped(): bool { return true; } public function group_key(): string { return 'acme_settings'; } protected function fields( Setting_Collection $settings ): Setting_Collection { return $settings->push( Text::new( 'site_name' ) ->set_label( 'Site Name' ) ->set_required(), Select::new( 'theme' ) ->set_label( 'Default Theme' ) ->set_option( 'light', 'Light' ) ->set_option( 'dark', 'Dark' ) ); } }
Page class:
use PinkCrab\Perique_Settings_Page\Page\Settings_Page; class Acme_Settings_Page extends Settings_Page { protected $parent_slug = 'options-general.php'; protected $page_slug = 'acme_settings'; protected $menu_title = 'Acme Settings'; protected $page_title = 'Acme Plugin Settings'; protected string $theme_stylesheet = Settings_Page::STYLE_VANILLA; public function settings_class_name(): string { return Acme_Settings::class; } }
Register the page through the Admin Menu module as normal. The settings object is resolved via the DI container, so you can type-hint dependencies on the constructor.
Building a Complete Settings Page
A fuller example using layouts, a custom theme, a repeater and field validation.
use PinkCrab\Perique_Settings_Page\Setting\Abstract_Settings; use PinkCrab\Perique_Settings_Page\Setting\Setting_Collection; use PinkCrab\Perique_Settings_Page\Setting\Field\{ Text, Email, Phone, Select, Checkbox, Repeater, Media_Library, Colour }; use PinkCrab\Perique_Settings_Page\Setting\Layout\{ Section, Row, Grid, Stack, Divider, Notice }; use Respect\Validation\Validator as v; class Contact_Settings extends Abstract_Settings { protected function is_grouped(): bool { return true; } public function group_key(): string { return 'acme_contact'; } protected function fields( Setting_Collection $settings ): Setting_Collection { return $settings->push( Notice::info( 'These details appear in the site footer.' ), Section::of( Row::of( Text::new( 'company_name' )->set_label( 'Company Name' )->set_required(), Email::new( 'support_email' ) ->set_label( 'Support Email' ) ->set_validate( v::email() ) )->sizes( 2, 1 ), Phone::new( 'support_phone' )->set_label( 'Support Phone' ) ) ->title( 'Contact Information' ) ->description( 'Primary channels for customer support.' ), Section::of( Grid::of( Media_Library::new( 'logo' )->set_label( 'Brand Logo' ), Colour::new( 'brand_colour' )->set_label( 'Brand Colour' ) )->columns( 2 ), Divider::make(), Repeater::new( 'social_links' ) ->set_label( 'Social Links' ) ->add_field( Text::new( 'label' )->set_label( 'Label' ) ) ->add_field( Text::new( 'url' )->set_label( 'URL' ) ) ) ->title( 'Branding' ) ->collapsible() ); } }
Settings API
All settings classes extend Abstract_Settings and implement three required methods.
Required methods
| Method | Description |
|---|---|
fields( Setting_Collection $settings ): Setting_Collection |
Return the collection with all fields and layouts pushed in. |
is_grouped(): bool |
true to save under a single option key, false to save each field as its own option. |
group_key(): string |
When grouped, the option name. When not grouped, the key prefix. |
use PinkCrab\Perique_Settings_Page\Setting\Abstract_Settings; use PinkCrab\Perique_Settings_Page\Setting\Setting_Collection; use PinkCrab\Perique_Settings_Page\Setting\Field\{ Text, Number }; class My_Settings extends Abstract_Settings { // true → stored as one associative array under group_key(). // false → each field stored as its own option, prefixed with group_key(). protected function is_grouped(): bool { return true; } // When grouped: the wp_options name that holds the whole array. // When ungrouped: the prefix added to every field's option name. public function group_key(): string { return 'acme_settings'; } // Build the field collection. Runs once at construction. protected function fields( Setting_Collection $settings ): Setting_Collection { return $settings->push( Text::new( 'site_name' )->set_label( 'Site Name' )->set_required(), Number::new( 'api_limit' )->set_label( 'API Limit' )->set_min( 1 ) ); } }
Value access methods
| Method | Description |
|---|---|
get( string $key, $fallback = null ) |
Return the current value for a field, or the fallback. |
set( string $key, $data ): bool |
Persist a new value for a field. Returns true on success. |
has( string $key ): bool |
Whether a field with that key exists. |
find( string $key ): Field|Field_Group|null |
Return the field instance, or null. |
delete( string $key ): ?bool |
Remove the stored value. Returns null if the field doesn't exist. |
get_keys(): array |
All field keys, including those nested inside layouts. |
get_all_fields(): array |
All Field / Field_Group instances, flattened. |
export(): array |
The full Setting_Collection as an array (layouts intact). |
refresh_settings(): void |
Re-hydrate every field from the repository. |
prefix_key( string $key ): string |
Returns "{group_key}_{key}". |
Settings Page API
All pages extend Settings_Page (which extends Menu_Page from the Admin Menu module).
Properties
| Property | Type | Description |
|---|---|---|
$parent_slug |
string |
Parent menu slug (e.g. options-general.php). Inherited from Menu_Page. |
$page_slug |
string |
Unique page slug. Inherited from Menu_Page. |
$menu_title |
string |
Label in the admin menu. Inherited from Menu_Page. |
$page_title |
string |
<title> and <h1> for the page. Inherited from Menu_Page. |
$position |
int |
Menu position. Inherited from Menu_Page. |
$capability |
string |
Required capability. Inherited from Menu_Page. |
$theme_stylesheet |
string |
Theme identifier. One of the STYLE_* constants, or an absolute path / URL. Default STYLE_VANILLA. |
$method |
string |
'POST' or 'GET'. Default 'POST'. |
$pre_template |
?string |
View template rendered inside .wrap, above the form. |
$pre_data |
array |
Data passed to $pre_template. |
$post_template |
?string |
View template rendered inside .wrap, below the form. |
$post_data |
array |
Data passed to $post_template. |
use PinkCrab\Perique_Settings_Page\Page\Settings_Page; class My_Settings_Page extends Settings_Page { // From Menu_Page — where this page sits in the admin menu. protected $parent_slug = 'options-general.php'; protected $page_slug = 'acme_settings'; protected $menu_title = 'Acme Settings'; protected $page_title = 'Acme Plugin Settings'; protected $position = 25; protected $capability = 'manage_options'; // Which bundled theme to load — STYLE_* constant, absolute path, or URL. protected string $theme_stylesheet = Settings_Page::STYLE_MATERIAL; // Form method. POST is the default and what you'll want 99% of the time. protected string $method = 'POST'; // Optional view templates rendered inside .wrap (above and below the form). protected ?string $pre_template = 'admin/intro'; protected array $pre_data = array( 'version' => '2.1' ); protected ?string $post_template = 'admin/footer'; protected array $post_data = array(); public function settings_class_name(): string { return My_Settings::class; } }
Theme constants
| Constant | File |
|---|---|
Settings_Page::STYLE_VANILLA |
assets/themes/vanilla.css |
Settings_Page::STYLE_MATERIAL |
assets/themes/material.css |
Settings_Page::STYLE_BOOTSTRAP |
assets/themes/bootstrap.css |
Settings_Page::STYLE_BOOTSTRAP_CLASSIC |
assets/themes/bootstrap-classic.css |
Settings_Page::STYLE_WP_ADMIN |
assets/themes/wp-admin.css |
Settings_Page::STYLE_MINIMAL |
assets/themes/minimal.css |
Settings_Page::STYLE_NONE |
No theme (structural CSS only). |
See docs/themes.md for the full visual guide.
Methods
| Method | Description |
|---|---|
settings_class_name(): string |
Abstract. Fully qualified class name of the matching Abstract_Settings subclass. |
scripts(): void |
Override to enqueue custom scripts. |
styles(): void |
Override to enqueue custom styles. |
before_render(): void |
Override to populate $pre_template / $post_template at render time. |
set_pre_template( string $template, array $data = [] ): static |
Fluent setter for the above-form template. |
set_post_template( string $template, array $data = [] ): static |
Fluent setter for the below-form template. |
get_method(): string |
Current form method. |
get_submit_label(): string |
Label on the submit button (override to change). |
get_form_action(): string |
Form action attribute (defaults to empty — posts to self). |
get_nonce_handle(): string |
Nonce handle used for form verification. |
get_nonce_field_name(): string |
Nonce field name (pc_settings_nonce). |
get_settings(): ?Abstract_Settings |
Access the injected settings instance. |
get_theme_stylesheet(): string |
Current theme identifier. |
use PinkCrab\Perique_Settings_Page\Page\Settings_Page; class My_Settings_Page extends Settings_Page { protected $page_slug = 'acme_settings'; // Required — the Abstract_Settings subclass this page renders. // Constructed via the DI container so you can type-hint dependencies. public function settings_class_name(): string { return My_Settings::class; } // Override the default 'Save Settings' label. public function get_submit_label(): string { return 'Save Acme Configuration'; } // Send the form to a custom endpoint. Default '' posts to self. public function get_form_action(): string { return admin_url( 'admin-post.php?action=acme_save' ); } // Enqueue extra scripts for this page. protected function scripts(): void { wp_enqueue_script( 'acme-admin', plugins_url( 'assets/admin.js', __FILE__ ), array( 'pc-settings-page' ), '1.0.0', true ); } // Enqueue extra styles for this page. protected function styles(): void { wp_enqueue_style( 'acme-admin', plugins_url( 'assets/admin.css', __FILE__ ), array( 'pc-settings-page-theme' ), '1.0.0' ); } // Runs inside render_view(), before any HTML is emitted. // Use it to populate the pre/post templates with runtime data. protected function before_render(): void { $settings = $this->get_settings(); $this->set_pre_template( 'admin/acme-intro', array( 'site_name' => $settings?->get( 'site_name', 'Acme' ), ) ); $this->set_post_template( 'admin/acme-footer', array( 'nonce_name' => $this->get_nonce_field_name(), 'nonce_key' => $this->get_nonce_handle(), ) ); } }
See docs/settings-facade-pattern.md for the rationale behind the split between Abstract_Settings (data) and Settings_Page (rendering).
Layouts
Layouts are composable containers that control how fields are arranged. All layouts implement Renderable and can be nested.
use PinkCrab\Perique_Settings_Page\Setting\Layout\{ Section, Row, Grid, Stack, Divider, Notice };
Section
Visual grouping with title, description and optional collapse behaviour.
Section::of( $field_a, $field_b ) ->title( 'General' ) ->description( 'Main configuration.' ) ->collapsible() // allow collapse ->collapsed() // start collapsed (implies collapsible) ->rtl() // right-to-left header layout ->gap( '24px' ); // CSS gap between children
Row
Horizontal arrangement using CSS grid.
Row::of( $field_a, $field_b, $field_c ) ->sizes( 1, 2, 1 ) // fractional column widths (1fr 2fr 1fr) ->align( 'center' ) // 'start' | 'center' | 'end' | 'stretch' ->gap( '16px' );
Grid
Fixed-column grid.
Grid::of( $a, $b, $c, $d ) ->columns( 2 ) // number of columns ->gap( '16px' );
Stack
Vertical stack (default gap 0).
Stack::of( $a, $b, $c ) ->gap( '8px' );
Divider
Horizontal rule between fields.
Divider::make();
Notice
Info, warning, error or success message inside the form.
Notice::info( 'This page only appears on multisite installs.' ); Notice::warning( 'These changes take effect immediately.' ); Notice::error( 'API key is missing.' ); Notice::success( 'Connection verified.' );
Field Types
19 fields, all extending Field. See docs/fields.md for the full method reference.
| Field | Class | Summary |
|---|---|---|
| Text | Field\Text |
Single-line text input. |
Field\Email |
Email input with browser validation. | |
| Phone | Field\Phone |
Tel input. |
| Url | Field\Url |
URL input. |
| Password | Field\Password |
Password input. |
| Textarea | Field\Textarea |
Multi-line text. |
| Number | Field\Number |
Numeric input with min/max/step. |
| Hidden | Field\Hidden |
Hidden value. |
| Select | Field\Select |
Dropdown, single or multiple. |
| Checkbox | Field\Checkbox |
Single on/off. |
| Checkbox_Group | Field\Checkbox_Group |
Multiple checkboxes sharing one key. |
| Radio | Field\Radio |
Radio group. |
| Colour | Field\Colour |
HTML5 colour picker. |
| Media_Library | Field\Media_Library |
WP media library selector. |
| WP_Editor | Field\WP_Editor |
Full TinyMCE / block editor instance. |
| User_Picker | Field\User_Picker |
Search and pick one or more users. |
| Post_Picker | Field\Post_Picker |
Search and pick one or more posts. |
| Repeater | Field\Repeater |
Repeating set of sub-fields. |
| Field_Group | Field\Field_Group |
Named group of related fields saved as an array. |
Themes
Six bundled themes, each a single CSS file layered over core.css.
| Theme | Constant | Style |
|---|---|---|
| Vanilla | STYLE_VANILLA |
Neutral default with static labels. |
| Material | STYLE_MATERIAL |
Material Design 3 with floating labels. |
| Bootstrap | STYLE_BOOTSTRAP |
Bootstrap 5 with floating labels. |
| Bootstrap Classic | STYLE_BOOTSTRAP_CLASSIC |
Bootstrap 5 with static labels. |
| WP Admin | STYLE_WP_ADMIN |
Native WordPress admin look. |
| Minimal | STYLE_MINIMAL |
Bare-bones, maximum density. |
Custom themes can be loaded via absolute path or URL. See docs/themes.md for screenshots and details.
Repositories
Persistence is handled by an implementation of Setting_Repository. Four are bundled.
| Repository | Purpose |
|---|---|
Repository\WP_Options_Settings_Repository |
Default. Stores each setting as its own row in wp_options, or as a grouped array when is_grouped() returns true. |
Repository\WP_Options_Individual_Repository |
Always stores each field as its own option (ignores grouping). |
Repository\WP_Options_Named_Groups_Repository |
Always groups all fields under a single option. |
Repository\WP_Site_Options_Decorator |
Wraps another repository to read/write via get_site_option() / update_site_option() for multisite. |
Implement Setting_Repository for a custom backend (transients, custom tables, remote API, etc.).
Change Log
- 2.1.0 — Full rewrite for Perique Framework 2.1. Rendering moved to Form Components. New layout helpers (Section, Row, Grid, Stack, Divider, Notice). Six bundled themes. Pickers backed by a REST endpoint. Repeater with add / remove / drag-reorder. Full jQuery removal — everything runs on vanilla JS. 648+ unit and integration tests (100% coverage), 70+ Playwright end-to-end tests. Drops support for Perique Framework
< 2.1. - 0.1.0 — Initial recreation of the legacy settings page module on top of
WP_Settings_API.
