Separate form schemas from answers, enforce submission permissions, and test stale forms and invalid requests in a React form-builder integration.

A visual form builder needs a server-side submission contract. Before embedding one in a React app, decide which saved form definition validates each response, who can submit it, and who can read the stored answers.
Consider a contact form an editor can change without a deployment. The browser renders the current fields, but an older tab may still show yesterday's form. A caller can also skip the browser entirely and send JSON directly. The backend must make the acceptance decision from its own stored definition.
A definition describes fields and their rules. A submission contains answers associated with that form. For example, this small JSON Schema describes two fields:
{
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 1 },
"message": { "type": "string", "minLength": 10 }
},
"required": ["name", "message"]
}
The corresponding answer object is much smaller:
{
"name": "Sam",
"message": "I would like a product demonstration."
}
These illustrate the data boundary, not a complete BTST installation. Do not let a submission replace the authoritative schema by including its own field definitions. In JSON Schema, declaring a property and requiring its presence are separate choices; required names the mandatory properties. See the JSON Schema object reference.
Also separate valid JSON from supported form features. A renderer and its server converter may implement only part of JSON Schema. Verify each field type and validation rule you intend to expose to editors, especially custom widgets and conditional fields.
In @btst/stack 3.0.0, the Form Builder submission operation accepts a form slug and answer data. It resolves the stored form, checks authorization, rejects a form whose status is not active, and validates answers using the stored schema's Zod conversion. Persistence happens inside a transaction, with checks against concurrent changes to the form. The released submission implementation is the reference for these details.
That gives an existing Next.js, TanStack Start, or React Router app a backend workflow to connect to its public form route. Your application still supplies the database adapter, access rules, and any domain checks beyond the supported field validation.
Treat transformations as another validation boundary. A submission hook that rewrites an email address, inserts a derived field, or forwards answers to another service must preserve the receiving system's contract. Passing initial field validation does not prove every later transformation is valid.
An anonymous visitor may need permission to render and submit a contact form. That does not imply permission to list forms in an editor, modify their definitions, browse submissions, or read an individual response.
For a first integration, write down these expected outcomes:
| Request | Expected result under a public contact-form policy |
|---|---|
| Anonymous visitor reads the active public form | Allowed |
| Anonymous visitor submits valid answers | Allowed, subject to abuse controls |
| Anonymous visitor opens submission administration | Denied |
| Editor changes the form definition | Allowed only by the app's editor policy |
| Authorized operator opens one response | Allowed by a separate response-reading rule |
BTST exposes distinct Form Builder permissions for these operations. Authorization is opt-in in 3.0.0: attach the configured server auth adapter to createBackendStack({ auth: serverAuth, ... }). Omitting it leaves operations permissive. Configure them through the authorization guide and Form Builder documentation. Keep rate limiting and spam controls on the server; a hidden submit button cannot enforce them.
Start in a development database. Submit one valid response and confirm it can be retrieved by an authorized operator. Then omit a required answer and send a wrong value type directly to the submission endpoint. Neither request should create a stored response.
Next, load the form in a tab, deactivate it in administration, and submit from the old tab. The server should refuse it. Change a validation rule and repeat with an answer that the old rule accepted. Decide what message a visitor should see when the definition has changed.
Finally, retry a valid request and observe whether it creates another response. Do not assume submission retries are deduplicated. If duplicate prevention or historical schema versions matter to your workflow, design and verify those requirements explicitly.
The Form Builder plugin overview describes the included editor, renderer, API, and persistence. Use that boundary to estimate the remaining integration work, then follow the implementation docs with your own access policy.