Hooks

114

questions

WordPress action and filter hooks – real questions from interviews, code reviews, and daily development.

Do these five hooks fire in the order they’re registered in code?

No — they fire in a fixed pipeline order determined by the REST API’s own request lifecycle (rest_pre_dispatch → authentication → routing → rest_prepare_* → rest_insert_*), regardless of when your plugin registers its callbacks.

Should I use rest_prepare_post or register_rest_field to add a computed field to the response?

register_rest_field for a straightforward computed value tied to the schema. rest_prepare_post when you need to both add and remove fields, or when the logic depends on the full response object rather than a single field’s value.

What’s the difference between comment_post and wp_insert_comment?

comment_post fires specifically when a comment is submitted through the standard front-end comment form. wp_insert_comment fires for every comment insert regardless of source — front-end submissions, programmatic inserts, imports — so it’s the one to use when you need to catch all of them.

What’s the difference between rest_pre_dispatch and rest_authentication_errors for blocking a request?

rest_pre_dispatch can short-circuit a request entirely, before routing, for conditions that apply broadly (maintenance mode, global rate limits). rest_authentication_errors is specifically about identity and access — and it can undo another check’s rejection if you don’t respect an existing non-null $result.

When should a dedicated team review comment handling on a site?

Before the volume of user-generated content makes manual review impractical — a community site, a membership platform, or any site where comments are a primary engagement feature benefits from a security pass on the full submission pipeline, not just the rendered output.

When should a dedicated WordPress team review a site’s REST API hook usage?

When multiple plugins or custom code hook into authentication and response-shaping on the same endpoints — at that point, tracing which check actually wins requires understanding the full pipeline order, which is exactly the kind of audit worth outsourcing rather than reconstructing from scratch under time pressure.

Why did my rest_authentication_errors check stop another plugin’s block from working?

Almost certainly because the filter didn’t check whether $result was already non-null before returning its own value — overwriting an existing WP_Error (or a success) with your own default silently replaces the other check’s decision.

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.

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.

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.

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.

Schema JSON-LD

Zero Duplication

Content-First

FSE Hub