Hook actions
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
- delete_attachment Hook action
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
- add_attachment Hook action
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
- wp_logout Hook action
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
- admin_init Hook action
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
- after_setup_theme Hook action
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
- muplugins_loaded Hook action
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
- rest_insert_post Hook action
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
- wp_footer Hook action
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
- add_attachment Hook action
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
- before_delete_post Hook action
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
- edit_attachment Hook action
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
- enqueue_block_editor_assets Hook action
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
- muplugins_loaded Hook action
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
- rest_insert_post Hook action
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
- user_register Hook action
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
- wp_head Hook action
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
- wp_login Hook action
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
- transition_post_status Hook action
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
- admin_enqueue_scripts Hook action
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
- admin_notices Hook action
How often does wp_scheduled_delete run?
Once daily, as one of WordPress's built-in scheduled events.
Used in
- wp_scheduled_delete Hook action
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
- save_post Hook action
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
- plugins_loaded Hook action
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
- delete_attachment Hook action
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
- wp_enqueue_scripts Hook action
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
- enqueue_block_assets Hook action
Should I enqueue scripts in wp_head?
No. Hook wp_enqueue_scripts, then let core print the tags during wp_head.
Used in
- wp_head Hook action
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
- wp_insert_post Hook action
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
- comment_post Hook action
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
- edit_attachment Hook action
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
- wp_loaded Hook action
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
- wp_enqueue_scripts Hook action
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
- pre_get_posts Hook action
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
- wp_scheduled_delete Hook action
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
- rest_api_init Hook action
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
- save_post Hook action
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
- template_redirect Hook action
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
- admin_menu Hook action
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
- widgets_init Hook action
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
- wp_footer Hook action
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
- init Hook action
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
- init Hook action
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
- wp_insert_comment Hook action
Smart FAQ Management
4WP FAQ
Not just another FAQ block. A smart wrapper that adds intelligence
without breaking your design.