Hooks

78

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Why did my content disappear?

A the_content callback must return the string. Echoing instead of returning wipes the post body.

Used in

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

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

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

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

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

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

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

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