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 meta box is a panel on the post edit screen where your plugin collects extra fields — an ISBN, a page count — which are saved as post meta with update_post_meta() when the post is saved.
<?php
function gdc_books_add_meta_boxes() {
add_meta_box(
'gdc_books_details', // ID.
__( 'Book Details', 'gdc-books' ), // Title.
'gdc_books_render_details_box', // Render callback.
'gdc_book', // Post type.
'side', // Context: normal, side or advanced.
'high' // Priority within the context.
);
}
add_action( 'add_meta_boxes', 'gdc_books_add_meta_boxes' );<?php
function gdc_books_render_details_box( WP_Post $post ) {
wp_nonce_field( 'gdc_books_save_details', 'gdc_books_details_nonce' );
$isbn = get_post_meta( $post->ID, '_gdc_books_isbn', true );
$pages = get_post_meta( $post->ID, '_gdc_books_pages', true );
?>
<p>
<label for="gdc_books_isbn"><?php esc_html_e( 'ISBN', 'gdc-books' ); ?></label>
<input type="text" id="gdc_books_isbn" name="gdc_books_isbn"
value="<?php echo esc_attr( $isbn ); ?>" class="widefat">
</p>
<p>
<label for="gdc_books_pages"><?php esc_html_e( 'Pages', 'gdc-books' ); ?></label>
<input type="number" id="gdc_books_pages" name="gdc_books_pages" min="1"
value="<?php echo esc_attr( $pages ); ?>" class="widefat">
</p>
<?php
}<?php
function gdc_books_save_details( $post_id ) {
// 1. Our nonce: present and valid.
if ( ! isset( $_POST['gdc_books_details_nonce'] )
|| ! wp_verify_nonce( sanitize_key( $_POST['gdc_books_details_nonce'] ), 'gdc_books_save_details' ) ) {
return;
}
// 2. Autosaves do not submit our fields — skip them.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// 3. Permission for THIS post.
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// 4. Sanitise and save — or delete when emptied.
if ( isset( $_POST['gdc_books_isbn'] ) ) {
$isbn = preg_replace( '/[^0-9X]/', '', strtoupper( sanitize_text_field( wp_unslash( $_POST['gdc_books_isbn'] ) ) ) );
if ( '' === $isbn ) {
delete_post_meta( $post_id, '_gdc_books_isbn' );
} else {
update_post_meta( $post_id, '_gdc_books_isbn', $isbn );
}
}
if ( isset( $_POST['gdc_books_pages'] ) ) {
$pages = absint( $_POST['gdc_books_pages'] );
$pages ? update_post_meta( $post_id, '_gdc_books_pages', $pages ) : delete_post_meta( $post_id, '_gdc_books_pages' );
}
do_action( 'gdc_books_details_saved', $post_id );
}
// save_post_{post_type}: only fires for books.
add_action( 'save_post_gdc_book', 'gdc_books_save_details' );Each check guards against something real: the nonce against forged requests, the autosave check against wiping fields every 60 seconds, and the capability check against users editing posts that are not theirs.
Meta keys starting with _ are protected: hidden from the generic Custom Fields panel, so users cannot edit or break them there. Use them for any field your own UI manages.
Register your meta keys so they are typed, sanitised everywhere, and available to the REST API and to blocks:
<?php
function gdc_books_register_meta() {
register_post_meta(
'gdc_book',
'_gdc_books_pages',
array(
'type' => 'integer',
'single' => true,
'sanitize_callback' => 'absint',
'show_in_rest' => true,
'auth_callback' => function ( $allowed, $meta_key, $post_id ) {
return current_user_can( 'edit_post', $post_id );
},
)
);
}
add_action( 'init', 'gdc_books_register_meta' );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.