How to Add a Shortcode in WordPress

Creating a custom shortcode with attributes and sanitized output

Shortcodes are placeholders like [my_button] that WordPress replaces with dynamic content when a post is rendered. They’re the standard way to let content editors insert complex output — contact forms, sliders, pricing tables — without touching code.

Problem: How do you create a WordPress shortcode that accepts user-defined attributes and returns safely escaped HTML?

Solution: Register a shortcode in functions.php or a plugin file using add_shortcode():

add_shortcode( 'latest_posts', 'render_latest_posts_shortcode' );

function render_latest_posts_shortcode( $atts ) {
    // Merge user attributes with defaults
    $atts = shortcode_atts(
        [
            'count'    => 5,
            'category' => '',
        ],
        $atts,
        'latest_posts'
    );

    $args = [
        'posts_per_page' => absint( $atts['count'] ),
        'no_found_rows'  => true,
    ];

    if ( $atts['category'] ) {
        $args['category_name'] = sanitize_text_field( $atts['category'] );
    }

    $query = new WP_Query( $args );

    ob_start();
    if ( $query->have_posts() ) {
        echo '<ul class="latest-posts">';
        while ( $query->have_posts() ) {
            $query->the_post();
            echo '<li><a href="' . esc_url( get_permalink() ) . '">' . esc_html( get_the_title() ) . '</a></li>';
        }
        echo '</ul>';
        wp_reset_postdata();
    }
    return ob_get_clean();
}

Usage in a post or page:

[latest_posts count="3" category="news"]

To render a shortcode programmatically in a template:

echo do_shortcode( '[latest_posts count="5"]' );

NOTE: Always use ob_start() / ob_get_clean() to capture HTML output — shortcode callbacks must return a string, not echo directly. Use shortcode_atts() to sanitize defaults and strip unexpected attributes.