Register custom dynamic tags so editors can insert placeholders like {store_phone} into Notifal templates (HTML Builder, Block Editor, Elementor widgets).
Notifal fires notifal/tag/register at the end of RegisterTags::register() and passes the shared TagManager instance. Your callback calls $manager->registerTag( new Tag( ... ) ).
Hook reference: Hooks: Tags and Dynamic Data.
Registration timing
TagManager is created lazily the first time Notifal resolves it from the container. The notifal/tag/register action runs during that first resolution, which typically happens during WordPress init priority 0.
Register your callback early, directly in your plugin bootstrap file or on plugins_loaded. Do not wait for notifal/initialized, because that action fires after modules boot and the tag registry may already be built.
Tag class overview
new Tag(
string $key, // Placeholder key without braces, e.g. "store_phone"
string $label, // Admin UI label
Closure $resolver, // function ( array $context, string $tagKey ): string
string $category, // TagCategory constant or custom category slug
string $description // Optional help text in tag picker
);
Resolver $context may include domain objects depending on the content source, for example:
$context['user'], user DTO when user context exists$context['product'], product DTO for product pools$context['order'], order DTO for order pools$context['post'],$context['page'], WordPress post objects on singular views$context['is_preview'], boolean, true in admin preview mode
Use preview fallbacks when is_preview is true so the tag picker shows sample output.
Example: static site-wide tag
<?php
/**
* Plugin Name: My Notifal Custom Tags
* Description: Registers custom dynamic tags for Notifal templates.
* Version: 1.0.0
* Requires PHP: 7.4
*/
defined( 'ABSPATH' ) || exit;
use Notifal\Domain\Tags\Tag;
use Notifal\Domain\Tags\TagManager;
use Notifal\Domain\Tags\Enums\TagCategory;
use Notifal\Infrastructure\WordPress\Hooks\ActionHooks;
/**
* Register custom tags when Notifal builds the tag registry.
*
* @param TagManager $manager Shared TagManager instance from Notifal core.
* @return void
*/
add_action( ActionHooks::TAG_REGISTER, 'my_notifal_register_custom_tags' );
/**
* Register one or more custom tags on the TagManager.
*
* @param TagManager $manager TagManager passed by Notifal during registration.
* @return void
*/
function my_notifal_register_custom_tags( TagManager $manager ): void {
// Register a simple tag that reads a site option.
$manager->registerTag(
new Tag(
'store_phone',
__( 'Store Phone Number', 'my-notifal-tags' ),
function ( array $context ): string {
// Read the option value from the database.
$phone = get_option( 'my_store_phone', '' );
// Return sanitized text safe for HTML output contexts.
return sanitize_text_field( (string) $phone );
},
TagCategory::GENERAL,
__( 'Displays the store phone number from site settings.', 'my-notifal-tags' )
)
);
}
Use {store_phone} in any Notifal template.
Example: dynamic meta-style tag
Notifal core uses patterns like {user_meta_first_name}. You can register a parameterized key with {key} in the tag definition:
<?php
defined( 'ABSPATH' ) || exit;
use Notifal\Domain\Tags\Tag;
use Notifal\Domain\Tags\TagManager;
use Notifal\Domain\Tags\Enums\TagCategory;
use Notifal\Infrastructure\WordPress\Hooks\ActionHooks;
add_action( ActionHooks::TAG_REGISTER, function ( TagManager $manager ): void {
$manager->registerTag(
new Tag(
'acme_option_{key}',
__( 'ACME Plugin Option', 'my-notifal-tags' ),
function ( array $context, string $tagKey ): string {
// Extract the suffix after acme_option_ from the matched tag key.
preg_match( '/acme_option_(.+)/', $tagKey, $matches );
$option_name = isset( $matches[1] ) ? sanitize_key( $matches[1] ) : '';
// Bail when the option name could not be parsed.
if ( $option_name === '' ) {
return '';
}
// Read and sanitize the option value.
$value = get_option( $option_name, '' );
// Provide a preview placeholder in the admin tag preview UI.
if ( $value === '' && ! empty( $context['is_preview'] ) ) {
return esc_html__( 'Sample Option Value', 'my-notifal-tags' );
}
return sanitize_text_field( (string) $value );
},
TagCategory::GENERAL,
__( 'Reads a WordPress option: {acme_option_my_setting}', 'my-notifal-tags' )
)
);
} );
Template usage: {acme_option_my_setting}.
Example: WooCommerce-aware product tag
<?php
defined( 'ABSPATH' ) || exit;
use Notifal\Domain\Tags\Tag;
use Notifal\Domain\Tags\TagManager;
use Notifal\Domain\Tags\Enums\TagCategory;
use Notifal\Infrastructure\WordPress\Hooks\ActionHooks;
add_action( ActionHooks::TAG_REGISTER, function ( TagManager $manager ): void {
$manager->registerTag(
new Tag(
'product_brand',
__( 'Product Brand', 'my-notifal-tags' ),
function ( array $context ): string {
// Require a product object in the render context.
if ( empty( $context['product'] ) || ! is_object( $context['product'] ) ) {
return '';
}
// Call getMeta when the product DTO exposes taxonomy or meta data.
if ( ! method_exists( $context['product'], 'getMeta' ) ) {
return '';
}
// Read brand meta (adjust meta key to match your store setup).
$brand = $context['product']->getMeta( 'brand' );
// Preview fallback for empty values in the template editor.
if ( ( $brand === '' || $brand === null ) && ! empty( $context['is_preview'] ) ) {
return esc_html__( 'Sample Brand', 'my-notifal-tags' );
}
return sanitize_text_field( (string) $brand );
},
TagCategory::PRODUCTS,
__( 'Displays the product brand meta field.', 'my-notifal-tags' )
)
);
} );
Categories and settings UI
Built-in categories live in TagCategory (products, orders, users, posts, pages, general, cart, etc.).
Add custom categories with the notifal/tag/categories filter if you need a dedicated group in Global Settings tag toggles.
Duplicate tag keys throw InvalidTagException. Prefix keys with your plugin slug, for example acme_store_phone.
After registration, Notifal fires notifal/tag/manager/on_registered with the Tag instance if you need cross-plugin bookkeeping.
Related hooks
| Hook | Purpose |
|---|---|
notifal/tag/register | Register tags on TagManager (this cookbook) |
notifal/tag/categories | Add tag category slugs |
notifal/tag/manager/on_registered | Fires after each successful registerTag() |
notifal/register_comment_tags | Pro comment tag extension point |
REST: tags are also exposed under notifal/v1 (see REST API Reference).
What to read next
Cookbook series
- Cookbook: Modify Notification Before Display
- Cookbook: Add Custom Dynamic Tag (you are here)
- Cookbook: Customize Template Import
- Cookbook: Extend Display Rules
- Cookbook: Register a Custom HTML Builder Widget