Guide
No-code and low-code form handling for static sites and front-end apps
"No-code forms" means at least four different things, and the confusion is expensive because each one leaves you with a different set of obligations. Someone comparing two products labelled no-code may be comparing a form builder against an endpoint — products that solve different halves of the problem and do not substitute for each other.
This is a map of the four levels, what each one actually writes and owns, where each one stops being sufficient, and how to move between them. If you are choosing today, the last two sections — how to spot a no-code claim that is secretly a build step, and when refusing to write code is the wrong call — are the ones worth reading twice.
Four things people mean by "no-code"
| Level | What you do | What handles the submission | What you own |
|---|---|---|---|
| 1 · Form builder | Drag fields in an editor; the builder generates the markup and hosts the form | The builder's own platform | Their design system, their markup, their data model |
| 2 · Host-detected form | Add an attribute to a form on a supported host | The host's form service | A host-specific attribute, and a rebuild if you leave |
| 3 · Hosted endpoint | Point your existing form at an absolute URL | A service built to receive form posts | An account and a dependency; not the plumbing |
| 4 · Function | Write a handler, store, and send | Your own code | Storage, retries, spam, mail keys, and the failure at 2am |
Levels 1 and 2 are no-code in the strict sense: you never touch a server. Level 3 is no-code for the plumbing but assumes you write HTML — which, if you are building a site, you were doing anyway. Level 4 is not no-code at all; it is included because it is where level 3 stops, and knowing the boundary prevents a nasty surprise six months in.
The interesting property is not how much you type. It is what stops working when you change your mind. A level-1 form stops working when you want your own design. A level-2 form stops working when you change hosts. A level-3 form stops working only when you want to replace the endpoint, which is one attribute. That ranking, not the setup time, is what to optimise.
Level 1: the builder, and what it takes in exchange
A form builder is the fastest path from nothing to a working form: pick a template, add fields, publish. It is the right answer when the form is the product — a landing page, a survey you will throw away, a form that has to look like the builder's preview.
The exchange is that the form now lives in someone else's system. Your page links to it, or embeds it in an iframe, and the visual language, the validation behaviour, and the URL structure are theirs. Change the design system and you change the form's appearance; export the form and you get markup that only works while you stay.
For a form on a site you are actively designing, this is usually the wrong trade — not because builders are bad, but because the form is the one element of the page you cannot style like the rest.
Level 2: the host does the work, and knows it
A host-detected form is one attribute away from working. Netlify's approach is the clearest example: forms present in the built HTML are picked up at deploy time, submissions appear in the dashboard, and the notification goes to your account email.
<form name="contact" method="POST" data-netlify="true">
<input type="hidden" name="form-name" value="contact">
<input name="name" required>
<input name="email" type="email" required>
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
Two details decide whether this works. The hidden form-name field is how a POST is matched to the registered form, and a form rendered by client-side JavaScript was never seen at build time, so it was never registered. Both failures look like "the form does nothing", which is why level 2 has such a specific reputation for mysterious breakage.
If the site is committed to the host, this is the least work available and a perfectly good answer. The cost is portability: the attribute and the hidden field are host vocabulary, so moving hosts means rebuilding the intake, and moving to a hosted endpoint means both deleting host attributes and adding an absolute URL.
Level 3: your markup, someone else's plumbing
The hosted-endpoint level keeps the form in your code and moves only the part you did not want to run. You write the fields exactly as you want them; the endpoint receives, validates against configured rules, stores, notifies, and records delivery.
<form action="https://freelyform.com/f/FORM_ID" method="POST">
<label for="email">Email</label>
<input id="email" name="email" type="email" required>
<label for="message">Message</label>
<textarea id="message" name="message" required></textarea>
<button type="submit">Send</button>
</form>
Everything people describe as no-code configuration then lives in a dashboard rather than in a function: where notifications go, whether an autoresponse is sent and what it says, where the visitor lands after submit, what the success message is, which origins may post, whether file uploads are accepted and how large, whether a honeypot and captcha are on, and which rules block, route, or require.
That list is why "no-code" is a fair label here. None of it needs a deploy, and none of it is code you maintain. The form's markup is code you were writing anyway.
Front-end apps: the same level, one more line
React, Vue, Svelte, Next.js, Astro: the component posts to an endpoint instead of to a route handler. There is no SDK in the path, which matters for reasons beyond taste — an SDK is another dependency with its own upgrade cycle, and a framework-specific one is usually a thin wrapper around a fetch call you could have written.
export function ContactForm() {
const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle');
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const form = event.currentTarget;
setStatus('sending');
const response = await fetch('https://freelyform.com/f/FORM_ID', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(Object.fromEntries(new FormData(form)))
});
if (response.ok) { form.reset(); setStatus('sent'); }
else { setStatus('error'); }
}
return (
<form onSubmit={onSubmit}>
<input name="email" type="email" required />
<textarea name="message" required />
<button disabled={status === 'sending'}>Send</button>
<p role="status">{status === 'sent' ? 'Thanks — it arrived.' : ''}</p>
</form>
);
}
Three things to keep, whatever the framework: the fields keep their name attributes so the server can read them; the submit handler disables the button while the request is in flight, or impatient users create duplicates; and the error branch renders the endpoint's message rather than a generic failure, because a required-field error the visitor can fix is not the same event as a network timeout.
A fourth, optional: keep the native form element able to submit on its own. A page that only works with JavaScript is a page that loses every visitor whose script did not load, and progressive enhancement costs nothing here — the endpoint answers a native post just as well.
What configuration can replace, and what it cannot
The honest boundary is between rules about a submission and operations on your system.
| Job | Configuration is enough | Needs your code |
|---|---|---|
| Required fields, types, length caps | Yes — rules evaluated at the endpoint | |
| Send different enquiries to different inboxes | Yes — route rules keyed on a field | |
| Block link spam or a disposable domain | Yes — block and spam rules, plus heuristics | |
| Thank the submitter automatically | Yes — autoresponse template | |
| Notify a team chat | Yes — a webhook endpoint | |
| Transform a phone number into a normalised format | Yes — a webhook consumer, or your handler | |
| Create an order and take payment | Yes — it is a transaction, not intake | |
| Check a value against your customer database | Yes — it needs your data |
The pattern is that configuration handles decisions about one submission, and code handles consequences that reach other systems. A form backend that starts promising the right column is becoming an application platform, and it will be a worse one than the code you already have.
Moving between levels without a rebuild
Teams rarely go from level 1 to level 4 in one step, and they should not. What makes the path cheap is the markup.
1 → 3. Replace the builder's embed with your own fields and an endpoint URL. The visual work is the real cost; the intake work is one attribute.
2 → 3. Remove the host attribute and the hidden form-name field, add the absolute action. Re-test with the same submissions, because the validation rules now live somewhere else and the defaults differ.
3 → 2. Possible but usually a downgrade: you gain the host's dashboard and lose portability. Do it only if you are consolidating onto that host for other reasons.
3 → 4. Keep the same fields and replace the endpoint with your handler. This is the migration that benefits most from having started at level 3: the markup does not change, because it never encoded anything about the backend beyond one URL.
The reverse direction — 4 → 3 — is the one people underestimate. Replacing a hand-written handler with a hosted endpoint usually removes a maintenance duty rather than adding one, and the only thing you give up is the ability to run arbitrary logic inside the request, which most form handlers do not actually use.
Ten questions that expose a "no-code" claim
- Does it require a build step, a CLI, or a bundler plugin? If yes, it is not no-code — it is low-code with a toolchain.
- Does it require an SDK or a framework-specific package? If the answer is "recommended", read the underlying HTTP call; it is usually a post to a URL.
- Does it generate markup you have to use, or accept the markup you wrote?
- Is the behaviour configured in a dashboard, or written in a config file you commit?
- Where do the submissions live, and can you query them without going through their UI?
- What happens at the monthly allowance — do visitors start being rejected?
- Can you point the same markup at a different endpoint later without touching the fields?
- Does it record per-channel delivery, with the provider's message id and the error text when a send fails?
- Are retries and a replay available without a support ticket?
- Does it work with JavaScript disabled? A native form post is the fallback that never breaks.
Only the first two are about code. The rest are about whether the thing you are calling no-code is also something you can leave, and about whether "it works" means the submission arrived somewhere durable.
When refusing to write code is the wrong call
No-code is a means, not a virtue. Three situations want code, and choosing configuration there wastes more time than it saves.
When the form changes state elsewhere. Payment, inventory, account creation, ticketing with SLA rules — these are operations. A webhook consumer can bridge to them, but the transaction itself belongs in the system that owns the state.
When the validation is the product. If eligibility depends on business rules spread across several tables, no rule builder will express it, and you will spend longer fighting the abstraction than writing the check.
When compliance requires infrastructure you control. Data residency at the hardware level, audit trails you generate, a signed DPA with an auditor who wants to see the runtime — those are not configuration problems.
Everything else is intake, and intake is where configuration wins by a wide margin — not because writing a handler is hard, but because a handler is a permanent duty with an on-call surface, and a contact form should not have one.
A short checklist before you ship
- Submit once with JavaScript disabled and confirm the native path works.
- Submit with a required field empty and read the error a visitor would see.
- Confirm the notification arrived, and that the delivery record says so.
- If you enabled a captcha, confirm the page renders the widget — otherwise every post is rejected.
- Fill the honeypot deliberately and confirm the record is stored as spam, not silently dropped.
- Remove the endpoint and confirm the failure is visible rather than a blank success.
Where to go next
- What is a form backend — the definition and the decision, before the implementation.
- Static sites without server code — payload formats, CORS, and the lifecycle after a 200.
- React and Next.js — the client-side version with error handling.
- The contact form pattern — which fields earn their place.
- Plan ladder — what the free tier includes before you need a higher one.
No-code questions
Is a hosted form backend no-code?
For the form itself, yes: you keep your own HTML and change one attribute, and no build step or SDK is involved. The thing that stays in code is your page — you write the fields, not the plumbing. Configurable behaviour (redirect, autoresponse, routing rules, spam thresholds) is handled in a dashboard rather than in a function.
What is the difference between no-code and low-code for forms?
No-code means the behaviour is configured, not written: pick options, save, it works. Low-code means the same behaviour is available as a small amount of declarative markup or a short script you paste in. Both avoid a backend you operate; the difference is whether configuration lives in a UI or in a file.
Can a React or Next.js app use a no-code form backend?
Yes, and it is the common case. The component posts to an endpoint instead of to a route handler. Nothing is generated for you and no SDK is required — a form element works, and a fetch call works with one more line. What you keep is the ability to render your own validation and error states.
When does no-code form handling stop being enough?
When the submission has to change state in another system: create an order, decrement stock, charge a card, or join a transaction. Those are operations, not intake, and they belong in code that owns that state. Everything before that line — receive, validate, store, notify, record — is configuration.
Does using a hosted backend mean I lose control of validation?
No. Server-side validation runs at the endpoint from rules you configure, and the endpoint returns a real error response you can render. What you do not get is arbitrary logic: transformations and cross-record checks belong in your code or in a webhook consumer.
How do I avoid a no-code form that I cannot migrate away from?
Keep the markup standard and the endpoint absolute. A form whose behaviour lives in plain HTML attributes and one URL can move in a single edit; a form built with a host-specific detection attribute or a builder-specific embed has to be rebuilt. Check which of the two you are getting before you ship.