• Block Patterns: A predefined group of blocks (text, images, buttons, columns, etc.) inserted inside a post or page. Once inserted, they’re just normal blocks—you can fully edit them
  • Template Patterns: Larger structural layouts meant for templates used in the Site Editor (not regular page editing). Often define parts of a page or an entire page layout

  • The WordPress commitment is to conform to all WCAG 2.2 Level A and Level AA guidelines.
  • Nonce:
class MyPlugin_Admin {
    
    // Display a form in the WP Admin
    public function render_settings_page() {
        ?>
        <div class="wrap">
            <h1><?php echo esc_html__( 'Plugin Settings', 'my-plugin' ); ?></h1>
            <form method="post" action="admin-post.php">
                <?php wp_nonce_field( 'save_plugin_settings', 'my_admin_nonce' ); ?>
                
                <input type="hidden" name="action" value="my_save_action">

                <input type="text" name="setting_one" value="">
                <?php submit_button( esc_html__( 'Save Changes', 'my-plugin' ) ); ?>
            </form>
        </div>
        <?php
    }
}
public function handle_save() {
    // 1. Verify the nonce
    // If this fails, WP automatically calls wp_die() with a 403 error
    check_admin_referer( 'save_plugin_settings', 'my_admin_nonce' );

    // 2. If we reach here, the request is valid! 
    // Now sanitize the other inputs
    if ( isset( $_POST['setting_one'] ) ) {
        $data = sanitize_text_field( $_POST['setting_one'] );
        update_option( 'my_plugin_data', $data );
    }

    // 3. Redirect back
    wp_redirect( admin_url( 'admin.php?page=my-plugin-page&status=success' ) );
    exit;
}

$action (The “What”)

  • Value in your code: 'save_plugin_settings'
  • Purpose: This is a unique string that describes what is being done.
  • Why it matters: It prevents “Context Switching.” If a hacker gets a nonce for “deleting a comment,” they shouldn’t be able to use that same nonce to “save plugin settings.” The action makes the nonce specific to this task.

$name (The “How”)

  • Value in your code: 'my_admin_nonce'
  • Purpose: This becomes the name attribute of the hidden HTML input field.
  • Why it matters: When the form is submitted, this is the key you look for in the $_POST array to find the token.

  • In PHP, the leading backslash (\) indicates that the class belongs to the Global Namespace.
namespace MyPlugin;

// PHP thinks this is MyPlugin\DOMNode (which doesn't exist!)
public function getImageNodes(DOMNode $node) { ... }

// PHP knows to look at the global PHP core for \DOMNode
public function getImageNodes(\DOMNode $node) { ... }
  • DOMNode, DateTime, Exception, and StdClass are all built into PHP’s core. These live in the global namespace. When you are writing object-oriented code (like your MyPlugin_Admin class), it is a best practice to use the backslash for these core classes.
  • Alternative is to do this:
namespace MyPlugin;

use DOMNode; // Import from global

class Admin {
    public function getImageNodes(DOMNode $node) { 
        // No backslash needed here anymore
    }
}

  • Creating actions
function my_custom_header_display() {
    echo '<header>Welcome to My Site</header>';

    // This creates the hook. Anyone can now "attach" to 'after_my_header'
    do_action( 'after_my_header', get_the_ID() ); 
}
  • [text-domain]-[locale].mo : files are saved in this name explicitly so wp can find it. Ex: rt-movie-db-fr_FR.mo

Metadata

  • Metadata is additional information attached to WordPress objects without changing their core database schema. Metadata is stored as key–value pairs.
ObjectDatabase TableMeta Table
Posts (includes pages & CPTs)wp_postswp_postmeta
Terms (categories, tags, custom taxonomies)wp_termswp_termmeta
Userswp_userswp_usermeta
Commentswp_commentswp_commentmeta
The Metadata API is a core WordPress API that provides:
  • CRUD operations (Create, Read, Update, Delete)
  • Sanitization and serialization
  • Caching
  • Consistent access to meta across object types

Core CRUD Functions (Post Meta)

Add

add_post_meta( $post_id, $meta_key, $meta_value, $unique );

Update

update_post_meta( $post_id, $meta_key, $meta_value, $prev_value );

Get

get_post_meta( $post_id, $meta_key, $single );

Delete

delete_post_meta( $post_id, $meta_key, $meta_value );

  • They are wrappers to
add_metadata()
get_metadata()
update_metadata()
delete_metadata()

Protected Meta Keys

  • Start with _

  • Hidden from:

    • Custom Fields UI

    • REST API (unless registered)

add_post_meta()

  • Adds a new row

  • Can store multiple values for same key

add_post_meta( 42, 'color', 'red' ); add_post_meta( 42, 'color', 'blue' );

update_post_meta()

  • Updates existing meta

  • If it doesn’t exist → creates it

  • Always results in one value

update_post_meta( 42, 'color', 'green' );

Serialization Formats (XML, JSON, PHP Serialization)

  • Internally Uses PHP serialization. Stored as plain text
register_post_meta( 'post', 'event_date', [
  'type'              => 'string',
  'single'            => true,
  'sanitize_callback' => 'sanitize_text_field',
  'show_in_rest'      => true,
] );
  • Meta boxes allow editors to manage metadata visually.

Add Meta Box

add_meta_box(
  'event_details',
  'Event Details',
  'render_event_meta_box',
  'post'
);

save securely

if ( ! isset( $_POST['nonce'] ) ) return;
if ( ! wp_verify_nonce( $_POST['nonce'], 'save_event' ) ) return;

update_post_meta( $post_id, 'event_date', $_POST['event_date'] );

display

$date = get_post_meta( get_the_ID(), 'event_date', true );

if ( $date ) {
  echo esc_html( $date );
}

Shortcode API

  • The Shortcode API lets you define placeholders inside post content that WordPress replaces with dynamic output at render time.
add_shortcode( 'my_shortcode', 'my_shortcode_callback' );

function my_shortcode_callback( $atts, $content = null, $tag = '' ) {
    return 'Hello World';
}
  • shortcodes with variables
add_shortcode('wporg', 'wporg_shortcode');

function wporg_shortcode($atts = [], $content = null) {
    // 1. Define defaults and merge with user-provided attributes
    $pairs = shortcode_atts(
        array(
            'color' => 'blue',  // Default value
            'title' => 'Default Title',
        ), 
        $atts
    );

    // 2. Use the variables (with escaping!)
    $output = '<div style="color:' . esc_attr($pairs['color']) . '">';
    $output .= '<h3>' . esc_html($pairs['title']) . '</h3>';
    $output .= do_shortcode($content); // Allow other shortcodes inside
    $output .= '</div>';

    return $output;
}
  • Calling add_shortcode() adds it to global $GLOBALS['shortcode_tags']
[
  'foo' => 'callback',
  'gallery' => 'gallery_shortcode',
]
  • The flow

    1. the_content filter runs

    2. do_shortcode() is called

    3. Regex scans content(get_shortcode_regex() uses recursive parsing for nested shortcodes)

    4. Matches [shortcode attr="value"]

    5. Callback is executed

    6. Replacement happens inline

  • If shortcode is not defined, wordpress displays it as it is in frontend. No errors.

  • Handling booleans: [feature enabled] Parsed as: [ 'enabled' => '' ] Best practice: $enabled = array_key_exists( 'enabled', $atts );

  • Executing shortcodes from php: echo do_shortcode( '[greeting name="Alex"]' );

  • output buffering

function my_shortcode() {
    ob_start();
    ?>
    <div class="box">
        <h2>Hello</h2>
        <p>This is a shortcode.</p>
    </div>
    <?php
    return ob_get_clean();
}
  • Shortcode types:
    • Self-closing shortcode [gallery ids="1,2,3"]
    • Enclosing (wrapping) shortcode [box]content here[/box]