Hooks
questions
WordPress action and filter hooks – real questions from interviews, code reviews, and daily development.
Hook actions
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
Hook filters
Can I hide the admin footer text entirely?
Return an empty string from the filter — WordPress will render the footer area with no text.
Used in
- admin_footer_text Hook filter
Can I remove a default body class instead of adding one?
Yes — search $classes with array_search() and unset() the key, then return the array.
Used in
- body_class Hook filter
Do I still need wp-login.php to exist after filtering login_url?
Only if something still links to it directly. This filter changes what wp_login_url() outputs; it doesn't disable the real wp-login.php endpoint.
Used in
- login_url Hook filter
Does an attachment post exist when this filter runs?
No — this fires before wp_insert_attachment() creates the post, so there's no attachment ID available yet.
Used in
- wp_handle_upload Hook filter
Does changing wp_mail_from fix WordPress emails going to spam?
It helps, but deliverability mainly depends on SPF/DKIM/DMARC records and your mail sending method — this filter alone won't fix a misconfigured domain.
Used in
- wp_mail_from Hook filter
Does filtering comment_text change the comment stored in the database?
No — it only changes what's rendered on display. The database row is untouched.
Used in
- comment_text Hook filter
Does lowering this threshold affect already-uploaded images?
No — it only affects images processed after the filter is added; existing attachments aren't retroactively rescaled.
Used in
- big_image_size_threshold Hook filter
Does render_block run for classic-editor content?
No — only for actual blocks. Classic editor HTML that isn't parsed into blocks goes through the_content filters instead.
Used in
- render_block Hook filter
Does script_loader_tag work for scripts I just echo manually?
No — only for scripts registered via wp_enqueue_script()/wp_register_script(). Manually echoed <script> tags aren't touched.
Used in
- script_loader_tag Hook filter
Does the_excerpt run for manually written excerpts too?
Yes — it runs regardless of whether the excerpt was typed in the editor or auto-generated from content.
Used in
- the_excerpt Hook filter
Does this filter affect images pasted as raw HTML in the editor?
No — only images rendered through WordPress's own attachment image functions (wp_get_attachment_image and friends), not raw <img> HTML.
Used in
- wp_get_attachment_image_attributes Hook filter
Does this filter disable WP-Cron?
No — set the DISABLE_WP_CRON constant to true for that. This filter only adjusts the self-spawned HTTP request's arguments.
Used in
- cron_request Hook filter
Does this run on every page load for existing images?
No — only when metadata is generated or explicitly regenerated (new upload, or a 'Regenerate Thumbnails' action), not on normal display.
Used in
- wp_generate_attachment_metadata Hook filter
How do I reject a login attempt from this filter?
Return a WP_Error object. Returning null lets WordPress continue checking other registered authenticate callbacks instead of stopping the login outright.
Used in
- authenticate Hook filter
How do I target only one specific menu?
Check $args->theme_location (or $args->menu) inside the callback and return $items unchanged for every other menu.
Used in
- wp_nav_menu_items Hook filter
I added a column but the cells are blank — what’s missing?
manage_posts_columns only adds the header. You also need add_action( 'manage_posts_custom_column', … ) to echo the actual value for each row/column combination.
Used in
- manage_posts_columns Hook filter
Is it safe to allow SVG uploads with this filter alone?
No — pair it with an SVG sanitizer library or plugin. The raw filter only changes what's accepted, not what's safe.
Used in
- upload_mimes Hook filter
Is the wp_title filter deprecated?
The wp_title() template tag is legacy — modern themes render titles via wp_get_document_title() and the document_title_parts filter. wp_title only fires if a theme still calls wp_title() directly in header.php.
Used in
- wp_title Hook filter
Is this the right hook to build a headless auth layer?
Yes — it's the standard extension point for custom REST authentication schemes like API keys or signed requests.
Used in
- rest_authentication_errors Hook filter
post_class vs body_class — which one do I use?
body_class targets the <body> tag once per page; post_class targets each individual post wrapper and runs once per post in the loop.
Used in
- post_class Hook filter
rest_prepare_post vs register_rest_field — which should I use?
register_rest_field is the documented way to add a field with schema support; rest_prepare_post is a lower-level filter for quick tweaks or removing/reshaping existing fields.
Used in
- rest_prepare_post Hook filter
template_include vs single_template — which should I use?
single_template (and its siblings like page_template) only affect one specific query type; template_include is the catch-all that runs regardless of query type.
Used in
- template_include Hook filter
What does returning null mean versus WP_Error?
null means 'no opinion, defer to other checks'; a WP_Error means 'block this request with this specific error'.
Used in
- rest_authentication_errors Hook filter
What happens if I forget to return the metadata array?
The attachment loses its generated sizes and dimension data — always return $metadata, even unmodified.
Used in
- wp_generate_attachment_metadata Hook filter
What’s the default threshold?
2560 pixels on the long edge, introduced in WordPress 5.3.
Used in
- big_image_size_threshold Hook filter
What’s the difference between ‘upload’ and ‘sideload’ context?
'upload' is a direct user file upload; 'sideload' is a file pulled in programmatically, such as an image imported from a URL.
Used in
- wp_handle_upload Hook filter
What’s the difference between rest_pre_dispatch and a route’s permission_callback?
permission_callback runs per-route after matching, for auth checks on that specific endpoint. rest_pre_dispatch runs before routing even happens and can intercept every request site-wide.
Used in
- rest_pre_dispatch Hook filter
Why did my content disappear?
A the_content callback must return the string. Echoing instead of returning wipes the post body.
Used in
- the_content Hook filter
Why did my excerpt_length change do nothing?
The post already has a manually written excerpt, or the theme calls a hardcoded wp_trim_words() instead of get_the_excerpt() — this filter only affects the auto-generation path.
Used in
- excerpt_length Hook filter
Why did the_title also change my menu item labels?
The filter runs anywhere get_the_title()/the_title() is called, including some menu-label code paths. Check the post type or scope the condition more tightly.
Used in
- the_title Hook filter
Why does my custom WP-Cron interval never fire?
Usually cron_schedules wasn't hooked before wp_schedule_event() ran, or the interval key name doesn't match exactly — WordPress silently ignores unknown interval names instead of throwing an error.
Used in
- cron_schedules Hook filter
Why does the_content contain HTML comments like ?
Those are block delimiters the block editor saves into post_content. Each one already passed through the render_block filter before reaching the_content, so by the time your filter runs it's rendered HTML with block comments still attached — not raw block JSON.
Used in
- the_content Hook filter
Why doesn’t excerpt_more change my manual excerpt?
It only applies to auto-generated excerpts. A manually written excerpt is used exactly as typed, with no trailing string appended.
Used in
- excerpt_more Hook filter
Why doesn’t my wp_title filter work on this theme?
Modern themes render the title via wp_get_document_title(), which uses document_title_parts — wp_title() usually isn't called at all.
Used in
- document_title_parts Hook filter
Why would I need to touch this on a high-traffic site?
High-traffic sites often disable the default request-triggered cron and drive wp-cron.php via a real system cron instead — this filter is mostly relevant when you still rely on WordPress's own self-ping.
Used in
- cron_request Hook filter
Smart FAQ Management
4WP FAQ
Not just another FAQ block. A smart wrapper that adds intelligence
without breaking your design.