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
base64overhead - 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, validator: (options) => defaults.validator({ ...options, ajvPlugins: (ajv) => addKeywords(addFormComponents(ajv)), }), schema: { title: "File", maxSizeBytes: 1024 * 4, }, uiSchema: { "ui:components": { unknownField: "unknownNativeFileField", }, }, 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, validator: (options) => ({ ...defaults.validator(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} />