🛠️ Custom Functions & Helpers
Build reusable utilities and custom functionality
Master creating efficient, maintainable theme functions
Learning Objectives
- Create custom helper functions for common tasks
- Build utility functions for theme customization
- Implement custom template tags
- Create AJAX handlers for dynamic functionality
- Build custom shortcodes
- Organize functions for maintainability
- Implement security best practices
- Create reusable code snippets
Why Custom Functions?
Custom functions extend WordPress functionality, reduce code repetition, and make your theme more maintainable. They encapsulate complex logic into reusable components.
Key Principle
Content Helpers
Functions for excerpts, read more, content formatting
Image Utilities
Custom image sizes, placeholders, responsive images
Query Helpers
Custom queries, related posts, popular posts
User Functions
User avatars, author info, login checks
Analytics
View counts, reading time, post statistics
Theme Options
Custom settings, theme mods, options
Content Helper Functions
Custom Excerpt Functions
<?php
/**
* Custom excerpt length
*/
function mytheme_excerpt_length( $length ) {
if ( is_home() ) {
return 20;
} elseif ( is_archive() ) {
return 30;
}
return $length;
}
add_filter( 'excerpt_length', 'mytheme_excerpt_length', 999 );
/**
* Custom excerpt more string
*/
function mytheme_excerpt_more( $more ) {
return '...';
}
add_filter( 'excerpt_more', 'mytheme_excerpt_more' );
/**
* Get custom excerpt with specific word count
*/
function mytheme_get_excerpt( $limit = 20, $source = null ) {
$excerpt = $source ? $source : get_the_excerpt();
$excerpt = preg_replace( ' (\[.*?\])', '', $excerpt );
$excerpt = strip_shortcodes( $excerpt );
$excerpt = strip_tags( $excerpt );
$excerpt = substr( $excerpt, 0, $limit );
$excerpt = substr( $excerpt, 0, strripos( $excerpt, " " ) );
$excerpt = trim( preg_replace( '/\s+/', ' ', $excerpt ) );
$excerpt = $excerpt . '...';
return $excerpt;
}
/**
* Estimated reading time
*/
function mytheme_reading_time() {
$content = get_post_field( 'post_content', get_the_ID() );
$word_count = str_word_count( strip_tags( $content ) );
$reading_time = ceil( $word_count / 200 ); // Average reading speed
if ( $reading_time == 1 ) {
$timer = " minute";
} else {
$timer = " minutes";
}
return $reading_time . $timer;
}
/**
* Display reading time
*/
function mytheme_the_reading_time() {
echo '<span class="reading-time">';
echo '<svg class="icon">...</svg> ';
echo mytheme_reading_time() . ' read';
echo '</span>';
}
Content Formatting Helpers
<?php
/**
* Add custom classes to body tag
*/
function mytheme_body_classes( $classes ) {
// Add page slug
if ( is_page() ) {
global $post;
$classes[] = 'page-' . $post->post_name;
}
// Add user state
if ( is_user_logged_in() ) {
$classes[] = 'logged-in-user';
} else {
$classes[] = 'logged-out-user';
}
// Add sidebar status
if ( is_active_sidebar( 'sidebar-1' ) ) {
$classes[] = 'has-sidebar';
} else {
$classes[] = 'no-sidebar';
}
return $classes;
}
add_filter( 'body_class', 'mytheme_body_classes' );
/**
* Custom "Read More" link
*/
function mytheme_read_more_link( $text = 'Read More' ) {
return '<a class="read-more-link" href="' . get_permalink() . '">' .
$text . ' <span class="screen-reader-text">about ' .
get_the_title() . '</span></a>';
}
/**
* Truncate text with preservation of words
*/
function mytheme_truncate( $text, $length = 100, $ending = '...' ) {
if ( strlen( $text ) <= $length ) {
return $text;
}
$text = substr( $text, 0, $length );
$text = substr( $text, 0, strrpos( $text, ' ' ) );
$text = $text . $ending;
return $text;
}
Image & Media Helpers
Image Utility Functions
<?php
/**
* Get featured image URL
*/
function mytheme_get_featured_image_url( $size = 'full', $post_id = null ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
if ( has_post_thumbnail( $post_id ) ) {
$image = wp_get_attachment_image_src( get_post_thumbnail_id( $post_id ), $size );
return $image[0];
}
// Return placeholder
return get_template_directory_uri() . '/assets/images/placeholder.jpg';
}
/**
* Display responsive image
*/
function mytheme_responsive_image( $attachment_id, $sizes = array() ) {
$default_sizes = array(
'small' => 480,
'medium' => 768,
'large' => 1200,
);
$sizes = wp_parse_args( $sizes, $default_sizes );
$srcset = array();
foreach ( $sizes as $name => $width ) {
$image = wp_get_attachment_image_src( $attachment_id, array( $width, 9999 ) );
if ( $image ) {
$srcset[] = $image[0] . ' ' . $width . 'w';
}
}
$src = wp_get_attachment_image_url( $attachment_id, 'full' );
$alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
echo '<img src="' . esc_url( $src ) . '" ';
echo 'srcset="' . esc_attr( implode( ', ', $srcset ) ) . '" ';
echo 'sizes="(max-width: 480px) 100vw, (max-width: 768px) 50vw, 33vw" ';
echo 'alt="' . esc_attr( $alt ) . '">';
}
/**
* Get first image from post content
*/
function mytheme_get_first_image() {
global $post;
$first_img = '';
preg_match_all( '/<img.+src=[\'"]([^\'"]+)[\'"].*>/i', $post->post_content, $matches );
if ( isset( $matches[1][0] ) ) {
$first_img = $matches[1][0];
}
if ( empty( $first_img ) ) {
$first_img = get_template_directory_uri() . '/assets/images/default.jpg';
}
return $first_img;
}
/**
* Lazy load images
*/
function mytheme_lazy_load_images( $content ) {
if ( is_feed() || is_preview() ) {
return $content;
}
$content = preg_replace( '/(<img.*?)src=/i', '$1loading="lazy" src=', $content );
return $content;
}
add_filter( 'the_content', 'mytheme_lazy_load_images' );
Navigation & Query Helpers
Custom Query Functions
<?php
/**
* Get related posts
*/
function mytheme_get_related_posts( $post_id = null, $number = 3 ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
$categories = wp_get_post_categories( $post_id );
$args = array(
'category__in' => $categories,
'post__not_in' => array( $post_id ),
'posts_per_page' => $number,
'ignore_sticky_posts' => 1,
'orderby' => 'rand',
);
return new WP_Query( $args );
}
/**
* Get popular posts by view count
*/
function mytheme_get_popular_posts( $number = 5 ) {
$args = array(
'posts_per_page' => $number,
'meta_key' => 'post_views_count',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'ignore_sticky_posts' => 1,
);
return new WP_Query( $args );
}
/**
* Track post views
*/
function mytheme_track_post_views( $post_id ) {
if ( ! is_single() ) return;
if ( empty( $post_id ) ) {
global $post;
$post_id = $post->ID;
}
$count_key = 'post_views_count';
$count = get_post_meta( $post_id, $count_key, true );
if ( $count == '' ) {
$count = 0;
delete_post_meta( $post_id, $count_key );
add_post_meta( $post_id, $count_key, '0' );
} else {
$count++;
update_post_meta( $post_id, $count_key, $count );
}
}
add_action( 'wp_head', 'mytheme_track_post_views' );
/**
* Custom breadcrumbs
*/
function mytheme_breadcrumbs() {
$separator = ' > ';
$home = 'Home';
echo '<nav class="breadcrumbs">';
echo '<a href="' . home_url() . '">' . $home . '</a>' . $separator;
if ( is_category() || is_single() ) {
the_category( ', ' );
if ( is_single() ) {
echo $separator;
the_title();
}
} elseif ( is_page() ) {
the_title();
} elseif ( is_search() ) {
echo 'Search Results for: ' . get_search_query();
}
echo '</nav>';
}
AJAX Handler Functions
Load More Posts AJAX
<?php
/**
* AJAX Load More Posts Handler
*/
function mytheme_load_more_posts() {
// Verify nonce
if ( ! wp_verify_nonce( $_POST['nonce'], 'mytheme_load_more_nonce' ) ) {
wp_die( 'Security check failed' );
}
$page = isset( $_POST['page'] ) ? intval( $_POST['page'] ) : 1;
$posts_per_page = isset( $_POST['posts_per_page'] ) ? intval( $_POST['posts_per_page'] ) : 6;
$args = array(
'post_type' => 'post',
'posts_per_page' => $posts_per_page,
'paged' => $page,
'post_status' => 'publish',
);
$query = new WP_Query( $args );
if ( $query->have_posts() ) {
ob_start();
while ( $query->have_posts() ) {
$query->the_post();
get_template_part( 'template-parts/content', 'card' );
}
$content = ob_get_clean();
wp_send_json_success( array(
'content' => $content,
'max_pages' => $query->max_num_pages,
'found_posts' => $query->found_posts,
) );
} else {
wp_send_json_error( 'No more posts found' );
}
wp_die();
}
add_action( 'wp_ajax_load_more_posts', 'mytheme_load_more_posts' );
add_action( 'wp_ajax_nopriv_load_more_posts', 'mytheme_load_more_posts' );
/**
* AJAX Search
*/
function mytheme_ajax_search() {
check_ajax_referer( 'mytheme_ajax_search_nonce', 'nonce' );
$search_term = sanitize_text_field( $_POST['search'] );
$args = array(
's' => $search_term,
'post_type' => 'post',
'posts_per_page' => 5,
);
$query = new WP_Query( $args );
if ( $query->have_posts() ) {
$results = array();
while ( $query->have_posts() ) {
$query->the_post();
$results[] = array(
'title' => get_the_title(),
'url' => get_permalink(),
'date' => get_the_date(),
);
}
wp_send_json_success( $results );
} else {
wp_send_json_error( 'No results found' );
}
wp_die();
}
add_action( 'wp_ajax_ajax_search', 'mytheme_ajax_search' );
add_action( 'wp_ajax_nopriv_ajax_search', 'mytheme_ajax_search' );
Custom Shortcodes
Useful Shortcode Examples
<?php
/**
* Button shortcode
* Usage: [button url="http://example.com" text="Click Me" color="primary"]
*/
function mytheme_button_shortcode( $atts ) {
$atts = shortcode_atts( array(
'url' => '#',
'text' => 'Button',
'color' => 'primary',
'target' => '_self',
), $atts );
return sprintf(
'<a href="%s" class="btn btn-%s" target="%s">%s</a>',
esc_url( $atts['url'] ),
esc_attr( $atts['color'] ),
esc_attr( $atts['target'] ),
esc_html( $atts['text'] )
);
}
add_shortcode( 'button', 'mytheme_button_shortcode' );
/**
* Current year shortcode
* Usage: [year]
*/
function mytheme_year_shortcode() {
return date( 'Y' );
}
add_shortcode( 'year', 'mytheme_year_shortcode' );
/**
* Recent posts shortcode
* Usage: [recent_posts number="3" category="news"]
*/
function mytheme_recent_posts_shortcode( $atts ) {
$atts = shortcode_atts( array(
'number' => 5,
'category' => '',
), $atts );
$args = array(
'posts_per_page' => intval( $atts['number'] ),
'category_name' => sanitize_text_field( $atts['category'] ),
);
$query = new WP_Query( $args );
if ( ! $query->have_posts() ) {
return 'No posts found';
}
$output = '<ul class="shortcode-recent-posts">';
while ( $query->have_posts() ) {
$query->the_post();
$output .= '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
}
$output .= '</ul>';
wp_reset_postdata();
return $output;
}
add_shortcode( 'recent_posts', 'mytheme_recent_posts_shortcode' );
General Utility Functions
Useful Helper Functions
<?php
/**
* Check if current page is a blog page
*/
function mytheme_is_blog() {
return ( is_archive() || is_author() || is_category() || is_home() || is_single() || is_tag() ) && 'post' == get_post_type();
}
/**
* Get current page URL
*/
function mytheme_current_url() {
global $wp;
return home_url( add_query_arg( array(), $wp->request ) );
}
/**
* Format phone number for tel: links
*/
function mytheme_format_phone( $phone ) {
return preg_replace( '/[^0-9+]/', '', $phone );
}
/**
* Get social share links
*/
function mytheme_social_share_links() {
$url = urlencode( get_permalink() );
$title = urlencode( get_the_title() );
$links = array(
'facebook' => 'https://www.facebook.com/sharer/sharer.php?u=' . $url,
'twitter' => 'https://twitter.com/intent/tweet?url=' . $url . '&text=' . $title,
'linkedin' => 'https://www.linkedin.com/shareArticle?mini=true&url=' . $url . '&title=' . $title,
'pinterest' => 'https://pinterest.com/pin/create/button/?url=' . $url . '&description=' . $title,
);
return $links;
}
/**
* Get attachment ID from URL
*/
function mytheme_get_attachment_id_from_url( $url ) {
$attachment_id = 0;
$dir = wp_upload_dir();
if ( false !== strpos( $url, $dir['baseurl'] . '/' ) ) {
$file = basename( $url );
$query_args = array(
'post_type' => 'attachment',
'post_status' => 'inherit',
'fields' => 'ids',
'meta_query' => array(
array(
'value' => $file,
'compare' => 'LIKE',
'key' => '_wp_attachment_metadata',
),
)
);
$query = new WP_Query( $query_args );
if ( $query->have_posts() ) {
foreach ( $query->posts as $post_id ) {
$meta = wp_get_attachment_metadata( $post_id );
$original_file = basename( $meta['file'] );
$cropped_image_files = wp_list_pluck( $meta['sizes'], 'file' );
if ( $original_file === $file || in_array( $file, $cropped_image_files ) ) {
$attachment_id = $post_id;
break;
}
}
}
}
return $attachment_id;
}
/**
* Human-readable time difference
*/
function mytheme_time_ago() {
return human_time_diff( get_the_time( 'U' ), current_time( 'timestamp' ) ) . ' ago';
}
Organizing Custom Functions
Keep your functions organized by splitting them into separate files:
- theme-folder/
- functions.php
- inc/
- setup.php (theme setup functions)
- assets.php (styles and scripts)
- widgets.php (widget areas)
- navigation.php (menu functions)
- template-functions.php (template helpers)
- customizer.php (customizer settings)
- ajax-handlers.php (AJAX functions)
- shortcodes.php (shortcode definitions)
- helpers.php (utility functions)
Including Files in functions.php
<?php
/**
* Theme functions and definitions
*/
// Define constants
define( 'MYTHEME_VERSION', '1.0.0' );
define( 'MYTHEME_DIR', get_template_directory() );
define( 'MYTHEME_URI', get_template_directory_uri() );
// Include function files
$mytheme_includes = array(
'/inc/setup.php', // Theme setup
'/inc/assets.php', // Scripts and styles
'/inc/widgets.php', // Widget areas
'/inc/navigation.php', // Custom navigation
'/inc/template-functions.php', // Template helpers
'/inc/customizer.php', // Customizer additions
'/inc/ajax-handlers.php', // AJAX functions
'/inc/shortcodes.php', // Shortcodes
'/inc/helpers.php', // Helper functions
);
foreach ( $mytheme_includes as $file ) {
$filepath = MYTHEME_DIR . $file;
if ( file_exists( $filepath ) ) {
require_once $filepath;
}
}
Security in Custom Functions
Security Best Practices
- Sanitize Input: Always sanitize user input with appropriate functions
- Escape Output: Escape all dynamic output
- Validate Data: Validate data before processing
- Use Nonces: Implement nonces for forms and AJAX
- Check Capabilities: Verify user permissions
- Prefix Functions: Use unique prefixes to avoid conflicts
- Use Prepared Statements: For database queries
Security Examples
<?php
/**
* Secure custom function example
*/
function mytheme_secure_function() {
// Check user capabilities
if ( ! current_user_can( 'edit_posts' ) ) {
wp_die( 'Unauthorized access' );
}
// Verify nonce
if ( ! isset( $_POST['mytheme_nonce'] ) ||
! wp_verify_nonce( $_POST['mytheme_nonce'], 'mytheme_action' ) ) {
wp_die( 'Security check failed' );
}
// Sanitize input
$title = sanitize_text_field( $_POST['title'] );
$content = wp_kses_post( $_POST['content'] );
$url = esc_url_raw( $_POST['url'] );
$number = intval( $_POST['number'] );
// Validate data
if ( empty( $title ) || strlen( $title ) > 100 ) {
return new WP_Error( 'invalid_title', 'Invalid title provided' );
}
// Escape output
echo '<h2>' . esc_html( $title ) . '</h2>';
echo '<div>' . wp_kses_post( $content ) . '</div>';
echo '<a href="' . esc_url( $url ) . '">Link</a>';
}
Useful Code Snippets
Disable Emojis
function mytheme_disable_emojis() {
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_action( 'admin_print_styles', 'print_emoji_styles' );
}
add_action( 'init', 'mytheme_disable_emojis' );
Custom Login Logo
function mytheme_login_logo() {
echo '<style>
#login h1 a {
background-image: url(' . get_template_directory_uri() . '/logo.png);
width: 200px;
height: 80px;
background-size: contain;
}
</style>';
}
add_action( 'login_enqueue_scripts', 'mytheme_login_logo' );
Remove WordPress Version
remove_action( 'wp_head', 'wp_generator' );
add_filter( 'the_generator', '__return_empty_string' );
Practice Exercise
Create Custom Function Library