Forms & files
A small form layer removes the boilerplate from every create/edit screen, and a blob-storage abstraction handles uploads and image processing.
Forms
Forms are built from <Field> + <FormGrid> and submitted with useSubmitForm, which handles validation, the success toast, and mapping server-side validation errors back onto the right fields:
const form = useSubmitForm({ schema, endpoint: '/products', method: 'POST' });
<FormGrid onSubmit={form.submit}>
<Field control={form.control} name="name" label="Name" />
<Field control={form.control} name="price" label="Price" type="number" />
</FormGrid>Validation is shared with the backend's FluentValidation rules: a 400 ProblemDetails with field errors surfaces inline, so the client and server agree on what's valid. Designed loading, empty, and error states are shared primitives — no bare spinners or raw error JSON.
Files & images
File uploads use <FileUpload> (or the uploadFile helper) against a blob-storage abstraction (IBlobStore). The default provider stores blobs on the local filesystem; Azure Blob, S3, or R2 swap in via config without touching call sites.
Images are processed on the server with Magick.NET. The profile picture flow is the reference: click your avatar, upload an image, and it's center-cropped to a square and optimized before it's stored. In the Sales demo, products carry a gallery of drag-and-drop images with the first as the thumbnail.
Community edition
File uploads + image processing are a Pro feature. Community uses initials-only avatars. See Editions.
The form and upload primitives are the same ones every built-in screen uses, so your features look and behave consistently out of the box.