Overview
Zalbi Theme follows WordPress standards for extensibility through hooks (actions) and filters. This guide documents all available hooks for customizing theme behavior without modifying core files.
Understanding Hooks
Actions vs Filters
Actions allow you to insert custom code at specific points.// Execute code after theme setup
add_action( 'after_setup_theme', 'my_custom_function' );
function my_custom_function() {
// Your code here
}
Filters allow you to modify data before it’s used.// Modify content width
add_filter( 'zalbi_content_width', 'my_custom_width' );
function my_custom_width( $width ) {
return 1200; // Change from default 640
}
Theme Actions
after_setup_theme
Runs during theme initialization. Used for registering theme features.
File: functions.php:90
add_action( 'after_setup_theme', 'zalbi_setup' );
When to Use:
- Register navigation menus
- Add theme support features
- Load text domains
- Set image sizes
Example:
function my_theme_setup() {
// Add custom image size
add_image_size( 'product-thumb', 400, 400, true );
// Add custom menu location
register_nav_menus( array(
'footer-menu' => __( 'Footer Menu', 'zalbi' )
) );
}
add_action( 'after_setup_theme', 'my_theme_setup', 11 );
Priority 11 ensures it runs after Zalbi’s setup (priority 10).
Registers widget areas (sidebars).
File: functions.php:116
add_action( 'widgets_init', 'zalbi_widgets_init' );
Default Sidebar:
register_sidebar( array(
'name' => esc_html__( 'Sidebar', 'zalbi' ),
'id' => 'sidebar-1',
'description' => esc_html__( 'Add widgets here.', 'zalbi' ),
'before_widget' => '<section id="%1$s" class="widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title">',
'after_title' => '</h2>',
) );
Add Custom Sidebar:
function my_custom_sidebars() {
register_sidebar( array(
'name' => __( 'Footer Widgets', 'zalbi' ),
'id' => 'footer-widgets',
'description' => __( 'Widgets for footer area', 'zalbi' ),
'before_widget' => '<div class="footer-widget">',
'after_widget' => '</div>',
'before_title' => '<h3>',
'after_title' => '</h3>',
) );
}
add_action( 'widgets_init', 'my_custom_sidebars' );
wp_enqueue_scripts
Enqueues CSS and JavaScript files.
File: functions.php:135
add_action( 'wp_enqueue_scripts', 'zalbi_scripts' );
Default Assets:
function zalbi_scripts() {
// Main stylesheet
wp_enqueue_style( 'zalbi-style', get_stylesheet_uri(), array(), _S_VERSION );
// Navigation JS
wp_enqueue_script( 'zalbi-navigation', get_template_directory_uri() . '/js/navigation.js', array(), _S_VERSION, true );
// Font Awesome
wp_enqueue_style( 'zalbi-fontawesome', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css' );
// Google Fonts
wp_enqueue_style( 'zalbi-google-fonts', 'https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;900&family=Open+Sans:wght@400;600&display=swap' );
}
Add Custom Scripts:
function my_custom_scripts() {
// Custom JavaScript
wp_enqueue_script(
'my-scripts',
get_stylesheet_directory_uri() . '/js/custom.js',
array('jquery'),
'1.0.0',
true
);
// Custom CSS
wp_enqueue_style(
'my-styles',
get_stylesheet_directory_uri() . '/css/custom.css',
array('zalbi-style'),
'1.0.0'
);
}
add_action( 'wp_enqueue_scripts', 'my_custom_scripts' );
Always enqueue scripts/styles, never hardcode them in templates.
init
Runs after WordPress is fully loaded. Used for registering post types and taxonomies.
File: functions.php:150, 168, 183
Hinchables Post Type:
add_action( 'init', 'zalbi_register_hinchables' );
function zalbi_register_hinchables() {
$args = array(
'labels' => array(
'name' => 'Hinchables',
'singular_name' => 'Hinchable'
),
'public' => true,
'has_archive' => false,
'menu_icon' => 'dashicons-smiley',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
'rewrite' => array( 'slug' => 'catalogo' ),
);
register_post_type( 'hinchable', $args );
}
Eventos Post Type:
add_action( 'init', 'zalbi_register_eventos' );
function zalbi_register_eventos() {
$args = array(
'labels' => array(
'name' => 'Eventos',
'singular_name' => 'Evento'
),
'public' => true,
'has_archive' => false,
'menu_icon' => 'dashicons-calendar-alt',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
'rewrite' => array( 'slug' => 'eventos' ),
);
register_post_type( 'evento', $args );
}
Tipo Hinchable Taxonomy:
add_action( 'init', 'zalbi_register_taxonomies' );
function zalbi_register_taxonomies() {
register_taxonomy( 'tipo_hinchable', 'hinchable', array(
'labels' => array(
'name' => 'Tipos de Hinchable',
'singular_name' => 'Tipo de Hinchable',
),
'public' => true,
'hierarchical' => true,
'show_ui' => true,
'show_admin_column' => true,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'tipo' ),
) );
}
Extend with Custom Post Type:
function my_register_custom_types() {
register_post_type( 'service', array(
'labels' => array(
'name' => 'Services',
'singular_name' => 'Service'
),
'public' => true,
'supports' => array( 'title', 'editor', 'thumbnail' ),
'rewrite' => array( 'slug' => 'services' ),
) );
}
add_action( 'init', 'my_register_custom_types' );
customize_register
Registers customizer settings and controls.
File: functions.php:208
WhatsApp Button Customizer:
add_action('customize_register', 'zalbi_customize_whatsapp');
function zalbi_customize_whatsapp($wp_customize) {
// Add section
$wp_customize->add_section('zalbi_whatsapp_section', array(
'title' => __('Botón WhatsApp', 'zalbi'),
'description' => __('Configura aquí el número del botón flotante.', 'zalbi'),
'priority' => 120,
));
// Add setting
$wp_customize->add_setting('zalbi_whatsapp_number', array(
'default' => '',
'transport' => 'refresh',
'sanitize_callback' => 'sanitize_text_field',
));
// Add control
$wp_customize->add_control('zalbi_whatsapp_number', array(
'label' => __('Número de teléfono', 'zalbi'),
'section' => 'zalbi_whatsapp_section',
'type' => 'text',
'description' => 'Escribe el número con prefijo (ej: 34658887358).',
));
}
Retrieve Customizer Value:
$whatsapp_number = get_theme_mod( 'zalbi_whatsapp_number', '' );
if ( ! empty( $whatsapp_number ) ) {
echo '<a href="https://wa.me/' . esc_attr( $whatsapp_number ) . '">Contact</a>';
}
Add Custom Customizer Option:
function my_customizer_options( $wp_customize ) {
$wp_customize->add_section( 'my_section', array(
'title' => __( 'My Settings', 'zalbi' ),
'priority' => 130,
) );
$wp_customize->add_setting( 'my_color', array(
'default' => '#000000',
'transport' => 'refresh',
) );
$wp_customize->add_control( new WP_Customize_Color_Control(
$wp_customize,
'my_color',
array(
'label' => __( 'Primary Color', 'zalbi' ),
'section' => 'my_section',
)
) );
}
add_action( 'customize_register', 'my_customizer_options' );
wp_head
Outputs code in the <head> section.
File: inc/template-functions.php:37
add_action( 'wp_head', 'zalbi_pingback_header' );
function zalbi_pingback_header() {
if ( is_singular() && pings_open() ) {
printf( '<link rel="pingback" href="%s">', esc_url( get_bloginfo( 'pingback_url' ) ) );
}
}
Add Custom Code to Head:
function my_custom_head_code() {
?>
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
</script>
<?php
}
add_action( 'wp_head', 'my_custom_head_code' );
wp_body_open
Runs immediately after opening <body> tag.
File: inc/template-tags.php:163
if ( ! function_exists( 'wp_body_open' ) ) :
function wp_body_open() {
do_action( 'wp_body_open' );
}
endif;
Usage:
function my_body_content() {
echo '<div class="announcement-bar">Special Offer!</div>';
}
add_action( 'wp_body_open', 'my_body_content' );
Theme Filters
zalbi_custom_background_args
Modifies custom background feature defaults.
File: functions.php:66
add_theme_support(
'custom-background',
apply_filters(
'zalbi_custom_background_args',
array(
'default-color' => 'ffffff',
'default-image' => '',
)
)
);
Customize:
function my_background_args( $args ) {
$args['default-color'] = 'f0f0f0';
$args['default-image'] = get_template_directory_uri() . '/img/bg.jpg';
return $args;
}
add_filter( 'zalbi_custom_background_args', 'my_background_args' );
zalbi_content_width
Sets maximum content width in pixels.
File: functions.php:96
function zalbi_content_width() {
$GLOBALS['content_width'] = apply_filters( 'zalbi_content_width', 640 );
}
add_action( 'after_setup_theme', 'zalbi_content_width', 0 );
Customize:
function my_content_width( $width ) {
return 1200; // Wider content area
}
add_filter( 'zalbi_content_width', 'my_content_width' );
Content width affects oEmbed dimensions and image sizes.
body_class
Adds custom CSS classes to <body> tag.
File: inc/template-functions.php:27
add_filter( 'body_class', 'zalbi_body_classes' );
function zalbi_body_classes( $classes ) {
if ( ! is_singular() ) {
$classes[] = 'hfeed';
}
if ( ! is_active_sidebar( 'sidebar-1' ) ) {
$classes[] = 'no-sidebar';
}
return $classes;
}
Add Custom Classes:
function my_body_classes( $classes ) {
// Add language class
if ( function_exists( 'pll_current_language' ) ) {
$classes[] = 'lang-' . pll_current_language();
}
// Add custom post type class
if ( is_singular( 'hinchable' ) ) {
$classes[] = 'single-product-page';
}
return $classes;
}
add_filter( 'body_class', 'my_body_classes' );
WordPress Core Filters
excerpt_length
Controls excerpt word count.
function zalbi_excerpt_length( $length ) {
return 20; // Default is 55
}
add_filter( 'excerpt_length', 'zalbi_excerpt_length', 999 );
excerpt_more
Customizes excerpt “read more” text.
function zalbi_excerpt_more( $more ) {
if ( is_admin() ) {
return $more;
}
return '... <a href="' . get_permalink() . '">' . __( 'Continue Reading', 'zalbi' ) . '</a>';
}
add_filter( 'excerpt_more', 'zalbi_excerpt_more' );
the_content
Modifies post content before display.
function my_custom_content( $content ) {
if ( is_singular( 'hinchable' ) ) {
$content .= '<p class="disclaimer">Contact us for availability.</p>';
}
return $content;
}
add_filter( 'the_content', 'my_custom_content' );
Adds classes to navigation menu items.
function my_menu_classes( $classes, $item, $args ) {
// Add icon to specific menu item
if ( $item->title === 'Contact' ) {
$classes[] = 'menu-item-icon';
}
return $classes;
}
add_filter( 'nav_menu_css_class', 'my_menu_classes', 10, 3 );
ACF Integration Hooks
acf/load_value
Modifies ACF field values when loaded.
function my_modify_acf_value( $value, $post_id, $field ) {
// Add prefix to medidas field
if ( ! empty( $value ) ) {
$value = 'Size: ' . $value;
}
return $value;
}
add_filter( 'acf/load_value/name=medidas', 'my_modify_acf_value', 10, 3 );
acf/save_post
Runs when ACF saves post data.
function my_acf_save_post( $post_id ) {
// Auto-generate excerpt from description
if ( get_post_type( $post_id ) === 'hinchable' ) {
$description = get_field( 'descripcion', $post_id );
if ( $description && empty( get_post_field( 'post_excerpt', $post_id ) ) ) {
wp_update_post( array(
'ID' => $post_id,
'post_excerpt' => wp_trim_words( $description, 20 ),
) );
}
}
}
add_action( 'acf/save_post', 'my_acf_save_post', 20 );
Polylang Hooks
pll_the_languages
Customizes language switcher output.
function my_language_switcher() {
if ( function_exists( 'pll_the_languages' ) ) {
pll_the_languages( array(
'dropdown' => 0,
'show_names' => 1,
'show_flags' => 1,
'hide_if_empty' => 0,
'hide_current' => 0,
'force_home' => 0,
'display_names_as' => 'slug',
) );
}
}
Hook Priority
Hook priority determines execution order:
// Runs first (priority 1)
add_action( 'init', 'my_early_function', 1 );
// Runs default (priority 10)
add_action( 'init', 'my_normal_function' );
// Runs last (priority 99)
add_action( 'init', 'my_late_function', 99 );
Lower numbers run first. Default priority is 10.
Best Practices
Plugin vs Theme
Naming
Performance
Use Plugins for:
- Business logic
- Custom post types (if reusable)
- API integrations
- Feature toggles
Use Theme Hooks for:
- Presentation changes
- Theme-specific features
- Visual customizations
- Layout modifications
- Prefix functions:
zalbi_ or my_theme_
- Use descriptive names:
my_theme_add_event_meta
- Avoid generic names:
custom_function
- Only hook what you need
- Use appropriate priorities
- Remove unused hooks
- Cache expensive operations
Common Hook Combinations
Custom Post Type with Meta Box
// Register post type
function my_register_cpt() {
register_post_type( 'portfolio', array(
'public' => true,
'supports' => array( 'title', 'editor', 'thumbnail' ),
) );
}
add_action( 'init', 'my_register_cpt' );
// Add meta box
function my_add_meta_box() {
add_meta_box(
'portfolio_details',
'Portfolio Details',
'my_meta_box_callback',
'portfolio'
);
}
add_action( 'add_meta_boxes', 'my_add_meta_box' );
// Save meta
function my_save_meta( $post_id ) {
if ( isset( $_POST['client_name'] ) ) {
update_post_meta( $post_id, 'client_name', sanitize_text_field( $_POST['client_name'] ) );
}
}
add_action( 'save_post_portfolio', 'my_save_meta' );
Custom Archive Query
function my_pre_get_posts( $query ) {
if ( ! is_admin() && $query->is_main_query() && is_post_type_archive( 'hinchable' ) ) {
$query->set( 'posts_per_page', 12 );
$query->set( 'orderby', 'title' );
$query->set( 'order', 'ASC' );
}
}
add_action( 'pre_get_posts', 'my_pre_get_posts' );