Skip to main content
View as Markdown

Form Access Control

A form is a public route by default. That is right for a contact form and wrong for an internal expense claim, a partner-only order sheet, or a member survey — each of which needs the URL to answer differently depending on who asked.

access states the requirement, using the same permission model as tables, pages, buckets, automations and agents:

forms:
  - id: 2
    name: member-survey
    title: Member Survey
    submitTo: { table: survey_responses }
    access:
      require: authenticated
    fields:
      - { kind: standalone, name: rating, inputType: rating, required: true }

Access Properties

Property Description Default
require all, authenticated, or an array of role names — at least one when an array. all when access is omitted.
redirectTo Path to send a denied submitter to instead of the denial response. Must start with /. No redirect.

A role array may also name a group, with a group: prefix — ['admin', 'group:partners'] admits anyone whose role is admin or who belongs to the partners group.

redirectTo is accepted and validated today; the canonical /forms/{name} route currently answers a denial with the response below rather than a redirect.

What a Denied Submitter Gets

The gate applies to both opening the form and posting to it. The denial is deliberately not uniform.

require Anonymous visitor Signed in, wrong role Signed in, right role
omitted / all Allowed Allowed Allowed
authenticated 401 — sign-in required Allowed Allowed
['partner'] 404 404 Allowed

The 401 names the form and the level it requires, because "log in to use this" is a useful thing to tell a visitor.

Access and $user References

Requiring authentication is what makes the session available to the renderer, and therefore what makes $user.<prop> resolvable. On a public form there is no session to read: a $user.email in the prefill map silently drops and the field renders empty.

The inline form of the same reference is stricter. A per-field defaultValue: $user.email on a public form is rejected when the config is decoded, with an error naming the form and the field — a value that can only ever resolve empty is a mistake worth catching before boot rather than a blank input worth shipping.

Embedded Forms

A form rendered inside a page through formRef keeps its own gate, and the page inherits the strictest answer: if any embedded form denies the session, the whole page returns 404. The alternative — rendering the page with a hole where the form should be — would leak the form's existence through the shape of the page, which is exactly what the role gate exists to prevent.

Who Submitted

When a session is present, the submission ledger records submitter_user_id alongside the answers, and a bound table's created-by column is populated with the same id. Public submissions leave both null. Access control is therefore also what makes a submission attributable.

  • Roles & RBAC — the role names require matches against.
  • Auth Groups — the groups a group: entry refers to.
  • Prefill$user references, which depend on this gate.
  • Availability — the other reason a form may refuse a submission.

Last updated July 27, 2026

This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta — contributions and corrections are welcome.

Built with Sovrium