Software engineering

Twenty-five years of shipping software that holds up.

We started in Java and PHP and never stopped. Around a hundred custom WordPress modules, fifty Magento and Magento 2 modules, Shopify utilities, bespoke CRMs, and full Laravel platforms — plus the Linux servers underneath all of it, configured and run by the same people who wrote the code.

25

Years writing production code, from Java and PHP onward

100 +

Custom WordPress modules, from e-commerce to penetration testing

50

Magento and Magento 2 modules, across both editions

500,000

Lines of production code across client platforms

// What We Have Built

Twenty-five years of other people's platforms, and our own

Most of what we are asked for begins life described as a website and turns out to be software. Some of it extends a platform the client already runs; some of it exists because no platform fitted.

WordPress modules, around a hundred of them

E-commerce, form building, automations, integrations, and security tooling including penetration-testing utilities. Written as modules with their own upgrade path, not as functions bolted into a theme that the next update overwrites.

Fifty Magento and Magento 2 modules

Across both generations of the platform, which between them cover most of its life. On Magento 2 that means Open Source and Adobe Commerce — the same codebase with materially different constraints around licensing, extension review and upgrade cadence. Catalog work is where a generic e-commerce platform runs out of road first, and Magento is usually where that shows up.

Shopify utilities

Shopify fixes the storefront and leaves the operations around it open. That is where the work lives: syncing catalogs, moving orders into the systems that actually fulfill them, and automating whatever the merchant is currently doing by hand in a spreadsheet.

CRMs and CMSs, most of them by now

Twenty-five years leaves very few content or customer platforms we have not extended, integrated, migrated or replaced. Where nothing off the shelf fitted, we wrote the platform itself — Smooth Promotion below began life as exactly that, a multi-user, multi-level CRM and CMS built from scratch.

My Site Buddy, our own product

An AI agent that works inside a WordPress site rather than alongside it, with access to the files, the command line and the database, wired into the SEO and analytics tooling the work depends on. This site is managed with it, and it gets its own section below.

Laravel platforms

The current generation of the work, and where the largest builds now live. The multi-site commerce platform below is one of them, shown in full because a claim about custom development means nothing until somebody shows the inside of a codebase.

Headless front ends and APIs

When the front end wants to be its own application, or several applications need the same data, we build the API first and treat the interface as one consumer among several. That decision is cheap at the start and expensive to retrofit.

AI and agentic workflows

Retrieval over your own content, structured extraction, and agents that do a job rather than demo one. The interesting work is rarely the model — it is the plumbing, the evaluation, and knowing which parts should not be probabilistic at all.

Migrations off legacy systems

Twenty-five years in this trade means most of what we meet was written by somebody else, often a long time ago. Custom importers, a mapped schema, and a version stamped on every migrated row so the next migration can dispatch on it.

// Case in point

Smooth Promotion: two generations of one platform

The first generation was a multi-user, multi-level CRM and CMS we wrote on LAMP — PHP, Java and MySQL on an MVC framework, hosted on AWS. It ran Go Promotional and its sister storefronts for sixteen years. The second generation replaces it on Laravel, carrying one shared catalog of roughly 12,000 products across several web properties: 58,000 lines of PHP, 449 classes, 900 tests across 130 files, twelve in-repo modules. The cards run from the first generation into the second.

Generation one: sixteen years on LAMP

Linux, Apache, MySQL and PHP on an MVC framework, with Java alongside it, running on AWS. Deliberately unexotic, and that was the point — it stayed stable for sixteen years on technology that is thoroughly understood. Most systems do not earn their keep that long before a rebuild is worth doing.

One interface, many websites

Managing several storefronts from a single place was the original requirement and it survived into the rebuild unchanged, with an API layer reaching out to accounting software, email platforms and whatever else the business runs. Go Promotional sits on the front of it, fully responsive HTML5, tuned so each page costs as few queries as it can.

Generation two: a multi-site publishing engine

Every page, post, product and category exists once. A background indexer projects it into a per-site table that answers one question: is this visible here, at what URL, with what metadata. Adding a new site became a configuration exercise rather than a content migration.

A configurable URL engine

Each site picks its own path strategy per content type, so the same product can live at /product-name on one storefront and /drinkware/travel-mugs/product-name on another. Collisions resolve deterministically — the first claimant keeps the URL and later ones are flagged for an editor rather than silently breaking an established link, with a database constraint closing the race.

Links that do not break

Rename anything, at any depth, and inbound links keep working. The redirect left behind stores no target: it resolves the entity's current URL at request time, so a category renamed five times still has every historical URL pointing at the latest address with no chain to follow. Deleted content returns 410 Gone rather than a 404, which is the correct signal for a search engine.

A pricing engine that matches the trade

Promotional products carry quantity-tiered pricing across six brackets and imprint options that change the price. The engine stores the increment and the included count and derives each option price, rather than baking a price into every combination — which removes an entire class of off-by-one errors instead of testing for them.

Quote carts rather than checkouts

This industry quotes, it does not check out. An anonymous cart keyed to a long-lived visit token lets a buyer build a quote before identifying themselves, line items store the selection rather than a frozen price so a catalog update is never stale in a cart, and a state machine governs the lifecycle so an undeclared transition throws instead of quietly corrupting an order.

Passwordless customer accounts

Email and a one-time code, no passwords to leak. An unknown address produces exactly the same response as a known one, so the login form cannot be used to enumerate customers, and two independent layers of guard isolation keep the customer panel and the staff admin from ever reaching each other.

2.5 min

Full catalog reindex, down from 25 minutes

26

Queries to reindex 30 posts, down from 427

230 MB

Peak memory on a full rebuild, down from 900MB

75,000

Per-site documents the indexer keeps in sync

// Built For Ourselves

My Site Buddy: an AI agent that lives inside the site

The second thing worth showing in full, because it is where twenty-five years of building and the current generation of AI meet. Most AI website tools sit outside a site and produce advice. This one runs inside it, with the same access an engineer has, and does the work.

Inside the site, not beside it

It runs in the site's own container with the files, the command line, the database and wp-cli in reach. So it does not describe a change and leave you to make it — it makes the change, loads the page, and looks at what it actually did before saying it is done.

The tools an agency actually uses, wired in

Ahrefs, Semrush, DataForSEO, Google Search Console and Tag Manager connected directly, so a question about rankings is answered from the data rather than from a guess, and a headless browser for seeing the rendered page at any width instead of assuming it rendered.

Every change ships as a reversible migration

Content edits are written as code and versioned like code, which is what stops staging and production drifting apart. Every change can be rolled back and every change can be reviewed before it goes anywhere. The alternative — an agent editing a live database with no undo — is not a product, it is an incident waiting for a date.

It takes standing instruction and keeps it

Preferences live in a file the site owner controls, read at the start of every conversation. Tell it once that a client's absolute traffic numbers are never to be published and it will still be refusing to publish them six weeks later, in a task nobody connected to the original instruction.

AI used to build AI

The product is an AI tool and it was built with them, which is the honest version of this pitch: we are not selling a technique we have not run at ourselves first. The same thinking reaches client work — our Laravel platform ships schema introspection and version-accurate framework documentation specifically so a coding assistant working in that repository gets facts instead of confident guesses.

Copy that has to pass a detector

Anything it writes can be put through AI-text detection before it ships and rewritten, window by window, until it reads as human. That is a measured bar rather than an opinion about the prose, and it matters because a page that was generated and never edited is increasingly obvious to readers and search engines alike.

What it does to the pace

On a national multi-location brand — 27 locations across 8 metro markets — it shipped 120+ pages over 9 concurrent workstreams in 20 active development days: 361 commits, 11 releases, every release approved by a person before production. The same scope costed manually came to 820 to 1,060 hours of senior work, or about six months full time. The whole thing is published at mysitebuddy.ai/case-study.

Speed is the easy half

The reason those eleven releases were gated on a human is that generating is cheap and being right is not. This page went through several rounds of revision against real screenshots at phone and desktop width, and two rounds of factual correction once the client read it. That loop is the product; the speed is what it buys.

// Infrastructure

We run the servers underneath it

A build nobody can deploy, restore or patch is not finished. We configure and operate the infrastructure our work runs on, on whichever cloud you are already committed to.

Hosting, on whatever you are already on

AWS, Azure and Google Cloud, and the long tail underneath them — managed WordPress hosts, cPanel and Plesk estates, VPS providers, bare metal. There are very few configurations twenty-five years has not made us work with. Most clients arrive committed to a provider for reasons that have nothing to do with engineering, and moving clouds to suit an agency is rarely the best use of a budget.

Linux server configuration

nginx, PHP-FPM, MariaDB or Postgres, Redis, queue workers, TLS and certificate renewal, cron and scheduled jobs. Configured to match the application rather than left on defaults, and documented so the next person can read it.

Backups, snapshots and tested restores

A backup nobody has ever restored is a hope rather than a backup. We take scheduled snapshots, keep them somewhere the primary environment cannot reach, and rehearse the restore — because the first time you find out how long recovery takes should not be the day you need it.

Penetration testing

Testing an application the way somebody attacking it would: authentication and session handling, access control between accounts, injection, file upload paths, and the dependency tree underneath. Findings come back ranked by what an attacker could actually reach, with the fix alongside.

Hardening and patch discipline

Least-privilege access, no shared logins, audit trails on the actions that matter, secrets kept out of the repository, and a patch cadence for the operating system and the dependency tree. Most breaches we are called in after were a known vulnerability nobody had scheduled time to update.

Monitoring and uptime

Uptime and error monitoring, log retention, and alerting that reaches a person rather than a dashboard nobody has open. Knowing a site went down before the client rings is most of the job.

Compliance, and the clients who require it

We have delivered for publicly traded companies, where a security questionnaire and an audit trail are part of the deliverable rather than an afterthought, and we have taken multiple sites through ADA accessibility compliance end to end. Both come down to evidence — who had access, what changed, when it was patched, which pages were tested against WCAG and what was fixed — and evidence has to be collected while the work happens rather than reconstructed afterwards.

// Process

How a build actually runs

Four stages. Nothing here depends on a discovery phase that produces a slide deck.

Scope

We start with the domain rather than the design: what the rules are, where the data comes from, which parts are genuinely hard. Most estimates go wrong on a rule nobody mentioned because everyone in the room already knew it.

Data in →

Architect

Decide the seams before writing the code, and record why. The platform above carries a maintained architecture document per module, including the options that were considered and rejected, because the expensive question later is always why rather than what.

Strategy →

Build and test

Tests run against a real database rather than a convenient substitute, so schema behavior matches production. Where an architectural rule matters — such as URL resolution not scaling per record — a test locks it, so a future refactor cannot quietly undo it.

Execute →

Operate

Deploy, monitor, patch, back up, and keep measuring. The indexer on the current platform was rebuilt after profiling showed where the time actually went, and the old implementation was kept as the correctness oracle the fast path is tested against.

∞ Loop
// From the archive

The hosting questions we asked in 2010 are the ones we still ask

Passages from our own archive, quoted as they were published. The tooling in them has aged badly. The questions have not aged at all.

Reliability and speed, however, should be thoroughly discussed before you sign anything. A guaranteed uptime of 99.8% or better should be a rock bottom requirement. There is no such thing as "regular business hours" when you have a website. Even a little bit of downtime can cost you a customer. Remember: 99% uptime isn't good enough. 99.5% uptime isn't good enough. Insist on 99.8% or better uptime.

The number is the part worth keeping. 99.5% uptime sounds like a rounding error away from perfect, and it works out at nearly four hours down in a month, which on a store is a bad afternoon rather than a statistic. 99.8% is about ninety minutes. The distance between those two figures is most of what you are paying for when you move off shared hosting.

We ask for the SLA in writing before a migration, together with what it actually pays out when it is missed. Usually that turns out to be a credit against the following month and not much else.

If you choose the less expensive shared web hosting, where your site shares a server with a bunch of other sites, be aware that their bandwidth usage could affect you. Suppose another site sharing the same server as you gets a front-page mention on PCWorld or some other huge site. They'll be snarfling up bandwidth like crazy. Could it affect your site? Well, if their site is overwhelmed to the point that it crashes the server and you share that server, then the answer is yes. Ask any potential web hosting providers what happens in just these situations.

Sixteen years and several hosting generations later, this is still the most common cause of a site being slow for reasons nobody on the client's side can account for. The vocabulary moved on to noisy neighbors and burstable CPU credits. The failure underneath it is identical: you are sharing a machine, and somebody else on that machine is having a good day.

So the first thing we establish on a performance complaint is whether the problem is the application or the tenancy. Those want completely different fixes, and from a browser they look the same.

Thinking that the migration is some simple copy-paste is a common misperception. The root of the problem lies in the fact that even though Magento 1 and Magento 2 differ in a single digit in the name, these two versions are completely different from each other on fundamental levels. At times migration to M2 can basically mean the same as building the entire thing from scratch (what store-owners often tend to do). It takes a lot of coding, untangling data, bug fixing, testing, upgrading, and implementing custom solutions to get things done the right way.

That went out in the month Magento 1 support ended, and the same paragraph would serve for most of the platform moves we are asked to quote now. A version number that goes up by one is not a promise about how much of your build survives it.

The estimate that catches people out is rarely the migration itself. It is the modules around it. Fifty Magento and Magento 2 modules went through this shop, and on a migration the honest answer for a fair proportion of them is that they get rewritten rather than ported.

AWS makes it easy to take snapshots of your EBS volumes. However, if you have many volumes, a way to automate and rotate snapshots becomes essential. There are many solutions out there to handle automated snapshots. One such excellent solution is the ec2-automate-backup script. Setting this script on a cron job, you can snapshot all your volumes in a specific region. Below is how I've set this up.

I have multiple EBS volumes attached to multiple EC2 instances. I needed a way to take a daily snapshot of all volumes. In addition, I needed the snapshots to rotate, such that only the last 7 days worth of snapshots would be kept.

I chose to create a small EC2 instance specifically for running ec2-automate-backup from a cron job that will backup the volumes of all my production instances.

Bob Tantlinger wrote that in 2014 and he is still here. The specific tooling has retired, since the EC2 command line it depends on was replaced years ago, but the shape of the answer is what we still build. Snapshots run on a schedule and rotate against a retention window, and the job runs somewhere that is not the machine being backed up.

The rotation is the part that gets left out of most setups we inherit. A backup job with no retention policy fills its volume and then quietly stops, and that tends to get discovered during a restore.

The ec2-automate-backup script can back up any volume that has a certain tag. Thus adding the tag Backup=true to any volume will automatically make it back up. This is nice, since we don't ever have to touch the cron or any other scripts. We can simply set it and forget it, and manage the Backup=true tag from within the AWS console.

Tag-driven configuration was a good instinct in 2014 and it is ordinary practice now under other names. A new volume ends up protected because somebody labeled it correctly rather than because somebody remembered to edit a script.

The same thinking applies to the restore, which is the half almost nobody tests. A snapshot you have never restored from is a hypothesis, so a restore drill is part of handover on anything we host.

If there's one often overlooked aspect of deploying a website, it's email delivery. Sure, you take into account your website bandwidth, DNS, server performance, etc... But email always seems to come low on the totem pole. I suppose this might be due to the fact that it's ubiquitous. You use it every single day and never really think about all messy underpinnings of it. And, yes, email as it is today is pretty much a mess... I like to think of email as a throw-back to a simpler, more wholesome time when people actually trusted each other. A time before messages from Nigerian princes and bogus pharmaceutical ads filled your inbox.

Email is still the thing that gets left off a launch plan, and it is still the thing that breaks first once real volume arrives. Nothing in that paragraph has dated apart from the reference to Nigerian princes.

On a build we treat deliverability as part of the infrastructure rather than as an application setting, which in practice means SPF, DKIM and DMARC are configured and verified before launch rather than after the first bounce report.

As an email user, you never really have to think about any of the above, but as a website owner, you absolutely must! As your site grows, you will undoubtedly need to send vast quantities of email of some kind or another – transactional emails, news letters, whatever. Unfortunately, sending large volumes of email can be a real pain. Web hosts and ISPs are typically not too keen on their customers sending massive amounts of email. Shared hosts usually limit the number of outbound email you can send per hour. Or maybe your site is on a dedicated server and mail is routed through a smarthost, which limits the maximum amount of email you can send per day. Ultimately, sending large volumes of legitimate email can be a complex challenge that requires a lot of forethought.

The hourly caps described there are the reason a store's order confirmations start arriving an hour late during a sale, and the client experiences it as the website being broken. It is almost never the website.

This is one of the checks that goes into a hosting audit before we quote a migration, because a send limit is cheap to discover in advance and expensive to discover on a launch day.

A while back I was working on a project that required the GUI to allow the user to dynamically add, remove and rearrange various form fields contained in table rows. The tricky part was that the UI needed to have this functionality for several different types of elements across several different forms. For instance, one set of fields was for adding and removing specifications to a product while another set of fields was for adding images to a product. Thus, I needed a solution that would be flexible enough to work across virtually any type of form elements.

Naturally, I turned to JQuery. I first took a look around within JQuery's plugin ecosystem to see if perhaps there was already a plugin that might do the job. While I did find a few different plugins for adding removing form elements, none of them did exactly what I needed, specifically re-arranging items... So, I was left with either trying to hack the functionality into an existing plugin, or roll up my sleeves and write my own. I choose the later option, since JQuery's excellent extension mechanism makes writing plugins a fairly straightforward process. The result is the plugin below, which I call dynoTable.

That is the whole of the custom-module decision, written out in 2012 and unchanged since. You look for something that already does the job, you find three things that nearly do, and then you decide whether hacking one of them is cheaper to own over five years than writing your own.

Most of the hundred-odd WordPress modules and fifty Magento modules counted further up this page began at exactly that fork. The ones we regret are the ones where we bent somebody else's plugin.

Around 700 posts, published since 2003

Read the archive
// Testimonials

What people say about working with us

We have been working with John and Web Moves for 14 years and the results have been astounding. John is a straight shooter — if he says he can do it, you can be confident he will deliver.
S

Shaun O'Brien

Owner, Selby Acoustics

Shaun was John's client

The great thing about John is his honesty — he tells it like it is. He is respected by major players including Microsoft and eBay, and media seek out his views: the front page of the Wall Street Journal among them.
P

Phil Leahy

Founder, MedCart Marketplace & Retail Global Events

Phil was John's client

John is the single most knowledgeable individual in the SEO industry I have ever worked with. Whether as a client, vendor or business partner, I cannot recommend him more highly.
M

Matthew Kligerman

Building Dirt — AI land development platform

John was Matthew's client

// Questions

Frequently asked questions

Do you only build WordPress?

No. WordPress is the most common request, and the work also covers Magento, on both Open Source and Adobe Commerce, along with Shopify, Laravel applications, and custom CRMs and content systems. Around a hundred custom WordPress modules and fifty Magento modules have come out of this practice.

Can you take over a site somebody else built?

Usually, and the first step is reading it rather than replacing it. Plenty of inherited sites need a repair and a plan rather than a rebuild, and saying so costs us the bigger project often enough to be worth stating plainly.

Do you handle accessibility?

Yes. Several ADA accessibility programmes taken end to end and measured against WCAG. There is no certification to name here, and anybody claiming one should be asked which standard it is and who audited it.

What hosting do you work with?

All of it: AWS, Azure and Google Cloud, plus managed WordPress hosts, cPanel and Plesk estates, VPS and bare metal. Where the platform is genuinely the constraint rather than the code, we will say so.

// Next Steps

Have something that outgrew its platform?

Tell us what it does today and where it stops. We will come back with what we would build, what we would keep, and an honest view of whether it is worth rebuilding at all.