Last updated July 11, 2026, Version 0.34
This guide shows how to add custom settings to GatherPress from a companion plugin or theme.
Adding a New Settings Tab
Create a class that extends GatherPress\Core\Settings\Base:
<?php
namespace My_Plugin\Settings;
use GatherPress\Core\Settings\Base;
use GatherPress\Core\Traits\Singleton;
class Notifications extends Base {
use Singleton;
protected function get_slug(): string {
return 'notifications';
}
protected function get_name(): string {
return __( 'Notifications', 'my-plugin' );
}
protected function get_priority(): int {
return 5; // Lower = earlier in tab order.
}
protected function get_sections(): array {
return array(
'email' => array(
'name' => __( 'Email Settings', 'my-plugin' ),
'description' => __( 'Configure email notifications.', 'my-plugin' ),
'options' => array(
'notify_on_rsvp' => array(
'labels' => array(
'name' => __( 'RSVP Notifications', 'my-plugin' ),
),
'field' => array(
'label' => __( 'Send email when someone RSVPs.', 'my-plugin' ),
'type' => 'checkbox',
'options' => array(
'default' => false,
),
),
),
'admin_email' => array(
'labels' => array(
'name' => __( 'Admin Email', 'my-plugin' ),
),
'field' => array(
'label' => __( 'Email address for notifications.', 'my-plugin' ),
'type' => 'text',
'size' => 'regular',
'options' => array(
'default' => '',
),
),
),
),
),
);
}
}
Then instantiate it in your plugin:
add_action( 'plugins_loaded', function() {
if ( class_exists( 'GatherPress\Core\Settings\Base' ) ) {
My_Plugin\Settings\Notifications::get_instance();
}
});
Your tab will appear in the GatherPress settings page, and your options are automatically:
- Stored in the shared
gatherpress_settingsoption - Sanitized based on field type
- Included in import/export
- Stripped when matching their defaults
Reading and Writing Your Custom Settings
use GatherPress\Core\Settings;
$settings = Settings::get_instance();
// Read.
$notify = $settings->get( 'notify_on_rsvp' ); // false (default)
$email = $settings->get( 'admin_email' ); // '' (default)
// Write. Values matching the default are automatically removed.
$settings->set( 'notify_on_rsvp', true );
Field Types
checkbox
Boolean toggle. Sanitized to (bool).
'my_option' => array(
'labels' => array( 'name' => __( 'Label', 'my-plugin' ) ),
'field' => array(
'label' => __( 'Description shown next to the checkbox.', 'my-plugin' ),
'type' => 'checkbox',
'options' => array( 'default' => false ),
),
),
text
Text input. Sanitized with sanitize_text_field().
'my_option' => array(
'labels' => array( 'name' => __( 'Label', 'my-plugin' ) ),
'field' => array(
'label' => __( 'Description shown above the input.', 'my-plugin' ),
'type' => 'text',
'size' => 'regular', // 'small', 'regular', or 'large'.
'options' => array( 'default' => 'default value' ),
),
),
number
Numeric input. Sanitized with intval().
'my_option' => array(
'labels' => array( 'name' => __( 'Label', 'my-plugin' ) ),
'field' => array(
'label' => __( 'Description.', 'my-plugin' ),
'type' => 'number',
'size' => 'small',
'options' => array(
'default' => 10,
'min' => '1',
'max' => '100',
),
),
),
select
Dropdown. Sanitized with sanitize_text_field().
'my_option' => array(
'labels' => array( 'name' => __( 'Label', 'my-plugin' ) ),
'field' => array(
'label' => __( 'Description.', 'my-plugin' ),
'type' => 'select',
'options' => array(
'default' => 'option_a',
'items' => array(
'option_a' => __( 'Option A', 'my-plugin' ),
'option_b' => __( 'Option B', 'my-plugin' ),
),
),
),
),
autocomplete
Dynamic search field for selecting posts or users. Value stored as JSON string.
'my_option' => array(
'labels' => array( 'name' => __( 'Label', 'my-plugin' ) ),
'field' => array(
'type' => 'autocomplete',
'options' => array(
'type' => 'user', // or 'page'.
'label' => __( 'Select users', 'my-plugin' ),
'limit' => 5,
'default' => '[]',
),
),
),
Field Config Options
Preview
Add a live preview below text fields. Create a template in includes/templates/admin/settings/partials/:
'field' => array(
'type' => 'text',
'options' => array( 'default' => 'Y-m-d' ),
'preview' => array(
'template' => 'my-preview', // Renders partials/my-preview.php.
'custom' => 'extra data', // Passed to the template.
),
),
Rewrite
Flag fields that affect permalink structure. When these change, rewrite rules are automatically flushed:
'field' => array(
'type' => 'text',
'rewrite' => true,
'options' => array( 'default' => 'my-slug' ),
),
show_if (conditional visibility)
Show a field only when one or more controlling fields hold a specific value. Useful for “this setting only matters when that other setting is on” relationships — for example, hiding the Google Maps API Key field unless Google is the selected map provider.
show_if is a sibling of field, not nested inside it. It maps controlling field keys to expected values:
'google_maps_api_key' => array(
'labels' => array( 'name' => __( 'Google Maps API Key', 'my-plugin' ) ),
'description' => __( 'Required when Google Maps is selected.', 'my-plugin' ),
'field' => array(
'type' => 'text',
'size' => 'regular',
'options' => array( 'default' => '' ),
),
'show_if' => array(
'map_platform' => 'google',
),
),
Match semantics
- Scalar value — string equality after casting.
'map_platform' => 'google'matches when the controlling field’s current value (coerced to string) equals'google'. - Array of values — OR within one key.
'map_platform' => array( 'google', 'mapbox' )matches when the current value is either. - Multiple keys — AND across keys. Every entry in the
show_ifarray must be satisfied for the field to be visible.
'show_if' => array(
'map_platform' => array( 'google', 'mapbox' ),
'venue_map_default_render_mode' => 'interactive',
),
Hidden ≠cleared
Hiding a field is purely visual — its stored value is never dropped just because the field is currently hidden. If a user enters a Google Maps API key, switches the platform to OSM, and saves, the key remains in the gatherpress_settings option. Switching back to Google reveals the field with its prior value intact.
This works because (a) the field row is hidden via CSS, so the input still posts its value, and (b) the save path merges submitted input with the previously stored options — any field key not in POST keeps its existing value.
Constraints (v1)
- Same page only. The controlling field must live on the same settings tab as the dependent field.
- Single controller per key. Each option key on a settings page renders one input; that’s the input the JS will resolve.
- Comparisons are string-based. Booleans and numbers are coerced to string before comparing. To match a checkbox stored as
true, declare'my_toggle' => 'true'.
Key Uniqueness
All option keys must be globally unique across all tabs and sections. If two extensions register the same key, an admin error notice is displayed:
GatherPress: Duplicate settings keys found: my_option. Each key must be unique.
Use a prefix for your keys to avoid collisions (e.g., myplugin_notify_on_rsvp).
Custom (Non-Form) Tabs
For tabs that need custom UI instead of the standard settings form (like the Tools or Credits tabs), hook into gatherpress_settings_section at priority 9:
protected function setup_hooks(): void {
parent::setup_hooks();
add_action( 'gatherpress_settings_section', array( $this, 'settings_section' ), 9 );
}
public function settings_section( string $page ): void {
if ( Utility::unprefix_key( $page ) === $this->slug ) {
remove_action(
'gatherpress_settings_section',
array( Settings::get_instance(), 'render_settings_form' )
);
// Render your custom template.
Utility::render_template( '/path/to/your/template.php', array(), true );
}
}
Related Hooks
gatherpress_sub_pages— Filter to modify the sub-pages array directlygatherpress_roles— Filter to add custom roles to the Roles tab