WordPress Plugin Hooks
Every public filter and action the Sesamy WordPress plugin exposes, what it receives, when it fires, and how to use it.
The plugin ships a small set of hooks so you can change lock behaviour, customise paywall markup, and boot dependent code at the right moment without forking the plugin. Everything on this page is verified against plugin version 1.13.0 (sesamyab/wordpress-sesamy-2).
Reference
| Hook | Type | Signature | Since |
|---|---|---|---|
sesamy_plugin_loaded | Action | none | 1.3.0 |
sesamy_plugin_init | Action | none | 1.3.0 |
sesamy_plugin_init_priority | Filter | int $priority | 1.3.0 |
sesamy_is_post_locked | Filter | bool $locked, int $post_id | 1.5.0 |
sesamy_paywall_preview | Filter | string $preview_html | 1.3.0 |
sesamy_paywall | Filter | string $paywall_html | 1.3.0 |
sesamy_article_html | Filter | string $html, WP_Post $post, string $content | 1.11.0 |
These are the supported hooks, and their signatures are covered by the promise in Stability.
Where to put your code
Hooks that fire while the Sesamy plugin file loads (sesamy_plugin_loaded and sesamy_plugin_init_priority) are already spent by the time themes and later-loading plugins run, so they only work from a must-use plugin in wp-content/mu-plugins/ or a plugin whose folder sorts before sesamy2. The rest can live in your child theme's functions.php.
Keep output reader-independent
The plugin renders byte-identical HTML for every reader and resolves entitlements client-side, which is what keeps pages fully cacheable. If your callback varies its output by logged-in user, session, or cookie, you will serve one reader's markup to everyone behind a page cache. Vary on the post, not on the visitor.
Actions
sesamy_plugin_loaded
do_action( 'sesamy_plugin_loaded' );Fires from PluginCore::setup(), which runs while the Sesamy plugin file itself loads, immediately after the plugin registers its init handler and before that handler runs. It signals that the plugin is present and about to boot.
Only earlier-loading code can catch this
WordPress loads plugins in order, and this action has already fired by the time anything loading after sesamy2 runs. To receive it, register your callback from a must-use plugin in wp-content/mu-plugins/, or from a plugin whose folder sorts before sesamy2. A theme's functions.php is far too late.
It is also too late to change the boot priority here: sesamy_plugin_init_priority is evaluated one line earlier in setup(), before this action fires. For dependent code that loads after Sesamy, use sesamy_plugin_init instead, which fires later on WordPress's init.
sesamy_plugin_init
do_action( 'sesamy_plugin_init' );Fires from PluginCore::init() on the WordPress init action, before any of the plugin's own classes are initialised. Settings, REST routes, the content container, and the proxy router all come up after this point, so it is the right place to boot code that depends on Sesamy being present but must run before Sesamy registers anything.
add_action( 'sesamy_plugin_init', function () {
// The Sesamy plugin is active and about to initialise.
require_once __DIR__ . '/acme-sesamy-bridge.php';
Acme_Sesamy_Bridge::init();
} );It also fires on activation
PluginCore::activate() calls init() directly so rewrite rules can be registered and flushed, which means this action fires once during plugin activation as well, outside a normal init request. Keep the callback safe to run in that context, and do not assume the rest of a page request is set up around it.
Filters
sesamy_plugin_init_priority
apply_filters( 'sesamy_plugin_init_priority', int $priority );| Argument | Type | Description |
|---|---|---|
$priority | int | The init priority the plugin boots on. Default 8. |
Returns: int, the priority to use.
The default of 8 puts Sesamy ahead of most plugins that register on init at the default priority of 10. Raise it if your own code registers taxonomies or post types that Sesamy needs to see, and lower it if something must run before Sesamy.
add_filter( 'sesamy_plugin_init_priority', function ( $priority ) {
// Boot Sesamy after ACME registers its custom post types on init.
return 12;
} );Register this early
The filter is read once, inside PluginCore::setup(), while the Sesamy plugin file loads. A callback added later, for example from a theme, is never consulted. Add it from a must-use plugin, or from a plugin whose folder sorts before sesamy2.
sesamy_is_post_locked
apply_filters( 'sesamy_is_post_locked', bool $locked, int $post_id );| Argument | Type | Description |
|---|---|---|
$locked | bool | Whether the plugin considers the post locked, from the per-post toggle or an Automatic locking term rule. |
$post_id | int | The post being checked. |
Returns: bool, the effective lock state. The value is cast with (bool).
The single source of truth for lock state. It runs inside is_post_locked() and its result drives frontend rendering in every lock mode, the <head> meta tags, the REST endpoint, and the admin UI, so a post you lock here behaves exactly like one locked from the editor. Posts that are locked by a filter show as Locked (filter) in the post list column, which distinguishes them from Locked (per-post toggle) and Locked (Category: Premium) (term rule).
Use it for bespoke rules the settings screen cannot express, such as locking by author, by a legacy membership plugin's post meta, or by publication date.
add_filter( 'sesamy_is_post_locked', function ( $locked, $post_id ) {
// Already locked by the editor or a term rule, nothing to add.
if ( $locked ) {
return true;
}
// Carry over locks from a legacy membership plugin during migration.
if ( get_post_meta( $post_id, '_acme_members_only', true ) ) {
return true;
}
// Lock everything in the ACME Premium section older than 30 days.
$post = get_post( $post_id );
if ( $post && has_category( 'acme-premium', $post ) ) {
return strtotime( $post->post_date_gmt ) < strtotime( '-30 days' );
}
return $locked;
}, 10, 2 );Keep the callback cheap
is_post_locked() is called repeatedly during a request and once per row when the post list renders, so an uncached query or remote request here shows up directly in admin load times. Read post meta, cache anything heavier, and return early when $locked is already true.
sesamy_paywall_preview
apply_filters( 'sesamy_paywall_preview', string $preview_html );| Argument | Type | Description |
|---|---|---|
$preview_html | string | The free preview markup. Everything before the <!-- more --> tag when the post has one and there is content on both sides of it, otherwise the excerpt wrapped in a <p>. |
Returns: string, the preview markup to render.
Fires from ContentContainer::process_content() for every singular page of an enabled post type, locked or not. The result goes into the preview slot of <sesamy-content-container>, or into the visible wrapper in Capsule mode. It is the free part of the article, so it stays in the HTML for search engines and social crawlers.
add_filter( 'sesamy_paywall_preview', function ( $preview ) {
$post_id = get_the_ID();
if ( ! $post_id ) {
return $preview;
}
// Only add the teaser on posts that actually sit behind the paywall.
if ( ! \SesamyPlugin\Helpers\is_post_locked( $post_id ) ) {
return $preview;
}
return $preview . '<p class="acme-preview-note">Continue reading with ACME Plus.</p>';
} );The filter passes only the markup, so read the current post from get_the_ID() or the global $post if you need context. \SesamyPlugin\Helpers\is_post_locked() is the same function that fires sesamy_is_post_locked, so it returns the effective lock state including anything your own filters add. Do not call it from inside a sesamy_is_post_locked callback, which would recurse.
sesamy_paywall
apply_filters( 'sesamy_paywall', string $paywall_html );| Argument | Type | Description |
|---|---|---|
$paywall_html | string | The <sesamy-paywall settings-url="…"></sesamy-paywall> element, or an empty string. |
Returns: string, the paywall markup. Appended inside <article class="sesamy-article"> only when the post is locked.
The string is empty when no paywall is configured for the site or the post, or when the post has a locked-content redirect URL, in which case the reader is redirected instead of being shown a paywall. Guard on that, otherwise you will render your additions on posts that were meant to have no paywall at all.
The element is rendered as an explicit open and close pair so you can insert slot content between the tags. See <sesamy-paywall> for the slots the component accepts.
add_filter( 'sesamy_paywall', function ( $paywall ) {
// No paywall configured, or the post redirects instead. Leave it alone.
if ( '' === $paywall ) {
return $paywall;
}
$slot = '<div slot="below-headline">Special offer for new readers</div>';
return str_replace( '</sesamy-paywall>', $slot . '</sesamy-paywall>', $paywall );
} );Prefer paywall settings for copy changes
Headlines, button labels, and offer copy are configured per paywall in the Sesamy portal and delivered through settings-url, so they can be changed without a deploy. Reach for this filter when you need markup the paywall settings cannot produce.
sesamy_article_html
apply_filters( 'sesamy_article_html', string $html, WP_Post $post, string $content );| Argument | Type | Description |
|---|---|---|
$html | string | The rendered article container: <article class="sesamy-article" item-src="…" publisher-content-id="…"> holding the preview, the gated content in the configured lock mode, and the paywall when the post is locked. |
$post | WP_Post | The post being rendered. |
$content | string | The post content as the_content handed it to the plugin, before any wrapping. |
Returns: string, the markup the_content outputs.
Fires from ContentContainer::apply_content_filter() on the the_content filter for singular main-query pages of an enabled post type. It is the last step in the plugin's rendering, after sesamy_paywall_preview and sesamy_paywall have run, so $html is the complete container. Use it to add markup before or after the container, wrap it in theme structure, or swap the output for a particular post.
add_filter( 'sesamy_article_html', function ( $html, $post, $content ) {
// Only badge posts that sit behind the paywall.
if ( ! \SesamyPlugin\Helpers\is_post_locked( $post->ID ) ) {
return $html;
}
return '<p class="acme-premium-badge">ACME Premium</p>' . $html;
}, 10, 3 );Keep the container intact
The Sesamy bundle relies on the <article class="sesamy-article"> element and the lock-mode markup inside it to identify and unlock the article. Add around the container or inside it rather than rewriting it, and never touch the encoded or encrypted content.
Replaces the v1 sesamy_content filter
The v1 plugin (sesamyab/wordpress-sesamy) documented a sesamy_content filter that received ( $post, $content ). From 1.11.0 this plugin no longer applies that filter, so a callback hooked to it never runs and nothing warns you. Move the callback to sesamy_article_html and note the argument order: the rendered markup comes first, then the post, then the original content.
Stability
Every hook on this page keeps its name, arguments, and firing point across minor releases. Breaking changes go in a major version with a migration note in the release.
The plugin defines other internal hooks that are not listed here. They exist to wire the plugin's own classes together, carry no stability promise, and can change or disappear in any release, so treat anything absent from the reference table as private.
If you need a hook that does not exist yet, open an issue on GitHub describing what you are trying to build. Adding a documented hook is usually cheaper for everyone than maintaining a fork.
Next Steps
- WordPress Integration for installation and settings
- Content Protection for how lock modes and Capsule encryption work
- Paywall Strategies for choosing how much content to give away
- Content Meta Tags for the metadata the plugin emits per post