Your Front-End Shouldn’t Know What Marketing Wants
View original- best
Summary (TL;DR)
This article argues that hypermedia APIs decouple front-end code from backend data models, preventing endless tickets when marketing adds new form fields. It contrasts hardcoded key/value field structures with self-describing JSON actions that include field types, labels, required flags, and patterns. The author explains that clients should know the format, not the specific fields, and highlights extension points for future requirements. The pattern also works for machine-to-machine clients with rich affordances like searchable selects. It suggests using standards like JSON:API, HAL, or Collection+JSON instead of custom formats. Hardcoding is fine for stable forms like login. The key benefit is that the server owns the model, and the client just renders actions, making changes faster and reducing coupling.
Your Front-End Shouldn’t Know What Marketing Wants
How hypermedia removes the coupling between who defines the data and who displays it
5 days ago

Custom fields break front-end applications.
Rendering them is easy, but the hard part is deciding which fields exist, and in most apps the client decides.
Here is a pattern I see teams repeat without questioning it. Marketing wants a new signup field, so they file a ticket with engineering, the change ships a few days later, and by then marketing has already thought of the next field. The cycle starts over.
The front-end knows too much.
It knows the names, the types, and the order of every field marketing cares about. That knowledge is a dependency, and every dependency is a reason to ship again.
Hypermedia removes that dependency and the idea has been around for years.
Most JSON APIs still ignore it.
Stop telling the client what to render and start describing it in the response.
Take a signup form. A traditional front-end hardcodes something like this:
{
"fields": {
"email": "string",
"name": "string",
"company": "string"
}
}The client reads response.fields.email, response.fields.name, and response.fields.company and renders three inputs. Add a "job title" field and the client has no idea what to do with it, so a developer gets a ticket.
A hypermedia response looks different:
{
"actions": [
{
"name": "sign-up",
"fields": [
{ "name": "email", "type": "text", "label": "Email Address" },
{ "name": "name", "type": "text", "label": "Full Name" },
{ "name": "company", "type": "text", "label": "Company" }
]
}
]
}The client doesn’t check for “email” or “company.” It finds the sign-up action and loops through the fields, rendering each one by its type. Add a “job title” field on the server and the client renders it on the next request, without a single change to the front-end code.
The client’s job changes from “know the fields” to “know the format.” That’s a smaller, more stable contract.
An “action” is a flow concept, not a web concept. Whoever drives a CLI, a web UI, an iOS app, or an Android app is there to do something: sign up, search, pay. The concept holds in all of them.
The name “sign-up” is domain vocabulary, the way a clinic has “booking” and a store has “checkout.” You hook the vocabulary of your business into the message itself, and the message becomes self-describing.
The format stays generic, and the vocabulary carries the domain.
Sure, you can loop the first example too:
Object.keys(response.fields)
// ["email", "name", "company"]However, the looping is not the point. The point is what happens when the model grows.
Marketing asks for user-facing text they can edit in the CMS, so each field needs a label. A month later some fields become mandatory, and that’s a required flag. Then the input has to match a strict pattern or the user can’t move forward, and the pattern is a regex the client runs before the request leaves, the same one the server runs when it arrives.
In the key/value format each field name points at one type string, so the type fills the only slot the syntax gives you, and there’s nowhere to hang the next property. To make room you change the shape of the message, and every client that reads it changes with you.
That’s the cost.
You coupled the whole model to a key/value syntax, a shape with no extension points.
A key/value object gives you one slot per field. No extension.
The array of objects has room. Each field is an object — an action, so a new property hangs off it without disturbing the rest.
{
"name": "email",
"type": "text",
"label": "Email Address",
"required": true,
"pattern": "^[^@]+@[^@]+$"
}A client that doesn’t read pattern skips it and keeps working, and a client that does read it validates the input with the same rule the server applies. You shipped client-side validation, and the old clients didn't notice.
That’s the extension point.
The first format gives you a value slot and calls it done. The second gives you somewhere to put the requirement marketing hasn’t asked for yet.
Now stretch the example. What happens when the client isn’t a browser?
Sometimes it’s another service filling out the form through an API, and the same structure works for it. The machine looks for the action it needs and reads the fields. It fills the ones it recognizes, and if marketing adds a field it has no data for, it skips that field.
The machine doesn’t break, and it doesn’t need a code change.
It handles what it knows and leaves the rest alone. This is the part that makes the pattern worth the effort, since a human-driven UI and a machine-driven API client share the same response format. They both read the structure, and neither depends on a hardcoded list of field names.
The pattern gets more interesting when fields need richer behavior.
Say marketing wants the signup form to ask “Who else is coming with you?” They want users to search for and select other people, like an autocomplete input. In a hardcoded client, that’s a feature request with a custom component, a new API endpoint, and a round of QA before anyone outside the team sees it.
In a hypermedia response, you describe the affordance:
{
"name": "guests",
"type": "searchable-select",
"label": "Who else is attending?",
"search": {
"href": "/api/users/search",
"method": "GET",
"param": "q"
}
}The client sees "type": "searchable-select" and renders the matching component. That component takes a search term, queries the href, and shows the matches. The client doesn't know it's searching for users. It only knows the interaction pattern.
A machine client sees the same field and can call the search endpoint directly if it has a name to look up. The affordance is the same, and the “driver” (be it a machine or a human) decides what to do with it.
Point a web browser at a server that returns HTML and you get all of this for free.
A <form> with <input> elements is hypermedia, and the form element even carries an action attribute. An <input> with a <datalist> is the searchable-select affordance, built into the platform. The browser renders whatever fields the server sends.
I think the entire problem comes from one move.
We pushed rendering into a JavaScript client that expects to know the data model in advance, then rebuilt a weaker version of what HTML already did and spent years patching the coupling it created. I wonder how much front-end code exists only to recreate what the browser already does.
JSON APIs that describe their own affordances aren’t a new idea. Formats like JSON:API, HAL, and Collection+JSON already standardize the shape, so I’d reach for one of those instead of inventing a format from scratch. The goal is to bring that same property, server-described interaction, to API clients and SPAs that lost it somewhere along the way.
Not every form needs this treatment. A login page has two fields, and they aren’t going to change.
Hardcode them.
The real cost is ownership. A non-technical team owns the data model, and a technical team owns the client. Every new field becomes a ticket, a delay, and a meeting, all of it spent keeping one hardcoded promise in sync with a model the client team doesn’t even own. Hypermedia breaks that promise on purpose. The server describes what exists, and the client decides how to render it.
Neither side needs to know the other’s business.
Put plainly: A front-end that knows the specifics of every action is fast to build. A front-end that knows nothing about the specifics of every action is fast to change.
After all, they don't need to care about the next field, they only need to know one thing: what the concept of an "action" is.
If you liked this, you might like readplace.com, built for exactly this kind of reading.
Thanks for reading. If you have some feedback, reach out to me on LinkedIn, Reddit or by replying to this post.