## Updated answer for WordPress 5.5.0+ As of 5.5.0, a new filter has been added to `wp_delete_attachment`: ```php /** * Filters whether an attachment deletion should take place. * * @since 5.5.0 * * @param bool|null $delete Whether to go forward with deletion. * @param WP_Post $post Post object. * @param bool $force_delete Whether to bypass the Trash. */ $check = apply_filters( 'pre_delete_attachment', null, $post, $force_delete ); if ( null !== $check ) { return $check; } ``` This "short-circuit" filter is before _almost_ any meaningful action is made, save for trashing the attachment if certain conditions are met. A cleaner approach would now be: ```php function action_maybe_delete( $id ) { echo "Let's not delete Attachment id: " . $id ; // prevent the attachment from actually being deleted if( 1 == 1 ) { // made up condition return false; } }; // add the action add_action( 'pre_delete_attachment', 'action_maybe_delete', 10, 1 ); ``` - Returning anything other than `null` from this will prevent `wp_delete_attachment` from going any further, and instead the result of the filter will be returned. --- ## Old Answer from pre 5.5.0 First, let's take a look at where `delete_attachment` is fired in core. This happens on line `5062` of `wp-includes/post.php` in the [wp_delete_attachment](https://developer.wordpress.org/reference/functions/wp_delete_attachment/) function. Here's the relevant snippet: /** * Fires before an attachment is deleted, at the start of wp_delete_attachment(). * * @since 2.0.0 * * @param int $post_id Attachment ID. */ do_action( 'delete_attachment', $post_id ); wp_delete_object_term_relationships($post_id, array('category', 'post_tag')); wp_delete_object_term_relationships($post_id, get_object_taxonomies($post->post_type)); We can see that WordPress core is calling `do_action` - this method does not care what's returned; unlike `apply_filters`, `do_action` simply fires a hook and then processing continue despite any return values. Looking further at the source of `wp_delete_attachment`, there doesn't appear to be a way to "short-circuit" this process and prevent deleting the attachment. **Except**, technically, you could use PHP's `die` or `exit` statements to end processing of the script, which _should_ effectively prevent the file from being deleted. function action_maybe_delete( $id ) { echo "Let's not delete Attachment id: " . $id ; // prevent the attachment from actually being deleted if( 1 == 1 ) { // made up condition die; // Prevent the script from continuing. } }; // add the action add_action( 'delete_attachment', 'action_maybe_delete', 10, 1 ); ?php