Data and schema
Generate forms from definitions
Build schema-driven React forms with GsbForm, customize fields, and save through authorized backend operations.
Use GsbForm from @gsb-core/react-forms to render a form from an installed entity
definition. The definition supplies the properties, data types, references, labels,
ordering, and form visibility metadata. Your React application supplies the surrounding
screen and the authorized operation that saves submitted values.
This is runtime React form generation, not a visual application designer or a command that generates source files. It does not install backend definitions or grant access. Start with a workspace-bound React app and read schema design before changing the underlying model.
Prerequisites
Run inside your application's established workspace client and authentication setup. Definition loading, record loading, reference pickers, and uniqueness checks use the configured Core services. Installing the React package alone does not configure them. Use the selected workspace's real definition name or id and verify the signed-in user can read it and perform the intended operation.
For an existing React application, add the package with pnpm if it is not already a direct dependency:
pnpm add @gsb-core/react-forms
Keep compatible Core and React package versions and the generated lockfile. The boilerplate guide explains workspace binding and frontend composition.
Create a record
This component accepts a save operation from its owning screen. Replace Customer
with an installed definition. The saveCustomer callback must call the established
Entity Service integration; it is not local persistence or a simulated backend.
import { GsbForm, type FormValues } from "@gsb-core/react-forms";
interface CustomerCreateFormProps {
saveCustomer: (values: FormValues) => Promise<void>;
}
export function CustomerCreateForm({
saveCustomer,
}: CustomerCreateFormProps) {
return (
<GsbForm
entityDefName="Customer"
formMode="create"
submitButtonText="Create customer"
onSubmit={saveCustomer}
/>
);
}
Submitting does not automatically save a record. onSubmit receives validated
form values. Await the authorized save inside that callback, propagate failures, and
only show success or navigate after persistence succeeds. See working with entity
data for the save contract. Supplying onChange observes draft
values; it does not save them either.
View and update records
Use entityId to load an existing record. For example, a read-only detail screen can
render:
import { GsbForm } from "@gsb-core/react-forms";
export function CustomerDetails({ customerId }: { customerId: string }) {
return (
<GsbForm
key={customerId}
entityDefName="Customer"
entityId={customerId}
formMode="view"
showSubmitButton={false}
/>
);
}
For editing, use formMode="update" and pass an onSubmit callback that saves against
the selected record's identity. A record id is selection input, not authorization.
When switching between records or workspaces in the same screen, remount the form
with a key that changes with that context so loaded state is not reused accidentally.
| Mode | Purpose | Persistence |
|---|---|---|
create |
Build a new record using create-mode schema metadata. | Your onSubmit callback creates it. |
view |
Display an existing record read-only. | No write is needed; hide the submit button. |
update |
Edit an existing record using update-mode metadata. | Your onSubmit callback saves the selected record. |
You can also supply an already loaded entityDef and entity instead of asking the
form to fetch them. Prefer that approach when the owning screen already has the
authorized definition and record. Do not supply conflicting identifiers and values.
Customize the generated form
Keep shared field rules in the entity definition. Use component props for a particular screen's presentation, not as substitutes for server permissions.
| Prop | Effect |
|---|---|
excludeFields |
Omits named properties from this form. |
readOnlyFields |
Makes named properties read-only in this screen. |
requiredFields |
Adds required-field checks for named properties. |
includeSystemFields |
Includes system properties normally excluded; defaults to false. |
showGroups |
Displays schema-derived field groups; defaults to true. |
showSubmitButton, submitButtonText |
Controls the built-in submit control. |
readOnly, isLoading |
Applies screen-level read-only or loading state. |
onLoad |
Receives the generated FormConfig. |
onCancel |
Handles the cancel action when provided. |
controlOverrides |
Selects a supported control kind for a named property. |
registry |
Supplies control components for supported control kinds. |
For deeper control customization, use the exported createControlRegistry API and
the package's typed control contracts. A custom control must preserve value changes,
validation feedback, and read-only behavior. Do not duplicate the record's business
rules in a custom input.
Validation and permissions
The form derives validation from its generated field configuration and checks unique
fields before invoking onSubmit. Backend validation and authorization remain
authoritative: another caller can bypass the UI, and concurrent writes can invalidate
a client-side uniqueness result. A hidden or read-only field is not a security rule.
Use authentication and workspace access for the authorization
boundary. Never put credentials in props intended for display, URLs, browser storage,
or VITE_* configuration, and do not log submitted personal data.
Troubleshoot and verify
If the form cannot load, inspect the selected workspace, definition name, record id,
and caller permissions. If a field is missing, inspect system-field exclusion,
excludeFields, form-mode and screen visibility metadata, and reference targets.
Do not replace missing definitions or inaccessible records with fake schema or data.
Verify create, view, and update separately. Check required fields, references, read-only fields, invalid values, and save failures. After a successful save, reload and read the record through Entity Service to prove persistence. Also switch records and workspace contexts to confirm the screen does not retain another context's values.
Existing FormGenerator integrations
FormGenerator remains a compatibility entry point and delegates to GsbForm. New
code should use GsbForm. The older fieldRenderers map belongs to that compatibility
wrapper; use registry and controlOverrides with GsbForm rather than copying the
old renderer pattern into new screens.
docs/guides/form-generation.md + @gsb-core/react-forms