Hook actions

43

questions

Answers to common questions about WordPress action hooks, from attachment and post lifecycle events to login, admin, and theme-setup hooks. Includes notes on how these actions behave in block themes (FSE) and with the REST API.

Can I cancel a deletion from inside delete_attachment?

No — this action fires as notification, not permission. It can't stop the delete from proceeding.

Used in

Can I read the image dimensions inside add_attachment?

Not yet — metadata generation happens afterward. Hook wp_generate_attachment_metadata if you need sizes or EXIF data.

Used in

Can I redirect the user somewhere custom after logout?

Yes — hook wp_logout (or the logout_redirect filter for more control over the target URL) and call wp_safe_redirect() followed by exit.

Used in

Can I redirect users away from wp-admin on admin_init?

Yes, that's a very common pattern — check current_user_can() and wp_redirect() + exit if the check fails, being careful to allow admin-ajax.php requests through.

Used in

Can I use after_setup_theme in a plugin?

Yes, but it's primarily meant for themes. Plugins usually use plugins_loaded or init instead.

Used in

Can I use functions from my regular plugins inside a muplugins_loaded callback?

No — regular (non-mu) plugins have not loaded yet at this point. That is exactly what plugins_loaded is for.

Used in

Do I need this if I already hook save_post?

Only if you need the REST request object itself — headers, route, or client-specific data that save_post doesn't expose.

Used in

Do I need to call wp_footer() myself in a block theme?

No — a block (FSE) theme has no footer.php to edit. WordPress calls wp_footer() automatically after the block template renders, so anything hooked here still prints right before </body>.

Used in

Does add_attachment fire for REST API uploads too?

Yes — it fires from wp_insert_attachment() regardless of whether the upload came from wp-admin, the REST API, or a programmatic call.

Used in

Does before_delete_post fire when I click ‘Trash’ on a post?

No. Moving a post to Trash fires wp_trash_post instead. before_delete_post only fires for a permanent/hard delete — either from Trash or via force-delete.

Used in

Does edit_attachment fire when I update media via the REST API?

No — that path fires rest_insert_attachment instead. edit_attachment is specific to the classic wp-admin edit form.

Used in

Does enqueue_block_editor_assets also run on the front-end for block themes (FSE)?

No — this hook is editor-only, even in a block theme. For assets needed both in the editor and on the front-end (e.g. a block's own styling), use enqueue_block_assets instead.

Used in

Does muplugins_loaded fire if I have no mu-plugins at all?

Yes — WordPress calls this hook unconditionally on every request, whether the mu-plugins folder has files in it or not.

Used in

Does rest_insert_post replace save_post for REST-created posts?

No — both fire. rest_insert_post adds the request context on top of what save_post already gives you.

Used in

Does user_register fire for users created in wp-admin by an administrator?

Yes — any path that goes through wp_insert_user() triggers it, including admin-created accounts and REST API user creation.

Used in

Does wp_head still fire in a block theme (FSE), since there’s no header.php?

Yes. WordPress auto-inserts wp_head()/wp_footer() around the block-based template output, so every callback you register still runs — you just never see or edit the call site yourself.

Used in

Does wp_login fire for REST API or application password logins?

No — it fires only for the standard wp_signon() cookie-based login flow, not REST authentication.

Used in

How do I detect only the first publish, not every re-save?

Check that $new_status === 'publish' && $old_status !== 'publish' inside transition_post_status.

Used in

How do I find the right $hook_suffix value?

Add error_log($hook_suffix) inside the callback and check your PHP error log while on the target screen, or use get_current_screen()->id.

Used in

How do I make an admin_notices message dismissible?

Add the is-dismissible CSS class to the notice div and enqueue wp-admin's built-in dismiss script, or track dismissal yourself via user meta.

Used in

How often does wp_scheduled_delete run?

Once daily, as one of WordPress's built-in scheduled events.

Used in

Is it safe to run heavy logic directly inside save_post on a large site?

Not if it's synchronous — save_post runs inside the save request itself, so slow logic on a large database can lock tables and delay the editor's save response. Offload it to a queue or scheduled event (e.g. Action Scheduler or wp_schedule_single_event) instead.

Used in

Is plugins_loaded too early for register_post_type?

Yes for some setups — use init instead; plugins_loaded is for dependency checks, not registering WordPress objects.

Used in

Is the file still on disk when this fires?

Yes — the file and its generated sizes are still present; this is the last point where get_attached_file() returns a valid path.

Used in

Is wp_enqueue_scripts the right hook for loading scripts inside the block editor?

No — that's enqueue_block_editor_assets (editor only) or enqueue_block_assets (editor + front-end). wp_enqueue_scripts only runs for the public front-end.

Used in

Should block CSS go on enqueue_block_assets or block.json’s style property?

block.json's style/editorStyle properties are the modern, preferred way for a registered block's own CSS. enqueue_block_assets is better for shared/global styling that isn't tied to one specific block registration.

Used in

Should I enqueue scripts in wp_head?

No. Hook wp_enqueue_scripts, then let core print the tags during wp_head.

Used in

Should I use wp_insert_post or save_post?

Use save_post for editor-driven saves; use wp_insert_post if you also need to catch posts created programmatically outside the editor.

Used in

What does $comment_approved actually contain?

1 for approved, 0 for pending moderation, or the string 'spam' — check it before triggering notifications so you don't email admins about spam.

Used in

What’s different between edit_attachment and save_post here?

save_post also fires for attachments since they're a post type, but edit_attachment is scoped specifically to the dedicated media-edit admin screen.

Used in

What’s the difference between init and wp_loaded?

init runs first and is where you register things; wp_loaded runs after all init callbacks finished, so it's safer for code that reads what other plugins registered.

Used in

Why did my script load but not run in the right order?

Set proper dependency arrays (e.g. ['jquery']) in wp_enqueue_script() instead of relying on hook priority to control load order.

Used in

Why did my widget’s post list get shortened after I used pre_get_posts?

You likely modified every WP_Query, not just the main one. Add $query->is_main_query() to the condition.

Used in

Why didn’t this fire at exactly midnight?

WP-Cron is pseudo-cron — it only checks for due events on incoming site requests, so timing depends on traffic unless a real system cron drives it.

Used in

Why does register_rest_route() have to run on rest_api_init specifically?

The REST server object doesn't exist yet on earlier hooks like init; rest_api_init is where WordPress builds it before dispatching the request.

Used in

Why does save_post fire twice?

Once for the autosave/revision and once for the real post. Guard with wp_is_post_autosave() and wp_is_post_revision().

Used in

Why doesn’t is_page() work in my redirect on init?

The main query hasn't executed yet on init. Use template_redirect, where the query is finalized and conditional tags are reliable.

Used in

Why doesn’t my admin page show up?

add_menu_page() must be called on admin_menu, not admin_init or plugins_loaded — those run before the menu system is ready.

Used in

Why doesn’t my sidebar show up in the widgets screen?

register_sidebar() must run on the widgets_init hook — calling it earlier or later means WordPress never registers the widget area.

Used in

Why is my analytics snippet blocking page render?

Large synchronous <script src> tags echoed in wp_footer still delay paint — add the async/defer attribute instead of a raw echoed tag.

Used in

Why is my custom post type missing from the admin menu?

It usually means register_post_type() ran on a hook earlier than init, or the CPT registration code never executed on that request.

Used in

Why is my site slow on every page, not just one template?

init fires on every request type. Guard expensive logic with is_admin(), wp_doing_ajax(), or wp_doing_cron() so it only runs where it's actually needed instead of on every hit.

Used in

wp_insert_comment vs comment_post — which should I use?

comment_post is specific to the standard front-end submission flow and gives you the approval status directly. wp_insert_comment fires for every insert path (including REST/import) and gives you the full WP_Comment object.

Used in

Smart FAQ Management

4WP FAQ

Not just another FAQ block. A smart wrapper that adds intelligence
without breaking your design.

Schema JSON-LD

Zero Duplication

Content-First

FSE Hub