Extension API
Lerm\AdminConfig\WordPress\Runtime is the public integration surface for extending Admin Config.
If you are wiring the package into a plugin or theme for the first time, start with Quick Start and come back here when you need custom field types, stores, containers, or data sources.
For the smallest runnable example, see Minimal Extension Plugin on GitHub. For copyable focused snippets, see Extension Recipes.
Runtime methods
register_field_type( string $type, array $definition = array() )register_validator( string $type, callable $validator )register_field_module( FieldModule $module )register_store_factory( string $type, callable $factory )register_container( Container $container )register_data_source( string $source_id, callable $resolver )register_many( array $schemas )has_data_source( string $source_id )resolve_data_source( string $source_id, array $args = array() )defaults( string $schema_id )is_booted()data_sources()containers()field_modules()
Field types
Field type definitions can provide:
renderrender_nestedsanitizevalidateserializeclientpersist
Field registrations are merged by default, so later calls can extend an existing type with extra validators or client metadata.
Field-type client metadata is merged into each compiled field payload before
field-level client overrides are applied. Use this for reusable protocol hints
such as array( 'control' => 'text' ) when a custom PHP field type should reuse
an existing JavaScript control.
Validators
Validators receive:
function ( array $field, $value, bool $strict, OptionStore $store )
Return the validated value on success. Returning WP_Error records the message in the store validation bag, aborts the current save/import request, and surfaces the error back to the active admin container.
Data sources
Named data sources are small runtime registries for schema helpers and async fields.
Typical use today:
$runtime->register_data_source(
'tone_presets',
static fn (): array => array(
'calm' => 'Calm',
'bold' => 'Bold',
'clean' => 'Clean',
)
);
$choices = $runtime->resolve_data_source( 'tone_presets' );
That resolved payload can be injected into schema choices, custom field renderers, or other registration-time helpers.
When a field uses callable choices, remember that the callable may be invoked
multiple times across rendering, sanitization, and validation. Expensive choice
builders should either resolve once before schema registration or memoize their
result:
'choices' => static function (): array {
static $choices = null;
if ( null !== $choices ) {
return $choices;
}
$choices = my_expensive_choice_builder();
return $choices;
},
For reusable lists shared across multiple schemas, prefer register_data_source()
plus resolve_data_source() so the expensive lookup stays outside the field
render path.
Async select fields
The built-in ajax_select field uses the same data-source registry at request
time. The browser uses the REST data-source endpoint; AdminConfig 0.3.0 removed
the deprecated admin-ajax.php rollout fallback.
$runtime->register_data_source(
'campaign_library',
static function ( array $args = array() ): array {
$items = array(
array( 'value' => 'spring-launch', 'label' => 'Spring Launch' ),
array( 'value' => 'creator-series', 'label' => 'Creator Series' ),
array( 'value' => 'audio-week', 'label' => 'Audio Week' ),
);
$search = strtolower( trim( (string) ( $args['search'] ?? '' ) ) );
if ( '' !== $search ) {
$items = array_values(
array_filter(
$items,
static fn ( array $item ): bool => str_contains(
strtolower( $item['label'] . ' ' . $item['value'] ),
$search
)
)
);
}
return array(
'items' => $items,
'more' => false,
);
}
);
array(
'id' => 'featured_campaign',
'type' => 'ajax_select',
'source' => 'campaign_library',
'label' => 'Featured campaign',
'placeholder' => 'Search campaigns...',
'min_search_length' => 1,
'per_page' => 10,
'default' => 'spring-launch',
)
Resolver callbacks receive an $args array with these keys when the field is
queried over the async transport:
searchpageper_pageselectedcontextfieldschemaschema_id
context carries object IDs for meta-backed screens when available, such as
post_id, term_id, user_id, comment_id, or network_id.
Resolvers can return any of these shapes:
- associative array of
value => label - list of arrays like
array( 'value' => 'x', 'label' => 'X' ) - paginated payload like
array( 'items' => ..., 'more' => true )
The runtime normalizes all three forms to the same client payload and enforces the owning schema/container capability before the request resolves.
Containers
Custom containers can be registered through register_container(). The runtime supports late registration, so if a schema was already compiled before its container becomes available, the matching schemas will mount when the container is registered.
Field modules
field_modules() returns the module registry used by the runtime. Besides
automatic activation through enable_for_definition(), the public registry also
exposes:
field_types_for_definition( array $definition )modules_for_definition( array $definition )module_for_field_type( string $field_type )modules_for_field_types( array $field_types )enable_for_field_types( array $field_types )
That matters when field types are assembled dynamically outside the schema array available at registration time. In those cases, pre-enable the needed modules before you register or render the schema:
$runtime->field_modules()->enable_for_field_types(
array( 'typography', 'icon', 'accordion' )
);
Examples
See:
examples/minimal-extension-plugin/examples/schema-demo-plugin/examples/embedded-theme-demo/docs/extension-recipes.md