Build WordPress plugins from scratch: actions and filters, shortcodes, settings pages, custom post types, meta boxes, AJAX, REST endpoints, custom tables, cron and blocks.
Lessons
A shortcode is a tag such as [gdc_books count="3"] that a site owner types into content, and which your plugin replaces with generated HTML when the page is displayed.
Blocks are the modern way to add rich content (lesson 23), but shortcodes still matter: they work in the classic editor, widgets, page builders and the Shortcode block, and they are the quickest way to give users a dynamic element.
<?php
/**
* [gdc_books count="6" genre="fantasy" orderby="title"]
*
* @param array|string $atts Attributes from the shortcode tag.
* @param string|null $content Content between opening and closing tags.
* @param string $tag The shortcode name.
* @return string HTML. Shortcodes must return, never echo.
*/
function gdc_books_shortcode( $atts, $content = null, $tag = '' ) {
$atts = shortcode_atts(
array(
'count' => 6,
'genre' => '',
'orderby' => 'date',
),
$atts,
$tag // Allows the shortcode_atts_gdc_books filter.
);
$args = array(
'post_type' => 'gdc_book',
'posts_per_page' => min( 24, absint( $atts['count'] ) ),
'orderby' => in_array( $atts['orderby'], array( 'date', 'title', 'rand' ), true ) ? $atts['orderby'] : 'date',
'no_found_rows' => true,
);
if ( $atts['genre'] ) {
$args['tax_query'] = array(
array(
'taxonomy' => 'gdc_genre',
'field' => 'slug',
'terms' => sanitize_title( $atts['genre'] ),
),
);
}
$books = new WP_Query( apply_filters( 'gdc_books_query_args', $args, $atts ) );
if ( ! $books->have_posts() ) {
return '<p class="gdc-books-empty">' . esc_html__( 'No books found.', 'gdc-books' ) . '</p>';
}
$html = '<ul class="gdc-books-list">';
while ( $books->have_posts() ) {
$books->the_post();
$html .= sprintf(
'<li><a href="%s">%s</a></li>',
esc_url( get_permalink() ),
esc_html( get_the_title() )
);
}
$html .= '</ul>';
wp_reset_postdata();
return $html;
}
function gdc_books_register_shortcodes() {
add_shortcode( 'gdc_books', 'gdc_books_shortcode' );
}
add_action( 'init', 'gdc_books_register_shortcodes' );shortcode_atts() merges the user's attributes with your defaults and drops anything you did not list — a typo like cout="3" is silently ignored rather than breaking the query.absint(), the orderby allow-list and sanitize_title() turn it into safe values before it reaches the query.min( 24, ... ) caps the count, so count="99999" cannot be used to slow the site down.Watch out: Neverechoin a shortcode. If you need to use templates that echo, capture their output withob_start()andreturn ob_get_clean();.
A shortcode can wrap content: [gdc_note type="tip"]Read the first chapter free.[/gdc_note]. The wrapped text arrives in $content:
<?php
function gdc_books_note_shortcode( $atts, $content = null ) {
$atts = shortcode_atts( array( 'type' => 'info' ), $atts, 'gdc_note' );
$type = in_array( $atts['type'], array( 'info', 'tip', 'warning' ), true ) ? $atts['type'] : 'info';
return sprintf(
'<div class="gdc-note gdc-note--%1$s">%2$s</div>',
esc_attr( $type ),
wp_kses_post( do_shortcode( $content ) ) // Allows shortcodes nested inside.
);
}
add_shortcode( 'gdc_note', 'gdc_books_note_shortcode' );Register your stylesheet on every page but only enqueue it from inside the shortcode. WordPress then prints it in the footer, only on pages that actually contain [gdc_books]:
<?php
add_action( 'wp_enqueue_scripts', function () {
wp_register_style( 'gdc-books-list', GDC_BOOKS_URL . 'assets/css/books.css', array(), GDC_BOOKS_VERSION );
} );
// Then, as the first line inside gdc_books_shortcode():
wp_enqueue_style( 'gdc-books-list' );[gdc_books] and place it on a page with different count and orderby values.count="abc" and orderby="DROP TABLE" and confirm both fall back safely.[gdc_note] and nest a [gdc_books count="2"] inside it.In accordance with Section 6 & Section 7 of the DPDP Act 2023, third-party analytics and marketing trackers are strictly gated behind your affirmative consent. You may withdraw consent at any time under Section 6(4).
Essential for session authentication (ci_session), Cross-Site Request Forgery security (csrf_cookie_name), and Cloudflare DDoS/bot prevention. Cannot be disabled.
Gated under DPDP Section 7. Allows Google Analytics 4 (Google Consent Mode v2) to measure aggregate, anonymized tool usage.
Enables browser local storage to preserve your theme selection (dark/light mode) and code editor layout between visits.
Used to display developer sponsorships and native tool partner notices.