Skip to main content

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
}

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).

widgets_init

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' );

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

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

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' );

Build docs developers (and LLMs) love