There’s a moment, usually year three, when a WordPress site starts to feel like it’s wearing someone else’s coat. The admin loads slowly. A plugin you’ve never heard of wants to update. The staff wiki has a page titled “if the events page breaks, check this first.” The site hasn’t gotten worse - you’ve just accumulated the cost of every decision nobody quite made.
We’ve built and kept WordPress sites for clients since 2013. Five-year sites, ten-year sites, sites that outlived the agency that referred them to us. Every one of them runs on the same short answer to the plugin question: there’s a short list we trust, and everything outside it is a decision, not a reflex. The pile isn’t the plugins we chose. It’s the ones nobody chose.
The rule
The short list is the default. Anything beyond it needs a reason we can say out loud - “the client’s checkout needs it,” “it replaces two hundred lines we’d otherwise maintain.” If nobody can say the reason, the plugin doesn’t go in.
This isn’t plugin minimalism for its own sake. Our sites run a dozen-plus plugins each, and every one earns its keep. The discipline is about keeping the unchosen ones out - the plugin a content person installed to reorder some cards, the three a previous agency left behind, the “speed optimizer” someone found on a Tuesday.
Why the pile grows.
Plugins are free, and free things accumulate. A content person hits a wall - “I wish I could reorder these” - and the fastest path is a plugin. It works. They move on. No one tells the developer. Three years and fifteen plugins later, nobody on the team can confidently say what half of them do, and the pile has quietly become the site.
The second driver is agency handoffs. You hire someone to build a WordPress site; they want to finish fast. Every requirement that’s within a plugin’s reach becomes a plugin. The site ships, the agency moves on, and you inherit third-party code none of them wrote and none of them will maintain.
The third driver, honestly, is fear. A line of custom code feels permanent in a way a plugin doesn’t. If the plugin breaks, you can swap it. If your code breaks, you have to understand it. That instinct is backwards - the plugin is usually the harder thing to understand, because it has a roadmap you don’t control.
The plugin is the harder thing to maintain, because it has a roadmap you don’t control.
How to tell if you already have a pile.
You don’t need to count. You need to ask, of each active plugin, a single question: if this vanished tonight, would we know why we’d added it? A few reliable tells that the answer is no:
- Two plugins doing one job. Two SEO plugins, two caching layers, an image optimizer stacked on a second image optimizer. Each was added by someone who didn’t know the first one existed.
- A plugin nobody dares deactivate. “Don’t touch that one, something breaks.” That’s not a plugin, that’s undocumented load-bearing code with a stranger’s name on it.
- Last updated: two years ago. An abandoned plugin is a security incident with a countdown you can’t see. In the admin, sort by “last updated” and read from the bottom.
- A plugin for something that is one line of PHP. “Insert headers and footers,” “hide the admin bar,” “change the login logo.” Convenience plugins that each add an update, a settings page, and an attack surface to save you a snippet.
Run that pass once a year and the pile stops being a mystery. It becomes a list, and a list can be shortened.
The short list we actually run.
This is close to the same list on every site we ship. It’s boring on purpose.
- Advanced Custom Fields (Pro). The backbone. Content structure lives in ACF field groups and Flexible Content, so editors compose pages from real, brand-safe modules instead of a freeform builder. It’s the closest thing WordPress has to a schema.
- Contact Form 7 + CFDB7. Forms, with every submission stored in the database, not left to live only in an email that may or may not have arrived.
- Yoast SEO (Premium on the bigger sites). Titles, meta, sitemaps, redirects. We turn the “content analysis” nagging off on every install.
- FacetWP (with Relevanssi where search matters). Faceted filtering - the thing that turns a three-thousand-product catalogue into something a human can actually browse.
- WooCommerce. When there’s a shop.
- WP All Import / Export Pro, with the ACF and WooCommerce add-ons. Because catalogues arrive as spreadsheets and leave as spreadsheets, and nobody is hand-entering three thousand products.
- Wordfence + Sucuri. Security in two layers: a firewall and login hardening (Wordfence) plus an external integrity scanner (Sucuri).
- BackWPup. Backups - though it has aged badly, and we’re actively moving off it. The nightly server-level snapshots are the real safety net either way.
- WP Media Folder + WebP Express. A media library a human can organise, and automatic WebP without anyone thinking about it.
- WP Mail SMTP, plus the usual small, boring utilities - Redirection, Regenerate Thumbnails, Duplicate Post, an explicit post-order plugin. Each does one job.
That’s the shape of it. Everything else is a conversation.
What we don’t install.
No page builders. Not Elementor, not Divi, not WPBakery - none of them, on any site we’ve shipped. A page builder solves the “let a content person build any layout” problem by handing them a way to build a layout that doesn’t match the brand and can’t be maintained. ACF Flexible Content solves the same problem with a set of modules we designed, so the pages editors build are the pages we’d have built.
No caching plugin, either. Caching is a hosting-layer job for us - server config, object cache, a CDN - not a plugin running an auction on every page render. When the host is set up the way a developer would set it up, the “speed optimizer” plugins have nothing left to do.
What we build ourselves.
The short answer: anything that’s actually yours. In practice, three things.
The theme.
Always custom, on our own base (themway, now gotheme). The theme is the site - not a thin shell wrapped around a pile of someone else’s code. A new developer can read it in an afternoon, which is the quiet test of whether a codebase will survive a change of hands.
The domain, in ACF.
Post types and fields that mirror the client’s world - grain dryers and silos, steel grades and alloy families, funeral notices - so the admin speaks their language instead of WordPress’s. This is where “we could just use a plugin” becomes “a custom post type with four fields,” and the four-field version is the one still standing in year five.
Integrations, isolated.
Every third-party system gets its own small plugin or class: a two-way Recman sync, a signed-enquiry-to-PDF flow, a GeoIP redirect, a multilingual import/export across a Multisite network. The trick is that the rest of the site never talks to the vendor directly. It talks to a small interface we own:
interface Ats {
public function pushCandidate(Candidate $c): void;
public function pullJobs(): array;
}
// One file per vendor. Everything else in the app only ever sees Ats,
// so when Recman changes its API - and it will - one file changes.
final class RecmanAts implements Ats { /* ... */ }
That single seam is the difference between a vendor’s API change being a one-file edit and being a week of firefighting. It’s the same discipline we bring to the Laravel and Vue side of what we do - isolate the thing you don’t control, own the thing you do.
Auditing a pile you inherited.
Most WordPress sites don’t come to us clean. They come to us as a takeover, and the first month is nearly always the same work. If you want to do it yourself, the order that works:
- List what’s actually active. Not what’s installed - what runs.
wp plugin list --status=activefrom WP-CLI, or the active list in the admin. Installed-but-inactive plugins are clutter; active ones are risk. - Sort by last update and read from the bottom. Anything not touched in a year gets a flag. Anything not touched in two years gets a replacement plan.
- Give every survivor a one-line reason. If you can’t write the reason, you’ve found something to remove. This is the same rule as day one, applied backwards.
- Replace, don’t just delete. A plugin doing real work gets replaced by one from the short list, or by a few lines of custom code, before it comes out - never a feature quietly disappearing on a Friday.
We’ve done this enough times that the shape is predictable: a site arrives with thirty-something plugins and leaves with a short list, the admin gets faster, and the next PHP upgrade stops being a negotiation.
The dialogue with the client.
None of this works if plugins are being installed quietly on the side. The way we handle it is honest and slightly annoying: at kickoff we explain the short list, and we say we’ll push back on requests that reach for something outside it. Not block - push back. The client can always override us.
In practice, about four out of five of those requests turn into something smaller. “We need a plugin to reorder team members” becomes a five-minute ACF change. “We need a plugin for events” becomes a custom post type with three fields. “We need a plugin for the newsletter signup” becomes a handful of lines wired to the service they already pay for.
The fifth one gets a plugin, and it joins the list for a reason we wrote down.
What this buys you, at year five.
Sites we’ve built this way age differently. The admin stays quick because there’s no plugin auction on every render. Upgrading PHP is uneventful because there are no abandoned plugins pinning us to an old version. And when a client comes to us after an agency handoff, that predictable first month - reading the pile, taking it back down to a list - is the whole reason the site gets to have a quiet year five at all.
None of this is clever. It’s a short list we trust, a reason for anything outside it, and the discipline to write the parts that are actually yours. If you’re staring at a plugin pile and can’t remember choosing most of it, that’s not a WordPress problem. It’s a maintenance problem, and it’s the kind of thing we fix.
Common questions
Do you use a page builder like Elementor or Divi?
No. On every site we have shipped, we build a custom theme and structure content with ACF Flexible Content, so editors compose pages from modules we designed. No Elementor, Divi or WPBakery.
Which SEO plugin do you use?
Yoast, Premium on the larger sites, for titles, meta and sitemaps. Everything else about SEO is structure, content and page speed, which is work, not a plugin.
How do you handle forms without a form-builder plugin?
Contact Form 7 with CFDB7, which stores every submission in the database instead of only emailing it. For complex, multi-step or signed forms we add Formidable or write the logic ourselves.
Do you use the Gutenberg block editor?
For page structure we use ACF Flexible Content, and several sites run the Classic Editor. The goal is that a content editor composes brand-safe modules we designed, rather than a freeform layout that drifts from the brand.
Can you take over and fix an existing WordPress site?
Yes. A large part of our work is inheriting sites, often after an agency handoff, auditing the plugin pile, and taking it back down to a short list we can maintain for years.
Do you only build WordPress, or also custom applications?
Both. Alongside WordPress we build custom platforms in Laravel and Vue.js, with the same team and the same long-haul maintenance approach. WordPress is one half of what INFIGO does; bespoke application work is the other.