Elementor dynamic tag register
Skill Lonsdale201/wp-agent-skills/elementor/elementor-dynamic-tag-register
A community-maintained collection of agent skills for WordPress plugin and theme development.
npx -y skills add Lonsdale201/wp-agent-skills --skill elementor-dynamic-tag-registerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Register a custom Elementor Dynamic Tag from a companion plugin — hook the modern elementor/dynamic_tags/register action and call $manager->register( new MyTag() ), where MyTag extends \Elementor\Core\DynamicTags\Tag (echoes via render()) or \Elementor\Core\DynamicTags\Data_Tag (returns via get_value()). The legacy elementor/dynamic_tags/register_tags action + register_tag( $class ) still work but are deprecated since 3.5.0. Covers the four required methods (get_name / get_title / get_group / get_categories), registering UI groups with register_group( $slug, [ 'title' => … ] ), bootstrap timing under elementor/loaded, and the Pro-feature reality — the dynamic-tags API ships in free Elementor but the editor picker and the AJAX query control are Pro, so feature-detect and degrade. Use when scaffolding a plugin that adds dynamic tags, when a diff hooks elementor/dynamic_tags/register(_tags) or extends a DynamicTags base class, or when grouping tags in the editor.
SKILL.md
13.3 KB, ~3.0k tokens by cl100k_base, as published. Nobody here has run it
Elementor: register a Dynamic Tag
For developers shipping a companion plugin that adds Dynamic Tags — "show this product's shipping class", "this user's last order total", "this course's progress %". The dynamic-tags infrastructure (base classes, the registration manager, the hooks, the category/group system) ships in free Elementor under Elementor\Core\DynamicTags\*; this skill is the stable extension contract for registering your own tags against it. It does not cover building the tag's controls/fallback (see elementor-dynamic-tag-fields) or AJAX pickers (see elementor-dynamic-tag-ajax-select).
The Pro-feature reality (read this first)
The user's instinct that "dynamic tags are a Pro feature" is half-right, and the distinction matters:
- The API is in free Elementor —
\Elementor\Core\DynamicTags\Tag,Data_Tag,Base_Tag, theManager, theelementor/dynamic_tags/registerhook, and the category constants all live in the free plugin. Your tag classes compile and register without Pro. - Free Elementor registers zero tags of its own —
Module::get_tag_classes_names()returns[](modules/dynamic-tags/module.php:116-118) and only one group, "Base Tags" (module.php:130-136). - Pro ships ~34 tags plus 8 groups, every one gated behind a license check —
API::is_licence_has_feature( 'dynamic-tags', … )(elementor-pro/modules/dynamic-tags/module.php:97). - The AJAX query control (
QueryControlModule::QUERY_CONTROL_ID) used to pick from large datasets is Pro-only. Without Pro you fall back to a manual field — seeelementor-dynamic-tag-ajax-select.
Practical rule: build for "Elementor active, Pro likely present", feature-detect Pro-only pieces, and degrade gracefully. The reference plugin does exactly this with a has_query_control_support() check before using the Pro autocomplete control.
Misconception this skill corrects
"I'll register my tag with
add_action( 'elementor/dynamic_tags/register_tags', … )and$manager->register_tag( MyTag::class )like the old tutorials show."
Both still work — Elementor keeps deprecation shims — but both are deprecated since 3.5.0:
// DEPRECATED (3.5.0) — fires a _doing_it_wrong notice, still functional
add_action( 'elementor/dynamic_tags/register_tags', function ( $manager ) {
$manager->register_tag( '\MyPlugin\Tags\MyTag' ); // string class name
} );
// MODERN — register() takes an INSTANCE, not a class string
add_action( 'elementor/dynamic_tags/register', function ( $manager ) {
$manager->register( new \MyPlugin\Tags\MyTag() );
} );
Verified: the deprecated action is wrapped in do_deprecated_action( 'elementor/dynamic_tags/register_tags', …, '3.5.0', 'elementor/dynamic_tags/register' ) (manager.php:284-289); the modern action fires at manager.php:302. register_tag() is @deprecated 3.5.0 Use register() (manager.php:315); register() takes a Base_Tag instance (manager.php:337).
(The bundled reference plugin was migrated to the modern API on 2026-06-17 — TagManager.php:45,109 now hooks register and calls register( new $tag_class() ). Older copies and most tutorials still show the legacy pair, which is why it's worth recognising.)
When to use this skill
Trigger when ANY of the following is true:
- The diff hooks
elementor/dynamic_tags/registeror the legacyelementor/dynamic_tags/register_tags. - A class
extends \Elementor\Core\DynamicTags\Tag/Data_Tag/Base_Tag(or a ProPro_Tag/Pro_Data_Tag). - The user asks "how do I add a custom dynamic tag to Elementor", or wants to group tags in the editor's tag picker.
- Reviewing a companion plugin that adds dynamic content sources.
Workflow
1. Bootstrap — register the callback when Elementor is present
add_action( 'elementor/loaded', static function (): void {
add_action( 'elementor/dynamic_tags/register', [ \MyPlugin\Tags\Manager::class, 'register' ] );
} );
elementor/loaded fires once Elementor's core is available, so the \Elementor\Core\DynamicTags\* base classes are guaranteed loadable when your tag files autoload. The register action itself fires lazily, the first time Manager::get_tags() is called (manager.php:272-303) — well after load — so the extends in your tag class always resolves. Do not register tags before elementor/loaded.
2. Register groups, then tags
namespace MyPlugin\Tags;
use Elementor\Core\DynamicTags\Manager;
class Manager_Bootstrap {
public static function register( Manager $manager ): void {
// Groups first — a tag's get_group() must point at a registered slug.
$manager->register_group( 'myplugin', [
'title' => esc_html__( 'My Plugin', 'myplugin' ),
] );
$manager->register( new Post_Reading_Time() );
$manager->register( new Author_Twitter_Url() );
// Conditional on a dependency
if ( class_exists( '\WooCommerce' ) ) {
$manager->register( new Product_Shipping_Class() );
}
}
}
register_group( $group_name, array $group_settings ) merges your settings over a [ 'title' => '' ] default (manager.php:382-390). The slug is the key you return from each tag's get_group().
3. The minimal tag class
namespace MyPlugin\Tags;
use Elementor\Core\DynamicTags\Tag;
use Elementor\Modules\DynamicTags\Module as TagsModule;
class Post_Reading_Time extends Tag {
public function get_name(): string {
return 'myplugin-reading-time'; // unique, stable, namespace-prefixed
}
public function get_title(): string {
return esc_html__( 'Reading Time', 'myplugin' );
}
public function get_group(): string {
return 'myplugin'; // a slug you registered with register_group()
}
public function get_categories(): array {
return [ TagsModule::TEXT_CATEGORY ]; // which control types accept this tag
}
public function render(): void {
$words = str_word_count( wp_strip_all_tags( get_the_content() ) );
echo esc_html( max( 1, (int) ceil( $words / 200 ) ) . ' min' );
}
}
The four methods every tag must implement: get_name() (from Controls_Stack), get_title(), get_group(), get_categories() — the last three are abstract on Base_Tag (base-tag.php:35,42,53). Then render() for a Tag or get_value() for a Data_Tag (see elementor-dynamic-tag-fields).
4. Extend the FREE base classes (portability)
Extend \Elementor\Core\DynamicTags\Tag / Data_Tag, not the Pro bases (ElementorPro\Modules\DynamicTags\Tags\Base\Pro_Tag). The free bases load whenever Elementor is active; the Pro bases require Pro AND add a license check your tag does not need. The reference plugin extends the free bases throughout (e.g. ProductAttributes.php:10 extends Tag) and runs fine alongside Pro.
5. Unregister a tag (rare)
$manager->unregister( 'some-tag-name' ); // modern; unregister_tag() is deprecated 3.5.0
unregister() at manager.php:371; deprecated unregister_tag() at manager.php:351.
Critical rules
- Use the modern API —
elementor/dynamic_tags/register+register( new Tag() )(an instance). The_tagsaction andregister_tag( $class )(a string) are deprecated since 3.5.0 and emit notices. get_name()is the stable identifier. It keys the registry (manager.php:338) and is saved into every page that uses the tag. Renaming it orphans existing usages. Namespace-prefix it (myplugin-reading-time).- Register groups before (or alongside) tags. A
get_group()pointing at an unregistered slug leaves the tag ungrouped in the picker. - Bootstrap on
elementor/loaded, then add theregisteraction. Registering earlier risks the base class not being loadable when your tag file autoloads. - Extend the free base classes for portability; reach for Pro bases only when you specifically need their license-gated behavior.
- Feature-detect Pro-only pieces (the AJAX query control especially) and degrade — don't hard-require Pro unless your whole plugin does.
get_categories()must return registered category constants (TagsModule::TEXT_CATEGORY, etc.) — seeelementor-dynamic-tag-fields. A wrong/empty category means the tag won't appear under any control.
Common mistakes
// WRONG — passing a class string to register() (that was register_tag's signature)
$manager->register( '\MyPlugin\Tags\MyTag' ); // TypeError: expects Base_Tag instance
// RIGHT
$manager->register( new \MyPlugin\Tags\MyTag() );
// WRONG — tag points at a group nobody registered
public function get_group() { return 'my-cool-group'; } // never register_group()'d → ungrouped
// RIGHT — register the group in the same callback
$manager->register_group( 'my-cool-group', [ 'title' => esc_html__( 'My Cool Group', 'myplugin' ) ] );
// WRONG — generic name collides across plugins and Elementor core
public function get_name() { return 'reading-time'; }
// RIGHT — namespace-prefixed, stable
public function get_name() { return 'myplugin-reading-time'; }
// WRONG — registering before Elementor core is loaded
add_action( 'plugins_loaded', function () {
add_action( 'elementor/dynamic_tags/register', 'myplugin_register' ); // base class may not be loadable yet
} );
// RIGHT — gate on elementor/loaded
add_action( 'elementor/loaded', function () {
add_action( 'elementor/dynamic_tags/register', 'myplugin_register' );
} );
Cross-references
- Run
elementor-dynamic-tag-fieldsfor the body of the tag —TagvsData_Tag, the category constants,register_controls()field types, and the fallback system. - Run
elementor-dynamic-tag-ajax-selectwhen a tag (or widget) needs to pick from a large dataset (products, posts) without freezing the editor. - Run
wp-plugin-bootstrap/wp-plugin-architecturefor the companion-plugin scaffold and load-order patterns.
What this skill does NOT cover
- The controls/fields and fallback a tag declares — see
elementor-dynamic-tag-fields. - AJAX/search pickers for large option sets — see
elementor-dynamic-tag-ajax-select. - Editor-side UI internals (the React tag picker). Third-party plugins register tags; they don't extend the picker's rendering.
- Whether the dynamic switcher appears on a given control — that is per-control (
'dynamic' => [ 'active' => true ]) and, in practice, surfaced through Pro. This skill is about registering the tag, not enabling dynamic on someone else's control. - Atomic / v4 elements dynamic system beyond
get_atomic_group()(base-tag.php:44).
References
- Manager: wp-content/plugins/elementor/core/dynamic-tags/manager.php —
register()(337),register_tag()deprecated (315),register_group()(382),unregister()(371), the two actions inget_tags()(272-303, modern fired at 302). - Base tag: wp-content/plugins/elementor/core/dynamic-tags/base-tag.php —
abstract class Base_Tag extends Controls_Stack(19), abstractget_categories/get_group/get_title(35,42,53),get_atomic_group()(44). - Free module: wp-content/plugins/elementor/modules/dynamic-tags/module.php —
get_tag_classes_names()returns[](116-118),get_groups()"Base Tags" (130-136), hooks the modern action (89). - Pro module: wp-content/plugins/elementor-pro/modules/dynamic-tags/module.php — license gate (97), the 8 groups (144-171), tag list (98-134).
- Reference plugin (legacy API, working): wp-content/plugins/dynamic-elementor-extension-main/dynamic-tags/TagManager.php —
register_tagsaction (45),register_group()calls (58-83),register_tag()(109). - Official: https://developers.elementor.com/docs/dynamic-tags/
- Official documentation: https://developers.elementor.com/docs/dynamic-tags/register-tag/