wppaste
WordPress

wp_delete_attachment( int $post_id, bool $force_delete = false ): WP_Post|false|null

Since
2.0.0
Source
wp-includes/post.php:6814
Trashes or deletes an attachment.

Description

When an attachment is permanently deleted, the file will also be removed.
Deletion removes all post meta fields, taxonomy, comments, etc. associated with the attachment (except the main post).

The attachment is moved to the Trash instead of permanently deleted unless Trash for media is disabled, item is already in the Trash, or $force_delete is true.

Compatibility

WordPress
since 2.0.0
PHP
7.4–8.6-dev
  • 6.7.7
  • 6.8.8
  • 6.9.7
  • 7.0.4
  • 7.1.0

Present in every tracked release (6.7.7 to 7.1.0), and compiles on PHP 7.4 through 8.6-dev.

Parameters

$post_idint
Attachment ID.
$force_deletebooloptional
Whether to bypass Trash and force deletion.
Default false.Default: false

Return value

WP_Post|false|null
Post data on success, false or null on failure.

Performance profile

How much work a call to wp_delete_attachment() does, and what it touches: the algorithmic scaling, the Zend instruction count per call across PHP versions, the hooks it hands control to, and the core code that calls it. Measured from the compiled opcodes, not a stopwatch, so every number is identical on any machine running the same PHP version, and every function in core is ranked by cost.

Cost class
Heavy

Reaches the database via ->get_row().

Scaling
Scales with input

The body loops, so the work grows with what you pass in.

Instructions
17–164

Executed per call on PHP 8.5, depending on the branch taken. The body compiles to 182.

Plugin surface
4 hooks

Third-party callbacks on 'pre_delete_attachment', 'delete_attachment', 'delete_post' run inside this call, and their cost is not bounded by anything here.

Called by
4

4 places in core call this, so the cost is paid more often than your own code shows.

What it touches

  • querycontent queryget_post()called directly
  • hookthird-party callbacksapply_filters()called directly
  • sqldatabase query->get_row()called directly
  • transienttransientget_transient()one call below wp_delete_attachment()
  • serializeserialisationmaybe_serialize()one call below wp_delete_attachment()
  • cacheobject cachewp_cache_delete_multiple()one call below wp_delete_attachment()

Further down the call graph this can also reach option. That is the worst case, several calls deep and usually down an error path, not what a normal call pays.

What one call costs · 8 distinct outcomes

One number would be a lie: the work depends on which branch runs. These are every distinct cost wp_delete_attachment() can have, taken from its control-flow graph on PHP 8.5.

WhenInstructionsCalls it makes
always17->prepare(), ->get_row()
always24->prepare(), ->get_row(), get_post()
$check !== null34–41->prepare(), ->get_row(), get_post(), apply_filters()
always35->prepare(), ->get_row(), get_post(), wp_trash_post()
$check === null134–147->prepare(), ->get_row(), get_post(), apply_filters(), delete_post_meta(), delete_post_meta(), wp_get_attachment_metadata(), get_post_meta(), get_attached_file(), is_multisite(), do_action(), wp_delete_object_term_relationships(), get_object_taxonomies(), wp_delete_object_term_relationships(), delete_metadata(), wp_defer_comment_counting(), ->prepare(), ->get_col(), wp_defer_comment_counting(), ->prepare(), ->get_col(), do_action(), ID()
$check === null && is_multisite() && is_string($file) && !empty($file)141–150->prepare(), ->get_row(), get_post(), apply_filters(), delete_post_meta(), delete_post_meta(), wp_get_attachment_metadata(), get_post_meta(), get_attached_file(), is_multisite(), clean_dirsize_cache(), do_action(), wp_delete_object_term_relationships(), get_object_taxonomies(), wp_delete_object_term_relationships(), delete_metadata(), wp_defer_comment_counting(), ->prepare(), ->get_col(), wp_defer_comment_counting(), ->prepare(), ->get_col(), do_action(), ID()
$check === null148–161->prepare(), ->get_row(), get_post(), apply_filters(), delete_post_meta(), delete_post_meta(), wp_get_attachment_metadata(), get_post_meta(), get_attached_file(), is_multisite(), do_action(), wp_delete_object_term_relationships(), get_object_taxonomies(), wp_delete_object_term_relationships(), delete_metadata(), wp_defer_comment_counting(), ->prepare(), ->get_col(), wp_defer_comment_counting(), ->prepare(), ->get_col(), do_action(), ID(), do_action(), wp_delete_attachment_files(), clean_post_cache()
$check === null && is_multisite() && is_string($file) && !empty($file)155–164->prepare(), ->get_row(), get_post(), apply_filters(), delete_post_meta(), delete_post_meta(), wp_get_attachment_metadata(), get_post_meta(), get_attached_file(), is_multisite(), clean_dirsize_cache(), do_action(), wp_delete_object_term_relationships(), get_object_taxonomies(), wp_delete_object_term_relationships(), delete_metadata(), wp_defer_comment_counting(), ->prepare(), ->get_col(), wp_defer_comment_counting(), ->prepare(), ->get_col(), do_action(), ID(), do_action(), wp_delete_attachment_files(), clean_post_cache()

Across PHP versions

Compiles the same on PHP 7.4, 8.1, 8.2, 8.3, 8.4, 8.5 and 8.6-dev: 182 instructions, 17–164 executed per call, 15 branches. The work does not change between versions.

An instruction is not a fixed amount of time, so a matching count is not necessarily the same speed; what it rules out is a difference in the work itself.

Hooks and filters fired · 4

4 hooks fire while wp_delete_attachment() runs, in this order:

  1. apply_filters( pre_delete_attachment )filterline 6842 (+28 into the body)

    Filters whether an attachment deletion should take place.

  2. do_action( delete_attachment )actionline 6867 (+53 into the body)

    Fires before an attachment is deleted, at the start of wp_delete_attachment().

  3. do_action( delete_post )actionline 6890 (+76 into the body)

    Fires immediately before a post is deleted from the database.

  4. do_action( deleted_post )actionline 6896 (+82 into the body)

    Fires immediately after a post is deleted from the database.

Uses · 18

Show all 18

Used by · 4

Source code

function wp_delete_attachment( $post_id, $force_delete = false ) {	global $wpdb; 	$post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d", $post_id ) ); 	if ( ! $post ) {		return $post;	} 	$post = get_post( $post ); 	if ( 'attachment' !== $post->post_type ) {		return false;	} 	if ( ! $force_delete && EMPTY_TRASH_DAYS && MEDIA_TRASH && 'trash' !== $post->post_status ) {		return wp_trash_post( $post_id );	} 	/**	 * Filters whether an attachment deletion should take place.	 *	 * @since 5.5.0	 *	 * @param WP_Post|false|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;	} 	delete_post_meta( $post_id, '_wp_trash_meta_status' );	delete_post_meta( $post_id, '_wp_trash_meta_time' ); 	$meta         = wp_get_attachment_metadata( $post_id );	$backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );	$file         = get_attached_file( $post_id ); 	if ( is_multisite() && is_string( $file ) && ! empty( $file ) ) {		clean_dirsize_cache( $file );	} 	/**	 * Fires before an attachment is deleted, at the start of wp_delete_attachment().	 *	 * @since 2.0.0	 * @since 5.5.0 Added the `$post` parameter.	 *	 * @param int     $post_id Attachment ID.	 * @param WP_Post $post    Post object.	 */	do_action( 'delete_attachment', $post_id, $post ); 	wp_delete_object_term_relationships( $post_id, array( 'category', 'post_tag' ) );	wp_delete_object_term_relationships( $post_id, get_object_taxonomies( $post->post_type ) ); 	// Delete all for any posts.	delete_metadata( 'post', null, '_thumbnail_id', $post_id, true ); 	wp_defer_comment_counting( true ); 	$comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d ORDER BY comment_ID DESC", $post_id ) );	foreach ( $comment_ids as $comment_id ) {		wp_delete_comment( $comment_id, true );	} 	wp_defer_comment_counting( false ); 	$post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $post_id ) );	foreach ( $post_meta_ids as $mid ) {		delete_metadata_by_mid( 'post', $mid );	} 	/** This action is documented in wp-includes/post.php */	do_action( 'delete_post', $post_id, $post );	$result = $wpdb->delete( $wpdb->posts, array( 'ID' => $post_id ) );	if ( ! $result ) {		return false;

Changelog

Introduced in 2.0.0. Unchanged from 6.7.7 through 7.1.0.

  1. 6.7.7
  2. 6.8.8
  3. 6.9.7
  4. 7.0.4
  5. 7.1.0

Signature, return type and hooks compared across 5 parsed releases.

About this page

Parsed data
Generated from the wordpress-develop 7.1.0 tag, from src/wp-includes/post.php, and regenerated for each WordPress release so it tracks the code rather than a snapshot of it.
Corrections
Something wrong on this page? Report it and it gets fixed in the next regeneration.