Subscribe to Our Mailing List and Stay Up-to-Date! Subscribe

WordPress Shortcode Tutorial: Create Powerful Reusable Content

WordPress shortcodes transform complex functionality into simple, reusable snippets insertable anywhere via bracket syntax. From buttons and galleries to dynamic content and API integrations, shortcodes empower non-technical users to add sophisticated features without touching code. This comprehensive guide teaches shortcode creation, parameter handling, enclosing content, nesting, and optimization for powerful, maintainable WordPress functionality.

What Are WordPress Shortcodes

Shortcodes are WordPress-specific code macros executing complex functionality via simple syntax:

[shortcode]
[shortcode attribute="value"]
[shortcode]content[/shortcode]

Built-In Shortcodes:

  • – Image gallery
  • – Audio player
  • – Video embed
  • – URL embedding
  • – Image captions

Basic Shortcode Creation

Simple Shortcode (no parameters):

function dprt_button_shortcode() {
    return '<a href="#" class="btn btn-primary">Click Here</a>';
}
add_shortcode('button', 'dprt_button_shortcode');

// Usage: [button]

Output Multiple Elements:

function dprt_alert_shortcode() {
    $output = '<div class="alert alert-info">';
    $output .= '<strong>Notice:</strong> This is an important message.';
    $output .= '</div>';
    return $output;
}
add_shortcode('alert', 'dprt_alert_shortcode');

// Usage: [alert]

Shortcodes with Attributes

Single Attribute:

function dprt_highlight_shortcode($atts) {
    $atts = shortcode_atts(array(
        'color' => 'yellow', // Default value
    ), $atts);

    return '<span style="background-color: ' . esc_attr($atts['color']) . '; padding: 2px 5px;">' . esc_html($atts['color']) . '</span>';
}
add_shortcode('highlight', 'dprt_highlight_shortcode');

// Usage:
// [highlight color="red"]
// [highlight] (uses default yellow)

Multiple Attributes:

function dprt_custom_button_shortcode($atts) {
    $atts = shortcode_atts(array(
        'url' => '#',
        'text' => 'Click Here',
        'color' => 'blue',
        'size' => 'medium',
        'target' => '_self',
    ), $atts);

    $class = 'btn btn-' . esc_attr($atts['color']) . ' btn-' . esc_attr($atts['size']);

    return sprintf(
        '<a href="%s" class="%s" target="%s">%s</a>',
        esc_url($atts['url']),
        esc_attr($class),
        esc_attr($atts['target']),
        esc_html($atts['text'])
    );
}
add_shortcode('custom_button', 'dprt_custom_button_shortcode');

// Usage: [custom_button url="https://example.com" text="Learn More" color="green" size="large" target="_blank"]

Enclosing Shortcodes (Content Between Tags)

Basic Enclosing Shortcode:

function dprt_box_shortcode($atts, $content = null) {
    $atts = shortcode_atts(array(
        'style' => 'default',
    ), $atts);

    $class = 'box box-' . esc_attr($atts['style']);

    return '<div class="' . $class . '">' . do_shortcode($content) . '</div>';
}
add_shortcode('box', 'dprt_box_shortcode');

// Usage: [box style="info"]Your content here[/box]

Note: do_shortcode($content) processes nested shortcodes within content.

Formatting Content:

function dprt_quote_shortcode($atts, $content = null) {
    $atts = shortcode_atts(array(
        'author' => '',
        'source' => '',
    ), $atts);

    $output = '<blockquote class="custom-quote">';
    $output .= wpautop($content); // Converts line breaks to paragraphs
    if (!empty($atts['author'])) {
        $output .= '<cite>— ' . esc_html($atts['author']);
        if (!empty($atts['source'])) {
            $output .= ', <em>' . esc_html($atts['source']) . '</em>';
        }
        $output .= '</cite>';
    }
    $output .= '</blockquote>';

    return $output;
}
add_shortcode('quote', 'dprt_quote_shortcode');

// Usage:
// [quote author="Steve Jobs" source="Stanford Commencement 2005"]
// Stay hungry. Stay foolish.
// [/quote]

Dynamic Content Shortcodes

Display Post Count:

function dprt_post_count_shortcode($atts) {
    $atts = shortcode_atts(array(
        'post_type' => 'post',
    ), $atts);

    $count = wp_count_posts($atts['post_type']);
    return number_format_i18n($count->publish);
}
add_shortcode('post_count', 'dprt_post_count_shortcode');

// Usage: [post_count post_type="product"]

Recent Posts List:

function dprt_recent_posts_shortcode($atts) {
    $atts = shortcode_atts(array(
        'posts_per_page' => 5,
        'post_type' => 'post',
        'category' => '',
    ), $atts);

    $args = array(
        'posts_per_page' => intval($atts['posts_per_page']),
        'post_type' => $atts['post_type'],
        'post_status' => 'publish',
    );

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

    $query = new WP_Query($args);

    if (!$query->have_posts()) {
        return '<p>No posts found.</p>';
    }

    $output = '<ul class="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', 'dprt_recent_posts_shortcode');

// Usage: [recent_posts posts_per_page="10" category="news"]

Current Year:

function dprt_current_year_shortcode() {
    return date('Y');
}
add_shortcode('year', 'dprt_current_year_shortcode');

// Usage: Copyright [year] Your Company

Conditional Shortcodes

Display Content for Logged-In Users:

function dprt_members_only_shortcode($atts, $content = null) {
    if (is_user_logged_in()) {
        return '<div class="members-content">' . do_shortcode($content) . '</div>';
    } else {
        return '<div class="login-notice">Please <a href="' . wp_login_url(get_permalink()) . '">log in</a> to view this content.</div>';
    }
}
add_shortcode('members_only', 'dprt_members_only_shortcode');

// Usage:
// [members_only]
// Exclusive content for members
// [/members_only]

Role-Based Content:

function dprt_role_content_shortcode($atts, $content = null) {
    $atts = shortcode_atts(array(
        'role' => 'subscriber',
    ), $atts);

    if (current_user_can($atts['role'])) {
        return do_shortcode($content);
    }

    return '';
}
add_shortcode('role_content', 'dprt_role_content_shortcode');

// Usage: [role_content role="administrator"]Admin only content[/role_content]

Query-Based Shortcodes

Custom Loop Shortcode:

function dprt_products_grid_shortcode($atts) {
    $atts = shortcode_atts(array(
        'category' => '',
        'count' => 6,
        'columns' => 3,
    ), $atts);

    $args = array(
        'post_type' => 'product',
        'posts_per_page' => intval($atts['count']),
    );

    if (!empty($atts['category'])) {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'product_category',
                'field' => 'slug',
                'terms' => $atts['category'],
            ),
        );
    }

    $query = new WP_Query($args);

    if (!$query->have_posts()) {
        return '<p>No products found.</p>';
    }

    $column_class = 'col-md-' . (12 / intval($atts['columns']));
    $output = '<div class="products-grid row">';

    while ($query->have_posts()) {
        $query->the_post();
        $output .= '<div class="' . $column_class . ' product-item">';
        $output .= '<a href="' . get_permalink() . '">';
        if (has_post_thumbnail()) {
            $output .= get_the_post_thumbnail(get_the_ID(), 'medium');
        }
        $output .= '<h3>' . get_the_title() . '</h3>';
        $output .= '</a>';
        $output .= '</div>';
    }

    $output .= '</div>';
    wp_reset_postdata();

    return $output;
}
add_shortcode('products_grid', 'dprt_products_grid_shortcode');

// Usage: [products_grid category="featured" count="9" columns="3"]

Form Shortcodes

Contact Form:

function dprt_contact_form_shortcode() {
    ob_start();
    ?>
    <form method="post" action="" class="contact-form">
        <?php wp_nonce_field('contact_form_submit', 'contact_form_nonce'); ?>

        <p>
            <label for="name">Name:</label>
            <input type="text" name="name" id="name" required>
        </p>

        <p>
            <label for="email">Email:</label>
            <input type="email" name="email" id="email" required>
        </p>

        <p>
            <label for="message">Message:</label>
            <textarea name="message" id="message" rows="5" required></textarea>
        </p>

        <p>
            <button type="submit" name="submit_contact">Send Message</button>
        </p>
    </form>
    <?php
    return ob_get_clean();
}
add_shortcode('contact_form', 'dprt_contact_form_shortcode');

// Form processing (add separately)
function dprt_process_contact_form() {
    if (isset($_POST['submit_contact'])) {
        if (!isset($_POST['contact_form_nonce']) || !wp_verify_nonce($_POST['contact_form_nonce'], 'contact_form_submit')) {
            wp_die('Security check failed');
        }

        $name = sanitize_text_field($_POST['name']);
        $email = sanitize_email($_POST['email']);
        $message = sanitize_textarea_field($_POST['message']);

        // Process form (send email, save to database, etc.)
        wp_mail('admin@example.com', 'Contact Form Submission', $message);
    }
}
add_action('template_redirect', 'dprt_process_contact_form');

API Integration Shortcodes

External Data Display:

function dprt_api_data_shortcode($atts) {
    $atts = shortcode_atts(array(
        'endpoint' => '',
        'cache_time' => 3600, // Cache for 1 hour
    ), $atts);

    $cache_key = 'api_data_' . md5($atts['endpoint']);
    $data = get_transient($cache_key);

    if (false === $data) {
        $response = wp_remote_get($atts['endpoint']);
        if (is_wp_error($response)) {
            return '<p>Error fetching data.</p>';
        }

        $data = wp_remote_retrieve_body($response);
        set_transient($cache_key, $data, intval($atts['cache_time']));
    }

    $json = json_decode($data, true);

    // Format and return data
    $output = '<div class="api-data">';
    // Process $json as needed
    $output .= '</div>';

    return $output;
}
add_shortcode('api_data', 'dprt_api_data_shortcode');

Nested Shortcodes

Tabs Shortcode System:

function dprt_tabs_shortcode($atts, $content = null) {
    return '<div class="tabs-container">' . do_shortcode($content) . '</div>';
}
add_shortcode('tabs', 'dprt_tabs_shortcode');

function dprt_tab_shortcode($atts, $content = null) {
    $atts = shortcode_atts(array(
        'title' => 'Tab',
    ), $atts);

    return '<div class="tab"><h3>' . esc_html($atts['title']) . '</h3><div class="tab-content">' . do_shortcode($content) . '</div></div>';
}
add_shortcode('tab', 'dprt_tab_shortcode');

// Usage:
// [tabs]
//   [tab title="Features"]Content about features[/tab]
//   [tab title="Pricing"]Content about pricing[/tab]
//   [tab title="Support"]Content about support[/tab]
// [/tabs]

Shortcode Best Practices

1. Always Sanitize and Escape:

function dprt_secure_shortcode($atts, $content = null) {
    $atts = shortcode_atts(array(
        'url' => '',
        'text' => '',
    ), $atts);

    return sprintf(
        '<a href="%s">%s</a>',
        esc_url($atts['url']),      // Escape URL
        esc_html($atts['text'])     // Escape text
    );
}

2. Return, Don’t Echo:

// WRONG
function bad_shortcode() {
    echo 'Content';
}

// CORRECT
function good_shortcode() {
    return 'Content';
}

3. Use Output Buffering for Complex HTML:

function dprt_complex_shortcode() {
    ob_start();
    ?>
    <div class="complex-element">
        <!-- Complex HTML structure -->
    </div>
    <?php
    return ob_get_clean();
}

4. Reset Post Data After Queries:

function dprt_query_shortcode() {
    $query = new WP_Query($args);
    // ... loop through posts
    wp_reset_postdata(); // IMPORTANT!
    return $output;
}

5. Prefix Shortcode Names: Avoid conflicts by prefixing.

// Good: dprt_button
// Bad: button (could conflict with plugin/theme)

Removing Shortcodes

// Remove specific shortcode
remove_shortcode('gallery');

// Remove all shortcodes
remove_all_shortcodes();

Shortcode UI with Gutenberg

Register Shortcode Block (for Gutenberg):

function dprt_register_shortcode_block() {
    register_block_type('dprt/shortcode-button', array(
        'render_callback' => 'dprt_button_shortcode',
    ));
}
add_action('init', 'dprt_register_shortcode_block');

Testing Shortcodes

Test in Widgets:

// Enable shortcodes in widgets
add_filter('widget_text', 'do_shortcode');

Test in Excerpts:

// Enable shortcodes in excerpts
add_filter('the_excerpt', 'do_shortcode');

Conclusion

WordPress shortcodes create powerful, reusable content elements through simple bracket syntax. Build shortcodes with attributes using shortcode_atts(), handle enclosed content via $content parameter, sanitize all inputs with esc_url() and esc_html(), return output instead of echoing, and reset post data after custom queries. Shortcodes empower non-technical users to add sophisticated functionality while maintaining clean, maintainable code architecture.

  1. WordPress Shortcode API
  2. add_shortcode() Reference
  3. shortcode_atts() Reference
  4. do_shortcode() Reference
  5. Shortcode Best Practices

Call to Action

Shortcode implementations need protection. Backup Copilot Pro safeguards your custom WordPress code and configurations. Protect your shortcode library—start your free 30-day trial today!