Bisteinoff templates are hook-driven. header.php, footer.php, and the content templates call do_action( 'bisteinoff_…' ) instead of printing logos, menus, titles, and contact blocks directly. The parent theme then attaches its own callbacks to those actions.
That is the supported way to extend the theme from a child theme or a small custom plugin: you add your own callbacks, and you filter data the theme already builds. You do not edit parent theme files.
This page explains how to hook in. The catalogues are separate:
- Action Hooks Reference — every
do_action( 'bisteinoff_*' ) - Filter Hooks Reference — every
apply_filters( 'bisteinoff_*' )
Actions and filters
Actions run at a place in the markup. Use them to print extra HTML, or to replace output by removing the default callback and adding yours.
Filters receive a value, let you change it, and must return it. Use them for lists (modals, icon sets, social networks), strings (canonical URL, CSS classes), and admin field maps.
Both use the WordPress APIs you already know: add_action(), add_filter(), remove_action(), remove_all_actions().
Where to put the code
In the child theme’s functions.php. That file loads before the parent theme, so your add_action() / add_filter() calls are registered in time for the front end.
Keep the file small: one include per feature is easier to maintain than a single long functions.php.
<?php
/**
* Child theme functions.php
*/
defined( 'ABSPATH' ) || exit;
add_action( 'bisteinoff_after_page_content', 'mytheme_after_page_notice' );
function mytheme_after_page_notice(): void {
echo '<p class="bst-page-note">' . esc_html__( 'Need help? Contact us.', 'my-child' ) . '</p>';
}Add markup on a layout hook
Most content templates fire empty (or almost empty) wrappers such as bisteinoff_before_page_content and bisteinoff_after_page_content. Nothing in the parent theme is required to run there, so adding a callback is enough.
Priority 10 is the default. Use a lower number to run earlier, a higher number to run later. The theme’s own Bootstrap wrappers on page and front-page loops use priority 0 (open) and 100 (close).
// Banner above the page header (title + breadcrumbs).
add_action( 'bisteinoff_before_page_content', function (): void {
echo '<div class="container-fluid"><p class="bst-promo">' . esc_html__( 'Free shipping this week.', 'my-child' ) . '</p></div>';
}, 5 );
// Extra block after the main page loop, still inside <main>.
add_action( 'bisteinoff_after_page_loop', function (): void {
get_template_part( 'template-parts/page', 'cta' );
}, 20 );Print a component the theme already knows
Several actions are the public API for theme components. You can fire them from a child template, or attach extra output next to them. Common ones:
do_action( 'bisteinoff_logo', 'header' )or'footer'do_action( 'bisteinoff_nav', 'horizontal' )— also'hamburger','woo-cart','copyright',[ 'footer', '1' ]…'4','languages','footer_languages'do_action( 'bisteinoff_cta', 'horizontal' )or'hamburger'do_action( 'bisteinoff_contact', 'header' )— also'footer','hamburger'do_action( 'bisteinoff_title' )do_action( 'bisteinoff_breadcrumbs' )do_action( 'bisteinoff_ad', 1 )— slot number, 1-based. The parent templates do not call this; you call it from a child template or from another layout hook.
Example: show ad slot 1 under the page header.
add_action( 'bisteinoff_after_page_header', function (): void {
echo '<div class="container-fluid bst-ad-slot">';
do_action( 'bisteinoff_ad', 1 );
echo '</div>';
} );Replace default output
Parent callbacks are methods on internal objects ([ $this, 'render_logo' ] and similar). You cannot pass that object to remove_action() from a child theme. To replace output:
- Prefer a filter when one exists (canonical URL, breadcrumb items, CTA classes, modals, icon sets).
- Copy the template into the child theme (
header.php,footer.php,page.php, or a file undertemplate-parts/) and change thedo_action()calls. WordPress will use the child copy. - Clear every callback on that hook, then add yours. This is blunt: it also removes other plugins hooked to the same name. Use it only when you own the site.
// Replace the footer copyright line entirely.
add_action( 'wp', function (): void {
remove_all_actions( 'bisteinoff_copyright' );
add_action( 'bisteinoff_copyright', 'mytheme_copyright' );
} );
function mytheme_copyright(): void {
echo '<p class="bst-copyright">© ' . esc_html( gmdate( 'Y' ) ) . ' ' . esc_html( get_bloginfo( 'name' ) ) . '</p>';
}Hook remove_all_actions() on wp (or later), not at the top of functions.php. The parent theme registers its callbacks while it boots, which happens after the child functions.php has already run.
Change data with a filter
Filters always receive the current value as the first argument and must return the (possibly modified) value. Extra arguments are documented on the Filter Hooks Reference.
Add a social network to Website Settings and to the shortcode:
add_filter( 'bisteinoff_socials_config', function ( array $socials ): array {
$socials['mastodon'] = [
'name' => __( 'Mastodon', 'my-child' ),
'icon' => [
'label' => __( 'Mastodon Icon', 'my-child' ),
'description' => __( 'Add the social media icon.', 'my-child' ),
],
'link' => [
'label' => __( 'Mastodon Link', 'my-child' ),
'description' => __( 'Profile URL', 'my-child' ),
],
'shortcode' => [
'id' => 'mastodon',
'title' => 'Mastodon',
],
];
return $socials;
} );Register a custom page type so Website Settings can assign a “Quotes” page, and so the theme loads quotes-page.php from the child theme:
add_filter( 'bisteinoff_custom_pages', function ( array $pages ): array {
$pages['quotes'] = __( 'Quotes Page', 'my-child' );
return $pages;
} );Place quotes-page.php in the child theme root (same pattern as contact-page.php in the parent). Then assign the page under Website Settings.
Admin: extra fields and metabox chrome
Settings tabs expose two stable filter names built from the tab file name (without .php) and the panel suffix:
bisteinoff_admin_{tab}_{suffix}_settings and bisteinoff_admin_{tab}_{suffix}_widgets.
Website Settings uses suffix panel. Design Settings uses design_panel. Example: add a field on the Posts tab of Website Settings (posts.php + panel):
add_filter( 'bisteinoff_admin_posts_panel_settings', function ( array $settings, string $tab_id ): array {
$settings['my_related_heading'] = [
'label' => __( 'Related articles heading', 'my-child' ),
'type' => 'text',
'tab' => $tab_id,
];
return $settings;
}, 10, 2 );Post metaboxes fire bisteinoff_{name}_metabox_fields (for example bisteinoff_seo_metabox_fields) and also the generic bisteinoff_metabox_fields. Around each metabox the theme fires bisteinoff_admin_{id}_metabox_before and …_after, where {id} is the metabox id (bst_seo, bst_page, bst_contact, …).
Override a template part instead of a hook
Some output lives in template-parts/ (hamburger menu, cards, related articles). Copy the file into the child theme at the same relative path. WordPress locate_template() will load the child copy. That is the right approach when you need different markup, not an extra hook.
Navigation template parts are included from inside the Nav class, so $this is the Nav instance. Keep that in mind if you copy template-parts/nav-hamburger.php or nav-horizontal.php.
Hooks versus constants
Use a constant when the decision is site-wide and must exist before the theme boots (multilingual on/off, number of color slots, hide the SEO panel).
Use a hook when the decision depends on the current request or when you are changing markup or arrays the theme already built.
Naming and prefixes
Every public theme hook starts with bisteinoff_. Prefix your own callback functions and hook names with your child theme slug (mytheme_) so they do not collide with WordPress or with the parent theme.
Escape everything you print: esc_html(), esc_attr(), esc_url(), wp_kses_post().