Remove Existing Self Linking Images in WordPress



Ahh, the self linking image, the bane of so many WordPress users. So, you have a site with a ton of content where unscrupulous publishers, admins, and contributors left the Attachment Display Settings “Link To” option to its dreaded default. Sure, you can prevent that moving forward and stop automatically hyperlinking images, but what about your existing content that’s riddled with self linking images (dare I call them “WP Selfies”)? I threw together a quick function—a dangerous function if used incorrectly—to remove self linking images from your existing content once and for all.


In case you missed that ^ please backup your database before using this snippet. That backup is the only way to undo this action. The action is even commented out in the snippet because you should only run this once and very deliberately (you’ll need to uncomment it when you’re ready). It could take a while depending on the amount of content you have. The site I used it on had ~500 posts and it took around 30 seconds.

This snippet parses each post’s or page’s content one by one. It looks for any img inside an anchor tag and if the image URL is the same as the anchor’s href URL then it replaces the entire anchor with just the img tag that was within it.

It’s not perfect but it got the job done for me. If you want to suggest improvements then please leave a comment.


  1. I ran it and it seemed to work on some of the files but not all of the post. did I remove the code to early? Should I run it again?

      1. Ive let it run overnight. It hasn’t made any difference. I guess I took it out to early the first time. I have roughly 70 blog post. some only have a few images some have up to 50. I am grateful for the portion that worked. I guess ill have to do the rest manually unless you have any ideas

        1. This may be a limitation of your web host or PHP; you could try adjusting your PHP max_execution_time or changing the posts_per_page and offset parameters and run the query on every 10 posts; or clone your site and run the query locally then replace your remote db; or translate the query into an SQL search/replace pattern.

          1. Im on Siteground (host) php 7. im not sure how to add that code. i put post per page 200 and added offset 1 after the fields line and it broke. I had to restore the site

            1. After 'ids' you’ll need to add a comma, then on the next line is where you would add your offset parameter. So start off changing posts_per_page => 10 and offset => 0. Run that once, then change to offset => 10, run again. Keep adding 10 (or whatever number you put in posts_per_page) to the offset each time you run it to check the next grouping of (10) posts.

              1. ok I ran this and upped the offset by 20 at a time. I dont see it making any difference. I am chaning the number and hitting update. do I need to comment out the code each time inbetween?

                ‘posts_per_page’ => 20,
                ‘post_status’ => ‘any’,
                ‘fields’ => ‘ids’,
                ‘offset’ => 300

                1. You should not need to comment the code after each run. However, at this point I’ve probably helped about as much as I can without access to your hosting/server environment. The best advice I can give you is to keep running it, check your server logs for errors, and possibly increase PHP’s max_execution_time. Or do some Googling and figure out a way to run the query directly with SQL. I’ve been meaning to do that, but if you beat me to it I’d love to see what you come up with!

                2. This just dawned on me: you could also export the database, then download it, and use an IDE with a regex search feature (like VSCode) then reupload your db. It would take some trickery, but it can likely be done and you can eliminate your server as the middleman for executing this expensive query.

  2. Hello! This looks like a very useful script, but unfortunately I seem to be having difficulty getting it to properly work with my site, which is running on WordPress 4.9.8 on a VPS server (with plenty of cpu & memory).

    I added the code to my theme’s functions.php file and the script started doing it’s thing, but after about 5 minutes my server started getting slower and slower, until it became unreachable (I was still able to ssh in) all together. Restarting nginx and the MySQL server didn’t seem to break the spell, and the only thing that unfroze the sever was dropping my database’s tables and restoring them from a backup that I took just before running the script.

    My site is quite large, with around 1400 posts, and I would estimate that I have roughly 4000 self linking images spread throughout those posts. Curiously, the script appears to have fixed some of the self linking images, but no where close to all of them, even after I let the script have free reign (making the website inaccessible while doing so) for more than an hour.

    Any ideas for things that I might try? Thanks for creating this script and thanks in advance for any ideas that you might have!

    1. Hi Steve, thanks for the kind words. You could try making use of the offset parameter in your query and change posts_per_page to a set number, to only work on, say 500 posts each time you run the loop. If 500 gives you trouble then try fewer posts until you find a number that works, then use offset to complete the remaining loops.

      1. Hi Michael, many thanks for the prompt response and for the helpful suggestions. Just to ensure that I’m understanding correctly, I would want to edit line 6 of the script to say the following:

        'posts_per_page' => 500

        And then add a new line immediately below line 6, with the following:

        'offset' => 1

        And then after running the script within the functions.php file, I would edit the ‘offset’ to say 500, 1000, 1500, etc. increasing the offset by 500 (or whatever value ‘posts_per_page’ is set to, each time the script is run.

        Is my understanding correct? Thanks again!

        1. offset is actually the “number of post to displace or pass over.” So start with offset => 0 and posts_per_page => 500, then try offset => 500, then offset => 1000, etc. Let me know how you fare.

          1. Hello again Michael, thanks for explaining the offset in greater detail. I went ahead and tried changing posts_per_page to 500 and still experienced a server meltdown, and so I thought I’d set posts_per_page all the way down to 20 and unfortunately I still experienced a server meltdown in that instance as well. I’m not sure why, but my server doesn’t seem to cooperating. 🙁 Thanks anyways for the script and for your input. If you can think of anything else that might be worth a try, I’m game.

            1. At this point you’ll need to do some digging on your server and find the bottleneck. Check your server logs, PHP logs, and db logs.

  3. Thanks for this advice.


    Your PHP code changes were rolled back due to an error on line 27 of file wp-content/themes/x-child/functions.php. Please fix and try saving again.
    syntax error, unexpected '<'

    When removed it says same for P

    1. It’s possible that you copied the opening PHP tag (the “<?php”) which you shouldn’t need to do if you’re adding this to the bottom of your functions.php file. Seems like that would cause an error like this.

  4. Question: Can this be modified to remove ALL links from ALL images in every post? I imported a blog from blogger and am left with a ton of images that are still linked to the old site (so they are technically not ‘selfies’), every link also has a unique identifier for some reason so I can’t just swap out the url with a plugin just want to delete them all and leave them as ‘none’.

  5. This is a super useful snippet.

    It seems to work for me, however the load on my site stayed high even once it was complete. Even 24 hours later. The only way I could get load under control was to revert my wp_posts table from a backup. I even tried running the query a second time and the exact same thing happened.

    Any idea what could cause such strange behavior?

    I noticed that there were many “sleeping” MySQL processes that remained after running the snippet, up until I’d restore the table.

    Very strange!

    And a suggestion: how about using admin_init instead of init so that the snippet isn’t triggered to run over and over on sites that are highly trafficked?

    1. Thanks for the great suggestion, I immediately changed the hook to admin_init—really good idea.

      As for sleeping MySQL processes, it’s tough to say without actually seeing them. A workaround might be to run the script and then immediately upon completion export/dump the database which should be free of the self-linking images (WP Selfies), drop all tables as you would if you were restoring the original backup, and then instead of restoring the backup you can restore the export you made without the WP Selfies.

      Let me know if you have success with that and thanks again for the improvement!

  6. Followed all directions and received the following error:

    Fatal error: Call to undefined function get_current_site()

    Any idea what’s wrong?

    1. Seems that get_current_site() is just for WordPress multisite installations. Is there a way to run this without that method call?

    2. Sorry for the rapid-fire comments, but I ended up getting it to work. I found another (old?) version of the code here and was able to follow your directions above to get it to work.

      1. Thanks for the heads up about this Alex, it totally slipped under my radar. That old code doesn’t check the domain name of the WP selfies, which in some edge cases will remove WP selfies that may link to external sites. I now removed this function and was going to revise the check, but I still believe that even those edge cases are WP selfies and should be removed, so the old Gist code is now restored. If anyone has an issue related to edge cases like that then let me know and I can try to help if you’re stuck. Thanks again Alex!

  7. Be really careful with this script. It replaces ALL images that are linked. Not just the images that are linked to external WordPress pages on your own domain.

    1. You’re right, nice catch! I added an additional check (line 27) to ensure it only replaces images that are linked to the same domain.

  8. Great function. Thanks! 🙂
    But how exactly are you supposed to know when the function is finished?

    When I uncommented the action, my site was offline and the processes mysqld and php-fpm were almost at 100% hogging resources. The only way I could get my site online again (after 5 minutes for approx. 430 posts), was by commenting the action again. Is this the way it is supposed to work? How do you know when it is finished?

    1. This script only needs to run once. After that you can remove it. Depending on the number of posts you have, the script will run longer and may result in a “timeout”, meaning that your browser will stop waiting and show you a blank page. That doesn’t mean the script failed. The script will probably continue to run for a while (like in seconds) until it’s finished, but your browser decides not to wait any longer for a response (your browser is confused and doesn’t understand why it takes so long).

  9. Hello Michael,

    Many thanks for your solutions which saved us loads of time!

    For people who meet troubles to make it work, just few reminders:
    – DB backup
    – Copy the code to your functions.php file (Child theme)
    – When ready, uncomment the last line (//action… )
    – Save your file

    For us, it took about 1m30s for about 1800 posts. (WP 4.2.4)


    (and please don’t forget your DB backup)

  10. Hello, thanks for this post it’s very helpfull, but I haven’t been able to make it work. I copied this content on my function.php (Child theme) however nothing happends.
    Can someone help me with this problem, Thanks in advance.

  11. Thank you for publishing this. I just wrote a post on removing image links in WordPress. Your solution is solution Nr. 4, the only one working on existing posts with image links!

Leave a Comment