Actions

Actions let you execute code at specific points in WordPress. They don't return values — they fire and trigger side effects.

wp_head

head-meta

Outputs content in the <head> section

wp_head is the print slot inside document head. Use it for meta tags and inline snippets — not for enqueueing CSS or JS files.

Signature

do_action( 'wp_head' );

Example

add_action( 'wp_head', function() {
    echo '<meta name="theme-color" content="#0073aa">';
} );

Common Use Cases

  • Enqueue custom meta tags
  • Output inline styles or scripts
  • Add Open Graph / SEO tags
  • Insert tracking pixels
since 1.5.0wp-includes/general-template.php

wp_footer

head-meta

Fires before </body> closing tag

wp_footer is the last print slot on the front-end. Deferred scripts, analytics, and widgets that must load after content belong here.

Signature

do_action( 'wp_footer' );

Example

add_action( 'wp_footer', function() {
    echo '<script>console.log("Footer loaded");</script>';
} );

Common Use Cases

  • Inject deferred JavaScript
  • Add analytics snippets
  • Load chat widgets
  • Output footer-specific markup
since 1.5.1wp-includes/general-template.php

init

bootstrap

Runs after WordPress has finished loading

init is the default place to register post types, taxonomies, and rewrite-related APIs. It fires after plugins are loaded and before headers go out.

Signature

do_action( 'init' );

Example

add_action( 'init', function() {
    register_post_type( 'book', [
        'public' => true,
        'label'  => 'Books',
    ] );
} );

Common Use Cases

  • Register custom post types
  • Register custom taxonomies
  • Start sessions
  • Load text domains (plugins)
since 0.71wp-settings.php

wp_enqueue_scripts

enqueue

The correct hook to enqueue frontend assets

Queue front-end CSS and JS here. WordPress prints the tags during wp_head / wp_footer — do not echo link or script tags yourself.

Signature

do_action( 'wp_enqueue_scripts' );

Example

add_action( 'wp_enqueue_scripts', function() {
    wp_enqueue_style( 'my-style', get_stylesheet_uri() );
    wp_enqueue_script( 'my-script', get_template_directory_uri() . '/js/main.js', [], '1.0', true );
} );

Common Use Cases

  • Enqueue theme stylesheet
  • Load JavaScript libraries
  • Conditionally load assets per page
  • Pass PHP data to JS via wp_localize_script
  • Migrate off bundled jQuery (deprecated direction in core) toward vanilla JS or a bundler
since 2.1.0wp-includes/script-loader.php

save_post

save-crud

Fires after a post is saved or updated

save_post runs after WordPress writes the post row. Use it for caches, sync, and derived data — and always guard against autosave, revisions, and infinite loops.

Signature

do_action( 'save_post', int $post_id, WP_Post $post, bool $update );

Example

add_action( 'save_post', function( $post_id, $post ) {
    if ( $post->post_type !== 'book' ) {
        return;
    }
    delete_transient( 'book_list_' . $post_id );
}, 10, 2 );

Common Use Cases

  • Clear caches after post update
  • Send admin email notifications
  • Sync data to external APIs
  • Auto-generate related records
  • Queue heavy sync/recalculation work instead of running it inline on large tables
since 1.5.0wp-includes/post.php

admin_init

admin

Fires at the beginning of every admin page

admin_init is the admin counterpart to init: settings, redirects, and capability checks that must not run on the front-end.

Signature

do_action( 'admin_init' );

Example

add_action( 'admin_init', function() {
    register_setting( 'my_options_group', 'my_option_name' );
} );

Common Use Cases

  • Register plugin settings
  • Redirect non-admin users
  • Handle admin form submissions
  • Add admin notices conditionally
since 2.0.0wp-admin/admin.php

plugins_loaded

bootstrap

Fires once all active plugins have loaded

plugins_loaded is the earliest point where every active plugin's functions and classes are guaranteed to exist — use it for cross-plugin checks, not for registering WordPress objects.

Signature

do_action( 'plugins_loaded' );

Example

add_action( 'plugins_loaded', function() {
    if ( class_exists( 'WooCommerce' ) ) {
        require_once __DIR__ . '/integrations/woocommerce.php';
    }
} );

Common Use Cases

  • Check if another plugin/class exists
  • Load plugin translations
  • Bootstrap plugin dependencies
  • Set up autoloaders
since 1.5.0wp-settings.php

wp_loaded

bootstrap

Fires once WordPress, plugins, and theme are fully loaded

wp_loaded runs after every init callback has finished — use it when your code depends on things other plugins register on init.

Signature

do_action( 'wp_loaded' );

Example

add_action( 'wp_loaded', function() {
    if ( ! post_type_exists( 'book' ) ) {
        return;
    }
    flush_rewrite_rules();
} );

Common Use Cases

  • Read config set by other plugins on init
  • Conditional logic that depends on registered post types/taxonomies
  • Late-stage bootstrap checks
since 3.0.0wp-settings.php

after_setup_theme

bootstrap

Fires after the theme is loaded, before init

after_setup_theme is where themes declare support for core features — post thumbnails, menus, HTML5 markup — before WordPress finishes bootstrapping.

Signature

do_action( 'after_setup_theme' );

Example

add_action( 'after_setup_theme', function() {
    add_theme_support( 'post-thumbnails' );
    add_theme_support( 'title-tag' );
    register_nav_menus( [ 'primary' => 'Primary Menu' ] );
} );

Common Use Cases

  • Declare add_theme_support() features
  • Register nav menu locations
  • Set content width
  • Load theme text domain
since 2.6.0wp-settings.php

widgets_init

bootstrap

Fires when it's time to register widgets and sidebars

widgets_init is exclusively for register_sidebar() and register_widget() calls — nothing else belongs here.

Signature

do_action( 'widgets_init' );

Example

add_action( 'widgets_init', function() {
    register_sidebar( [
        'name' => 'Footer Widgets',
        'id'   => 'footer-widgets',
        'before_widget' => '<div class="widget %2$s">',
        'after_widget'  => '</div>',
    ] );
} );

Common Use Cases

  • Register sidebar/widget areas
  • Register custom WP_Widget classes
  • Unregister default widgets
since 2.2.0wp-includes/widgets.php

admin_enqueue_scripts

enqueue

The correct hook to enqueue admin-only assets

admin_enqueue_scripts is the admin equivalent of wp_enqueue_scripts — always check $hook_suffix so you don't load your CSS/JS on every admin screen.

Signature

do_action( 'admin_enqueue_scripts', string $hook_suffix );

Example

add_action( 'admin_enqueue_scripts', function( $hook_suffix ) {
    if ( 'post.php' !== $hook_suffix ) {
        return;
    }
    wp_enqueue_script( 'my-admin-js', plugins_url( 'admin.js', __FILE__ ), [ 'jquery' ], '1.0', true );
} );

Common Use Cases

  • Enqueue admin CSS/JS scoped to one screen
  • Load a color picker or media uploader script
  • Pass data to admin JS via wp_localize_script
since 2.8.0wp-admin/admin-header.php

template_redirect

loop-content

Fires right before WordPress decides which template to load

template_redirect is the standard place to short-circuit a request — custom redirects, maintenance mode, or blocking access — after the query is known but before any template file loads.

Signature

do_action( 'template_redirect' );

Example

add_action( 'template_redirect', function() {
    if ( is_page( 'old-page' ) ) {
        wp_safe_redirect( home_url( '/new-page/' ), 301 );
        exit;
    }
} );

Common Use Cases

  • Custom redirects based on conditional tags
  • Block access / maintenance mode
  • Serve a custom response instead of a template (e.g. JSON)
  • Force login on specific pages
since 1.5.0wp-includes/template-loader.php

pre_get_posts

loop-content

Modify any WP_Query before it runs

pre_get_posts fires for every query — main and secondary. Always check is_main_query() (and is_admin()) or you'll silently break widgets, related-post queries, and REST requests.

Signature

do_action( 'pre_get_posts', WP_Query $query );

Example

add_action( 'pre_get_posts', function( $query ) {
    if ( ! is_admin() && $query->is_main_query() && is_post_type_archive( 'book' ) ) {
        $query->set( 'posts_per_page', 24 );
    }
} );

Common Use Cases

  • Change posts_per_page for an archive
  • Include/exclude post types from search
  • Reorder posts on a custom query
  • Restrict a query by taxonomy or meta
since 2.0.0wp-includes/class-wp-query.php

wp_insert_post

save-crud

Fires immediately after a post is inserted or updated in the database

wp_insert_post looks like save_post but fires from every wp_insert_post() call, including programmatic inserts that never touch the block editor.

Signature

do_action( 'wp_insert_post', int $post_id, WP_Post $post, bool $update );

Example

add_action( 'wp_insert_post', function( $post_id, $post, $update ) {
    if ( 'book' !== $post->post_type || $update ) {
        return;
    }
    wp_mail( get_option( 'admin_email' ), 'New book added', get_the_title( $post_id ) );
}, 10, 3 );

Common Use Cases

  • Notify on new post creation (not updates)
  • React to programmatic/import inserts
  • Sync newly created posts to an external system
since 1.2.0wp-includes/post.php

transition_post_status

save-crud

Fires whenever a post's status changes

transition_post_status is the only hook that reliably tells you a status changed — including the old value — which save_post cannot do.

Signature

do_action( 'transition_post_status', string $new_status, string $old_status, WP_Post $post );

Example

add_action( 'transition_post_status', function( $new_status, $old_status, $post ) {
    if ( 'publish' === $new_status && 'publish' !== $old_status ) {
        wp_mail( get_option( 'admin_email' ), 'Post published', get_the_title( $post ) );
    }
}, 10, 3 );

Common Use Cases

  • Detect draft-to-publish transitions
  • Log status change history
  • Trigger workflows on unpublish/trash
  • Build scheduled-publish notifications
since 2.3.0wp-includes/post.php

admin_menu

admin

Fires before the admin menu is rendered — register menu pages here

admin_menu is exclusively for add_menu_page() / add_submenu_page() calls; anything else belongs on admin_init.

Signature

do_action( 'admin_menu' );

Example

add_action( 'admin_menu', function() {
    add_menu_page( 'My Plugin', 'My Plugin', 'manage_options', 'my-plugin', 'my_plugin_render_page', 'dashicons-admin-generic', 66 );
} );

Common Use Cases

  • Register top-level or submenu admin pages
  • Remove default menu items for non-admins
  • Reorder menu items
since 1.5.0wp-admin/menu.php

admin_notices

admin

Fires to print admin notices at the top of admin pages

admin_notices runs on every admin screen — always check current_user_can() or a specific screen ID so your notice doesn't nag every user on every page.

Signature

do_action( 'admin_notices' );

Example

add_action( 'admin_notices', function() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    echo '<div class="notice notice-warning"><p>Please configure My Plugin.</p></div>';
} );

Common Use Cases

  • Show setup/configuration warnings
  • Confirm a settings save succeeded
  • Prompt for a plugin update or license key
  • Surface validation errors after form submit
since 3.1.0wp-admin/admin-header.php

wp_login

auth

Fires after a user has successfully logged in

wp_login fires only on a successful authentication — pair it with wp_login_failed to cover both outcomes.

Signature

do_action( 'wp_login', string $user_login, WP_User $user );

Example

add_action( 'wp_login', function( $user_login, $user ) {
    update_user_meta( $user->ID, 'last_login', current_time( 'mysql' ) );
}, 10, 2 );

Common Use Cases

  • Track last login time
  • Redirect users by role after login
  • Log login events for security auditing
  • Trigger welcome logic on first login
since 1.5.1wp-includes/user.php

user_register

auth

Fires immediately after a new user is registered

user_register fires right after wp_insert_user() writes the row — the account exists but you're still inside the registration request, which matters for redirects and emails.

Signature

do_action( 'user_register', int $user_id, array $userdata );

Example

add_action( 'user_register', function( $user_id ) {
    $user = new WP_User( $user_id );
    $user->set_role( 'subscriber' );
    wp_mail( $user->user_email, 'Welcome!', 'Thanks for registering.' );
} );

Common Use Cases

  • Send welcome emails
  • Assign a default role
  • Sync new users to a CRM/mailing list
  • Set default user meta on signup
since 2.0.0wp-includes/user.php

rest_api_init

rest-api

Fires when the REST API is initialized — register custom routes here

rest_api_init is the register_rest_route() equivalent of init — it only fires on REST requests, not every page load.

Signature

do_action( 'rest_api_init', WP_REST_Server $wp_rest_server );

Example

add_action( 'rest_api_init', function() {
    register_rest_route( 'my-plugin/v1', '/books', [
        'methods'  => 'GET',
        'callback' => 'my_plugin_get_books',
        'permission_callback' => '__return_true',
    ] );
} );

Common Use Cases

  • Register custom REST routes
  • Expose extra fields on existing endpoints via register_rest_field
  • Add custom REST authentication logic
since 4.4.0wp-includes/rest-api.php

wp_logout

auth

Fires after a user logs out

wp_logout is the mirror of wp_login — use it to clear per-user cookies, caches, or add custom redirect logic.

Signature

do_action( 'wp_logout', int $user_id );

Example

add_action( 'wp_logout', function( $user_id ) {
    delete_transient( 'user_session_' . $user_id );
} );

Common Use Cases

  • Clear custom cookies or transients on logout
  • Log a security/audit event
  • Redirect to a custom page after logout
  • Invalidate a cached per-user fragment
since 1.5.1wp-includes/pluggable.php

comment_post

comments

Fires immediately after a comment is inserted

comment_post fires right after a new comment saves — the standard place to trigger notifications or moderation logic.

Signature

do_action( 'comment_post', int $comment_id, int|string $comment_approved, array $comment_data );

Example

add_action( 'comment_post', function( $comment_id, $approved ) {
    if ( 1 === $approved ) {
        wp_mail( get_option( 'admin_email' ), 'New comment', 'A comment was just approved.' );
    }
}, 10, 2 );

Common Use Cases

  • Send custom notification emails
  • Auto-flag suspicious comments for review
  • Sync comments to an external system
  • Award points/badges for commenting
since 1.2.0wp-includes/comment.php

wp_insert_comment

comments

Fires right after a comment row is written to the database

wp_insert_comment is the lower-level sibling of comment_post — it fires for every comment insert, including ones triggered programmatically, not just front-end submissions.

Signature

do_action( 'wp_insert_comment', int $id, WP_Comment $comment );

Example

add_action( 'wp_insert_comment', function( $id, $comment ) {
    error_log( 'New comment #' . $id . ' on post ' . $comment->comment_post_ID );
}, 10, 2 );

Common Use Cases

  • React to comments created via REST API or import scripts, not just the comment form
  • Sync a WP_Comment object right after creation
  • Trigger downstream logic that needs the full comment object rather than raw array data
since 2.8.0wp-includes/comment.php

enqueue_block_editor_assets

enqueue

Loads scripts/styles inside the block editor only

enqueue_block_editor_assets is the block-editor-only counterpart to wp_enqueue_scripts — it never runs on the public front-end.

Signature

do_action( 'enqueue_block_editor_assets' );

Example

add_action( 'enqueue_block_editor_assets', function() {
    wp_enqueue_script(
        'my-block-editor-script',
        get_template_directory_uri() . '/js/editor.js',
        [ 'wp-blocks', 'wp-element', 'wp-editor' ],
        '1.0'
    );
} );

Common Use Cases

  • Load custom block registration JS
  • Add editor-only CSS for custom blocks
  • Register a Gutenberg sidebar plugin
  • Load third-party editor extensions
since 5.0.0wp-includes/script-loader.php

before_delete_post

save-crud

Fires right before a post is permanently deleted

before_delete_post is your last chance to act while the post (and its data) still exists — right after this, wp_delete_post() removes the row for good.

Signature

do_action( 'before_delete_post', int $post_id, WP_Post $post );

Example

add_action( 'before_delete_post', function( $post_id, $post ) {
    if ( 'book' !== $post->post_type ) {
        return;
    }
    global $wpdb;
    $wpdb->delete( $wpdb->prefix . 'book_ratings', [ 'post_id' => $post_id ] );
}, 10, 2 );

Common Use Cases

  • Clean up custom database rows tied to a post
  • Delete associated generated/uploaded files
  • Sync the deletion to an external system
  • Log permanent deletions for an audit trail
since 1.2.0wp-includes/post.php

enqueue_block_assets

enqueue

Loads scripts/styles in both the block editor and the front-end

enqueue_block_assets is the one enqueue hook that runs in both places — the block editor and the public front-end — making it the right spot for a block's own shared styling.

Signature

do_action( 'enqueue_block_assets' );

Example

add_action( 'enqueue_block_assets', function() {
    wp_enqueue_style( 'my-block-style', get_template_directory_uri() . '/css/block.css' );
} );

Common Use Cases

  • Load a custom block's CSS so it looks right in the editor and on the front-end
  • Share fonts/variables between editor preview and rendered output
  • Avoid duplicating enqueue code across wp_enqueue_scripts and enqueue_block_editor_assets
since 5.0.0wp-includes/script-loader.php