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
Plugins load CSS and JavaScript with wp_enqueue_style() and wp_enqueue_script() on the wp_enqueue_scripts and admin_enqueue_scripts hooks — and a well-built plugin loads them only on the pages that actually use them.
The admin_enqueue_scripts hook passes the current screen's hook suffix. Compare it to the value add_menu_page() returned, and your scripts never touch any other admin page:
<?php
function gdc_books_admin_menu() {
$hook = add_menu_page( 'GDC Books', 'Books Library', 'manage_options', 'gdc-books', 'gdc_books_render_dashboard', 'dashicons-book-alt' );
// Load assets only when this exact page loads.
add_action( 'admin_print_styles-' . $hook, 'gdc_books_admin_assets' );
}
add_action( 'admin_menu', 'gdc_books_admin_menu' );
function gdc_books_admin_assets() {
wp_enqueue_style( 'gdc-books-admin', GDC_BOOKS_URL . 'assets/css/admin.css', array(), GDC_BOOKS_VERSION );
wp_enqueue_script( 'gdc-books-admin', GDC_BOOKS_URL . 'assets/js/admin.js', array( 'wp-api-fetch' ), GDC_BOOKS_VERSION, true );
}For post edit screens, check the screen object instead:
<?php
function gdc_books_editor_assets( $hook_suffix ) {
$screen = get_current_screen();
if ( in_array( $hook_suffix, array( 'post.php', 'post-new.php' ), true ) && 'gdc_book' === $screen->post_type ) {
wp_enqueue_script( 'gdc-books-isbn', GDC_BOOKS_URL . 'assets/js/isbn-check.js', array(), GDC_BOOKS_VERSION, true );
}
}
add_action( 'admin_enqueue_scripts', 'gdc_books_editor_assets' );Watch out: Loading your scripts on every admin page is the top cause of plugin conflicts. Your jQuery plugin or your CSS rule for .button can break a completely unrelated screen.<?php
function gdc_books_public_assets() {
// Register everywhere; enqueue only when used.
wp_register_style( 'gdc-books', GDC_BOOKS_URL . 'assets/css/books.css', array(), GDC_BOOKS_VERSION );
if ( is_singular( 'gdc_book' ) || is_post_type_archive( 'gdc_book' ) ) {
wp_enqueue_style( 'gdc-books' );
}
}
add_action( 'wp_enqueue_scripts', 'gdc_books_public_assets' );The shortcode from lesson 8 calls wp_enqueue_style( 'gdc-books' ) itself, so the stylesheet also loads on any page where the shortcode appears — and nowhere else.
<?php
wp_enqueue_script( 'gdc-books-favourites', GDC_BOOKS_URL . 'assets/js/favourites.js', array(), GDC_BOOKS_VERSION, true );
wp_add_inline_script(
'gdc-books-favourites',
'window.gdcBooks = ' . wp_json_encode(
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'gdc_books_favourite' ),
'i18n' => array(
'saved' => __( 'Saved to favourites', 'gdc-books' ),
'removed' => __( 'Removed from favourites', 'gdc-books' ),
),
)
) . ';',
'before'
);Your script now reads window.gdcBooks.ajaxUrl and window.gdcBooks.nonce. wp_json_encode() escapes the values safely for use inside a <script> tag. You may also see wp_localize_script() in older plugins; it does the same job but was designed for translations and converts every value to a string.
[gdc_books].console.log them.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.