File handling
SJSF provides several ways of working with files.
Data URL
Section titled “Data URL”When using file
or files
fields, the form automatically converts
uploaded files into Data URL strings.
Advantages:
- Easy-to-use format compatible with JSON Schema validators
- Files can be provided from the server (as initial values or when re-sending form data after a server-side validation error)
Disadvantages:
- Base64 encoding increases file size by about 30%
- Requires encoding/decoding steps to work with the actual file
- Some metadata is lost
You can use the createDataURLtoBlob
and fileToDataURL
functions
(available in both browser and server environments) from
the @sjsf/form/lib/file
module to convert files to and from Data URLs.
Native File
Section titled “Native File”When using native-file
or native-files
fields, uploaded files are
represented directly as File
objects.
Advantages:
- No encoding/decoding required
- File metadata is preserved
Disadvantages:
- Cannot be provided from the server
- May cause issues for JSON-oriented tools (e.g. JSON Schema validators)
SvelteKit
Section titled “SvelteKit”With any file handling method, you must set enctype="multipart/form-data"
on the form.
For native file handling, add the following transport
hook in your hooks.ts
file:
import type { Transport } from '@sveltejs/kit';
export const transport: Transport = { File: { encode: (v) => v instanceof File && 'file', decode: () => undefined }};
Preloading files
Section titled “Preloading files”Another approach is to preload files into a storage backend as soon as the user selects them. Instead of embedding file data in the form, the form value only stores a lightweight file identifier (ID).
This avoids the drawbacks of Data URLs and native File
objects:
- Smaller payloads
- No
base64
overhead - Files can be validated or processed on the server before submission
- Files can be provided from the server
See the advanced example preupload-file
for more information.
Validation
Section titled “Validation”SJSF provides several options for validating files.
Custom keyword
Section titled “Custom keyword”The most flexible approach is to add a custom JSON Schema keyword.
This method works consistently on both the server and the client,
and the timing of validation can be controlled with the fieldsValidationMode
option.
<script lang="ts" module> declare module "@sjsf/form" { interface Schema { maxSizeBytes?: number; } }</script>
<script lang="ts"> import { type Ajv } from "ajv"; import { BasicForm, createForm, ON_CHANGE } from "@sjsf/form"; import { formatFileSize } from "@sjsf/form/validators/file-size"; import { addFormComponents } from "@sjsf/ajv8-validator";
import * as defaults from "@/lib/form/defaults";
function addKeywords(ajv: Ajv): Ajv { ajv.addKeyword({ keyword: "maxSizeBytes", validate(max: number, data: unknown) { if (data === undefined) { return true; } if (!(data instanceof File)) { throw new Error(`Expected "File", but got "${typeof data}"`); } return data.size <= max; }, error: { message: (ctx) => `Max file size ${formatFileSize(ctx.schema)}`, }, }); return ajv; }
const form = createForm({ ...defaults, createValidator: (options) => defaults.createValidator({ ...options, ajvPlugins: (ajv) => addKeywords(addFormComponents(ajv)), }), schema: { title: "File", maxSizeBytes: 1024 * 4, }, uiSchema: { "ui:components": { unknownField: "nativeFileFieldWrapper", }, }, fieldsValidationMode: ON_CHANGE, });</script>
<BasicForm {form} />
However, not all validators support custom keywords,
and file storage formats other than File
may require additional preprocessing before validation.
FileList validator
Section titled “FileList validator”Alternatively, you can implement the AsyncFileListValidator
interface for your validator.
This validator runs right after the user selects files,
but before the form state is updated.
A practical example is the createFileSizeValidator
helper
from the @sjsf/form/validators/file-size
module, which checks maximum file size.
Once this module is included, you can set the maxFileSizeBytes
UI option on file fields.
<script> import { BasicForm, createForm } from "@sjsf/form";
import * as defaults from "@/lib/form/defaults"; import { createFileSizeValidator, formatFileSize, } from "@sjsf/form/validators/file-size";
const form = createForm({ ...defaults, createValidator: (options) => Object.assign( defaults.createValidator(options), createFileSizeValidator( ({ file, maxSizeBytes }) => `File ${file.name} is too large, max file size ${formatFileSize(maxSizeBytes)}`, options ) ), schema: { type: "string", title: "File", format: "data-url", }, uiSchema: { "ui:components": { stringField: "fileField", }, "ui:options": { maxFileSizeBytes: 1024 * 4, }, }, });</script>
<BasicForm {form} />