BTST
PluginsQuickstartDocs
Live Blog

From research to product evaluation

Evaluating a publishing workflow for an app you already own?

See what the BTST Blog plugin adds to an existing React or Next.js app
BTST

Open-source TypeScript features for the React application, data, and deployment you already own.

Released plugins

  • Blog
  • AI Chat
  • CMS
  • Form Builder
  • UI Builder
  • Kanban
  • Comments
  • Media
  • Route Docs
  • OpenAPI
  • Better Auth UI

Resources

  • Quickstart
  • Documentation
  • All plugins
  • Live Blog
  • GitHub (opens in a new tab)
© 2026 BTST. Open source under the MIT License.
AI Chat
September 11, 2026ReactForm Builders

React Form Builder Submissions: Validate Answers on the Server

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

React Form Builder Submissions: Validate Answers on the Server

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.

Keep the form definition separate from the answers#

A definition describes fields and their rules. A submission contains answers associated with that form. For example, this small JSON Schema describes two fields:

JSON
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
{
  "type": "object",
  "properties": {
    "name": { "type": "string", "minLength": 1 },
    "message": { "type": "string", "minLength": 10 }
  },
  "required": ["name", "message"]
}

The corresponding answer object is much smaller:

JSON
  1. 1
  2. 2
  3. 3
  4. 4
{
  "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.

What BTST validates when a response arrives#

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.

Give public submission and administrative reading different permissions#

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:

RequestExpected result under a public contact-form policy
Anonymous visitor reads the active public formAllowed
Anonymous visitor submits valid answersAllowed, subject to abuse controls
Anonymous visitor opens submission administrationDenied
Editor changes the form definitionAllowed only by the app's editor policy
Authorized operator opens one responseAllowed 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.

Test the boundary with one real form#

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.

In This Post

Keep the form definition separate from the answersWhat BTST validates when a response arrivesGive public submission and administrative reading different permissionsTest the boundary with one real form