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.