Hooks and filters

Every extension point Stoat exposes, what each one is for, and a working example of each.

Stoat’s hooks are few and deliberate. Each exists because something needed to be added without forking a template.

Actions

stoat_header_buttons

Extra links at the end of the header bar — a sign-in link, an account link, a newsletter opener. Fired twice per page with a context argument: 'bar' in the header, 'modal' in the mobile drawer, so a callback can dress the two differently.

add_action( 'stoat_header_buttons', function ( $context ) {
    $class = 'modal' === $context ? 'offcanvas-link' : 'header-link';
    printf(
        '<a class="%s" href="%s">%s</a>',
        esc_attr( $class ),
        esc_url( wp_login_url() ),
        esc_html__( 'Sign in', 'my-child' )
    );
} );

It fires before the header button, so a plain link reads as navigation and the call-to-action keeps the last slot.

This is how OwlDraft Publisher adds its Sign in / Account link.

stoat_entry_flag

Mark an entry in a listing — a members or paid chip, an Updated badge, a sponsor label. Fired once per row with the post ID and a context: 'thumbnail' when the row has a picture to mark, 'text' when it does not.

add_action( 'stoat_entry_flag', function ( $post_id, $context ) {
    if ( ! get_post_meta( $post_id, '_sponsored', true ) ) {
        return;
    }
    echo '<span class="entry-flag entry-flag--' . esc_attr( $context ) . '">Sponsored</span>';
}, 10, 2 );

Filters

stoat_header_button_signed_in_actions

Add an answer to When the reader is signed in. Whatever you add, you must also decide on stoat_header_button_visible — the theme cannot judge a state it does not know about.

add_filter( 'stoat_header_button_signed_in_actions', function ( $actions ) {
    $actions['hide_paid'] = __( 'Hide for paying members', 'my-child' );
    return $actions;
} );

stoat_header_button_visible

Whether the header button renders at all, after the signed-in rule has run. Receives the boolean and the chosen action.

add_filter( 'stoat_header_button_visible', function ( $visible, $action ) {
    if ( 'hide_paid' === $action && my_user_has_paid() ) {
        return false;
    }
    return $visible;
}, 10, 2 );

stoat_subtitle_post_types

Which post types get the Subtitle field. Defaults to array( 'post' ).

add_filter( 'stoat_subtitle_post_types', function ( $types ) {
    $types[] = 'page';
    return $types;
} );

Which taxonomies get the per-term Feature the first post field. Defaults to array( 'category', 'post_tag' ).

stoat_social_icons

The brand marks themselves — slug => <svg>. Add a slug here and, unless its domain is {slug}.com, a matching entry in stoat_social_icons_map.

stoat_social_icons_map

slug => array of domains, for detecting a link’s network from its URL.

add_filter( 'stoat_social_icons_map', function ( $map ) {
    $map['mastodon'][] = 'social.example.org';
    return $map;
} );

That one line teaches Stoat your own Mastodon instance. See social icons.

stoat_modals

The whole dialog registry, so you can change one the theme itself shipped rather than only adding your own.

Redeclaring a function

Every function in inc/ is wrapped in if ( ! function_exists() ), and a child theme’s functions.php loads before the parent’s. Declaring the same name in your child theme replaces the parent’s version entirely.

It is a real escape hatch and it is there on purpose. It is also a commitment: your copy stops receiving the parent’s fixes. Use a hook where one exists.

Last updated September 13, 2026

Something missing or out of date? Ask us and we will fix the page.