Not Every AI Feature in WordPress Needs an API Key
Every time someone adds an AI feature to WordPress, the same architecture shows up. Editor calls a REST endpoint, endpoint calls a vendor API, vendor bills you per request. It works. It is also the wrong shape for a whole category of problems, and I want to talk about one of them.
Here is the problem I actually had.
The Cover block crops your face off
The Cover block scales and crops its background image to fill whatever space it ends up in. Desktop, tablet, phone, all different aspect ratios, all cropping from a different edge. The default focal point is the dead center of the image.
If your subject is standing in the center of the frame, fine. If they are standing off to the left, which is how most photographs are actually composed, the phone crop routinely slices their head out of the picture. The editor looks correct. The site does not.
WordPress already ships the fix. The Cover block has a focal point picker in the inspector, and you can drag the point onto the subject. The problem is not capability, it is that nobody does it. It is a manual step on every single image, it produces no visible change in the editor at the size you are looking at, and an editor publishing fifteen posts a week is not going to do it fifteen times. So the feature exists and sits unused, and the crops stay broken.
That is a good shape for automation. The machine finds the subject, the human confirms. Nothing is created, nothing is generated, no one has to trust a model with anything irreversible.
Why the API call is the wrong answer here
The obvious build is to send the image to a vision API and get coordinates back. I did not want to do that, for reasons that have nothing to do with purity.
Cost scales with the media library. A vision API call is cheap. Thousands of calls across a busy publishing operation are not, especially when the feature saves only a few seconds per image. Even if inference runs only when an editor selects a Cover block, the feature acquires a permanent usage-based cost.
Latency lands in the wrong place. This suggestion has to appear while the editor is looking at the block. A round trip to a third party sits directly in the editing flow, and if the API is slow or down, your editor is staring at a spinner in the inspector panel wondering if WordPress broke.
And then there is the part that actually kills it in enterprise. Sending client images to a third party is a procurement conversation. It is a data processing agreement, a security review, and in some organizations a straight no. I have watched perfectly good features die in that review. Building a feature that requires that conversation, in order to save four seconds of dragging, is a bad trade.
So the constraint became: this has to work with no network call and no API key, or it is not worth building.
Running the model in the browser
The version I ended up with loads a face detection model into a Web Worker and runs it through WebAssembly, entirely client side. The image never leaves the browser. There is no server component and no key to configure.
The flow is boring, which is the point. You select a Cover block. The worker analyzes the image already loaded in the editor. A suggested focal point appears in the inspector, and you accept it, ignore it, or drag it somewhere else with the standard picker. The coordinates save into the block attributes exactly as if you had set them by hand, so nothing downstream knows or cares that a model was involved.
Two implementation notes that mattered more than I expected.
The Web Worker is not optional. Inference on the main thread locks the editor, and a frozen editor is a far worse experience than a badly cropped image. Inference of this size does not belong on Gutenberg’s main thread.
And the output has to be a suggestion, not an action. The plugin never writes the focal point without a human accepting it. This is partly a trust thing and partly a correctness thing, because the model is wrong often enough that silent automation would quietly degrade a site over months in a way nobody would trace back to the plugin.
The honest tradeoffs
Local inference is not free, it just moves the cost somewhere less visible.
The model has to be downloaded the first time the feature is used and initialized again when a new worker starts. Browser caching avoids repeated downloads, but the first analysis of a session can still have a noticeable warm-up cost.
Detection quality is bounded by what fits in a browser. A small model catches clear frontal faces well. Profiles, partially occluded faces, small faces in a group shot, and images where the subject is an object rather than a person are all weaker. This is a face detector doing a focal point job, not general subject detection.
And hardware variance is real. On my machine the analysis is fast enough to feel instant. On a mid-range Windows laptop, which is what a lot of editorial staff actually use, it might be slower, and I would not ship this to a large team without measuring on their actual hardware first.
The general pattern
The reason I think this is worth writing up is not the plugin. It is the category.
There is a class of editorial AI features where the task is small, the output is structured, and a human is going to confirm it anyway. Focal points. Alt text drafts. Suggested tags. Duplicate detection. For all of these, the reflex is to reach for a hosted model, and for all of these, the hosted model brings billing, latency, a dependency, and a data processing conversation that the feature does not remotely justify.
Small local models are worse than hosted ones. That is true and it is not the relevant question. The relevant question is whether they are good enough for a suggestion a human is about to approve, and for narrow structured tasks they very often are. When they are, you get a feature with no recurring cost, no key management, no vendor to be down, and nothing to explain to a security reviewer.
That is a real architectural option and it is under-discussed in WordPress. Not everything needs to phone home.
The plugin
Focal Point Suggest is on GitHub at github.com/abhishekfdd/focal-point-suggest. Demo video is in the README. It is not on the plugin repository and I have no immediate plans to put it there, it exists as a working example of the pattern.
