Internal linking is one of those SEO tasks everyone knows matters but almost nobody does consistently.
I’ve written enough content to know the pattern: you publish a post, forget to link it to anything, and six months later it’s sitting in a silo – no internal links in, no internal links out. Google has a hard time understanding its importance. So do visitors.
I tried fixing this manually. Then I tried a few plugins. None of them worked the way I wanted. So I built my own.
Here’s the full story – from the frustration that started it, to the logic behind the build, to what it actually does today.
The Problem With Internal Linking at Scale
When you have 10 posts, internal linking is easy. You just remember where things are.
When you have 50, 100, or 300+ posts? It becomes a mess. You’re writing a new article, and somewhere in the back of your head you know you’ve covered something relevant before – but you can’t remember the exact URL, the exact post title, or whether you’ve already linked to it from this page.
The result is inconsistent linking. Some posts get linked to constantly. Others are barely touched. Your site architecture ends up reflecting the order you wrote things in, not any kind of deliberate SEO strategy.
There are plugins that try to solve this. I tested a few. The common issues I ran into:
- They linked the same URL multiple times in a single article (looks spammy, hurts UX)
- They inserted links inside headings or image alt text (breaks markup, looks wrong)
- They matched short keywords too aggressively (e.g., “SEO” linking everywhere when you only wanted “local SEO” to link to one specific page)
- They didn’t support keyword variations (you’d have to pick one keyword per URL, not “local seo, what is local seo, local search optimization”)
- They were slow, or their settings UI was clunky
So I decided to build something myself.
The Core Idea
The concept is simple: define a list of keywords, pair each one with a URL, and let the plugin scan your content and automatically insert links wherever those keywords appear.
But the execution required a few important design decisions that make the difference between a plugin that’s helpful and one that creates more problems than it solves.
Here’s what I wanted:
1. Multiple keyword variations per URL A page about local SEO might get traffic from people searching “local seo,” “what is local seo,” “local search optimization,” or “local seo definition.” I want all of those to link to the same page – not define four separate rules.
2. One link per URL per article If an article mentions “local seo” five times, it should only be linked once – the first time it appears. Linking the same URL repeatedly is noisy and offers no SEO benefit after the first occurrence.
3. Longest match first If I have rules for both “seo” and “ecommerce seo,” and the text says “ecommerce seo,” I want the longer, more specific phrase to match – not the short one. This is critical for avoiding incorrect linking.
4. Skip headings, existing links, and image alt text A link inside an H2 looks terrible and can cause issues. A link inside an existing anchor creates invalid HTML. Alt text is for screen readers and search engines – not for hiding hyperlinks. All of these should be skipped.
5. One-way linking only Links should go from posts and custom post types to any content – not the other way around. Pages and archives shouldn’t start sprouting auto-links into your blog posts. That leads to chaotic site architecture.
6. Case-insensitive matching “SEO Audit,” “seo audit,” and “Seo Audit” should all match the same rule without any extra setup.
The Configuration Format
I wanted the configuration to be readable and editable by a human – not buried in a database table that requires a UI to interact with.
The format I landed on is one rule per line:
keyword1, keyword2, keyword3 | /relative-url/
For example:
local seo, what is local seo, local search optimization | /local-seo/
shopify seo audit, shopify seo checklist, shopify seo best practices | /shopify-seo/shopify-seo-audit-checklist/
ecommerce seo, what is ecommerce seo | /ecommerce-seo/

Note: The links should be pasted as relativeUrl instead of fullpathUrl if you are using staging or QA environments.
This is clean, portable, and easy to update in bulk. You can paste it into a spreadsheet, edit it, and paste it back. You can version control it. You can hand it to someone else and they’ll immediately understand what it does.
The plugin reads this configuration, parses it into keyword-URL pairs, sorts them by keyword length (longest first), and uses that sorted list to scan content.
How the Matching Works
When a post is loaded, the plugin:
- Parses the post content into an HTML structure
- Iterates through text nodes (skipping tags like
<a>,<h1>–<h6>, and anything inside image alt attributes) - Checks each text node against the sorted keyword list
- For the first match found, wraps the keyword in an anchor tag pointing to the paired URL
- Marks that URL as “used” for this article – so it won’t be linked again, even if a different keyword variation appears later
The “longest match first” sorting is important here. If the keyword list includes both “seo” and “shopify seo,” and the content says “shopify seo is important,” the plugin will match “shopify seo” first because longer strings are checked before shorter ones.
Without this, you’d end up with “seo” being linked inside the phrase “shopify seo” – which links to the wrong page and breaks the intent of the rule.
What It Links and What It Doesn’t
The plugin only processes posts and custom post types. Regular pages, category archives, tag pages, and author pages are excluded.
Links can go to anything – posts, pages, categories, tags, external URLs if needed. But they only originate from blog-style content.
This matches how I think about site architecture. Your posts are content. Your pages are destinations. Auto-linking from pages into your content creates weird circular logic and can dilute the authority flow you’re trying to build.
The plugin also ignores:
- Text inside existing
<a>tags (no double-linking) - Text inside
<h1>through<h6>tags - Image
altattributes - Any content that’s already been processed in the same request
The Results After Running This Across My Content
After setting this up and running it across a content library with a few hundred posts, a few things became immediately obvious:
A lot of posts were getting their first real internal links. Posts I’d written six months ago and forgotten about were suddenly connected into the broader content structure. Their crawl depth decreased. They started getting more impressions.
The linking was consistent. Every article about Shopify SEO automatically linked to the right pillar pages. I didn’t have to manually remember to do it every time I wrote something new.
The site architecture started to reflect my actual content strategy. Pillar pages accumulated links from supporting posts. Topic clusters started to make sense in Google Search Console’s internal link report.
It saved a significant amount of time. Writing a new article no longer required me to manually scan through all my existing pages to find what I should link to. The plugin handles it in the background.
The Limitations I’m Aware Of
This approach isn’t perfect. A few things worth knowing:
It can’t judge context. If a post is about a topic only tangentially related to a keyword, the plugin will still link it. You have to be thoughtful about which keywords you include in the list – don’t add “seo” by itself unless you want it linked everywhere.
It links the first occurrence, which isn’t always ideal. Sometimes the keyword appears in an awkward spot early in the article. The plugin links it anyway. You can always override with a manual link earlier if needed.
It doesn’t retroactively update old links. If you change a URL, you need to update the configuration. The old links in published content won’t update automatically.
Careful with very short keywords. Single-word or two-word keywords can be too aggressive. “SEO” will match in almost every post on an SEO site. Be specific.
Final Thoughts
This plugin started as a personal fix for a personal problem. I was tired of inconsistent internal linking, tired of posts sitting in silos, and tired of plugins that were either too aggressive or too limited.
Building it from scratch meant I could make every design decision intentionally – one link per URL per article, longest match first, skip headings and existing links, posts only.
If you’re managing a content-heavy site and internal linking feels like a task you’re always behind on, the underlying logic here is worth thinking about. Whether you build something, use a plugin, or set up a manual workflow – the goal is the same: make sure every page on your site is connected to something that matters, and that connection happens consistently, not just when you remember to do it.
That’s the whole idea. Execution is just the details.
Disclaimer: This article was written with the help of AI. The ideas, experience, and lessons shared here are entirely my own.
Author Profile

- I’m an SEO Manager with 7+ years of experience helping brands grow through data-driven strategies. Passionate about the intersection of search, content, and technology, I blend technical SEO, analytics, and creativity to drive performance and build meaningful digital experiences.
Latest entries
BlogApril 11, 2026How I Use Query Fan-Out to Write Better Content (And Built a Tool for It)
BlogApril 11, 2026I Managed 40+ SEO Clients. Google Sheets Broke. Here’s the Notion Template I Built to Fix It.
BlogMarch 29, 2026I Built an Agency Website in Bricks Builder as a Non-Developer. Here’s What Happened.
BlogMarch 10, 2026I Built My Own WordPress Auto Internal Linking Plugin – Here’s Why and How






