I am having an overview of custom post types. These have custom tax and also an attachment.

In my overview I need to provide links to delete the entries. With that I also need to delete the attachment and the meta data.

I was using this:

    if ( !current_user_can( 'delete_bkroadkill', $post->ID ) )

    $link = "<a href='" . wp_nonce_url( get_bloginfo('url') . "/wp-admin/post.php?action=delete&amp;post=" . $post->ID, 'delete-post_' . $post->ID) . "'>".$link."</a>";
    echo $before . $link . $after;

I found Delete Post Link to delete post, its meta and attachments but there is no solution provided.

Which will not delete anything else than the post. What is the proper way to do this?

  • To the best of my knowledge there is no 'built-in' way of doing this, you'd need to build a way of doing it yourself. I'm not sure what your level of PHP/WP coding is, but essentially you'd have to run a query to find all attachments of the Post, another to delete them, another to find the Post meta, and yet another to delete them. I believe when you then delete the Post that WP handles the update of all the the Taxonomy tables for you, so there should be no action necessary there.
    – David Gard
    Feb 17, 2014 at 14:27
  • Post meta should be deleted when the post is deleted (but not when it is "trashed"). Attachments will be orphaned but not deleted. You will need to do that part yourself.
    – s_ha_dum
    Feb 17, 2014 at 14:30

2 Answers 2


@s_ha_dum suggests that Post meta will be deleted automatically. Therefore because his reputation suggests he knows what he is talking about, this solution only handles Post attachments.

I'd suggest checking out the docs for the before_delete_post() hook, as it's quite handy to be able to check out what Post Type is being deleted, etc.

add_action('before_delete_post', 'delete_post_attachments');
function delete_post_attachments($post_id){

    global $post_type;   
    if($post_type !== 'my_custom_post_type') return;

    global $wpdb;

    $args = array(
        'post_type'         => 'attachment',
        'post_status'       => 'any',
        'posts_per_page'    => -1,
        'post_parent'       => $post_id
    $attachments = new WP_Query($args);
    $attachment_ids = array();
    if($attachments->have_posts()) : while($attachments->have_posts()) : $attachments->the_post();
            $attachment_ids[] = get_the_id();

    if(!empty($attachment_ids)) :
        $delete_attachments_query = $wpdb->prepare('DELETE FROM %1$s WHERE %1$s.ID IN (%2$s)', $wpdb->posts, join(',', $attachment_ids));


An important note from the aforementioned docs -

It's important to note the hook runs only when the WordPress user empties the Trash. If you using this hook note that it will not fire if the user is deleting an Attachment, since attachments are force deleted, i.e., not sent to the Trash. Instead use the delete_post() hook.

Another note

I should mention that while the code in this answer will delete all rows from the database related to Post attachments, it will not actually delete the attachments themselves.

My reasoning for this is performance. Depending on the number of attachments that you have, deleting them one at a time could take a while. I suggest it is better to to only delete the database entries for all attachments initially to enhance the user experience, and then run some separate house keeping at another time to delete the actual attachments (it's easy enough to search for and delete and non-associated files). Basically, less queries + less work during the user experience = less time.

  • How would I check for the post type?
    – 4ndro1d
    Feb 17, 2014 at 14:58
  • I've updated my answer (obviously replace my_custom_post_type with the relevant Post Type). Also, check the link in the post for the before_delete_post() hook, it's fully explained in there.
    – David Gard
    Feb 17, 2014 at 15:13
  • This also needs a query for $wpdb->postmeta otherwise there will be orphaned values in the postmeta table that will never be deleted.
    – Howdy_McGee
    Jun 8, 2016 at 22:45

I use this to delete associated media with post. If you want to test against a certain post type you can include the global $post_type variable. Pretty much it gets all attachments and deletes them one by one. Reference

function delete_associated_media( $id ) {
    $media = get_children( array(
        'post_parent' => $id,
        'post_type'   => 'attachment'
    ) );

    if( empty( $media ) ) {

    foreach( $media as $file ) {
        wp_delete_attachment( $file->ID );
add_action( 'before_delete_post', 'delete_associated_media' );
  • While that works, I'd suggest making an array of ID's to delete, then constructing a custom query and running that. Your answer will run multiple queries to delete the attachments; perhaps not an issue for 3 or 4, but if you have 100 attachments it could slow things down quite considerably.
    – David Gard
    Feb 17, 2014 at 15:11
  • This code runs no more queries than yours. It queries once to get the attachments, then 1 query to delete them, all through WordPress instead of any custom SQL.
    – Howdy_McGee
    Feb 17, 2014 at 15:19
  • 1
    Incorrect. You are querying the database once to get all attachments and then once per attachment when you delete them; 10 attachments = 11 queries in total. I query once to get all of the attachments and once to delete all of the associated posts; 10 attachments = 2 queries. It should be noted however that your code also deletes the file, where as mine merely removes the offending row from the database. I guess that is personal preference, but for performance I'd do it my way and then run some housekeeping to remove non-associated attachments (a simple plugin could handle that).
    – David Gard
    Feb 17, 2014 at 15:27
  • I see what you're saying. I thought the point was to remove the attachments entirely when a post was deleted.
    – Howdy_McGee
    Feb 17, 2014 at 15:37
  • The OP may have meant that, I guess it depends on how you interpret it. From past experience I've found that keeping potentially time consuming housekeeping tasks away from users contributes to a better experience, hence I interpreted it that way.
    – David Gard
    Feb 17, 2014 at 15:46

Not the answer you're looking for? Browse other questions tagged or ask your own question.