File validators

What is a file validator?

A file validator is a small rule that checks one file at a time. It looks at the file’s details (like image dimensions, metadata, duration, or other properties) and decides whether the file is allowed.

  • If the file breaks your rule, the validator reports an error (with a short message users can see).
  • If everything is okay, it reports nothing and the file is accepted.

Use file validators when you want to keep uploads clean and on-brief without reviewing files manually.

How it can be used

  • Enforce image shape or quality (for example, minimum width/height or a specific aspect ratio like 1:1 or 16:9).
  • Filename specific patterns.
  • Require certain metadata (for example, EXIF camera tag present, or PDF page count in a range).
  • Disallow specific content types (for example, animated GIFs for hero images).
  • Enforce video duration limits (for example, max 30 seconds).

If you need rules that depend on multiple files together (for example, “exactly one cover image plus three gallery images,”), use a collection validator instead.

What users will see

When a file doesn’t meet your rule, the uploader shows a clear message telling the user what to fix. Your team can also read the same results through events and the API for logging or analytics. See: Events and Uploader API.

Validation model and signature

A validator descriptor can be one of the following:

  1. Sync validator function
1type SyncValidator = (
2 file: OutputFileEntry,
3 api: UploaderPublicApi
4) => OutputErrorFile | undefined;

See also: OutputFileEntry, UploaderPublicApi

  1. Async validator function
1type AsyncValidator = (
2 file: OutputFileEntry,
3 api: UploaderPublicApi,
4 { signal }: { signal: AbortSignal }
5) => Promise<OutputErrorFile | undefined>;

See also: OutputFileEntry, UploaderPublicApi, AbortSignal

  1. Object
1type ValidatorDescriptor = {
2 runOn: "add" | "upload" | "change";
3 validator: SyncValidator | AsyncValidator;
4};

runOn decides when the validator runs:

  • add — when the file is added to the uploader (before upload). Use this for checks that depend on the file itself.
  • upload — after a successful upload (server-side fileInfo may be available). Use this for checks that depend on fileInfo that appears only after upload.
  • change — when the file is added, uploaded, or whenever the file is being edited using the Image Editor

By default, validator runs on change if not specified.

Error type returned on failure:

1type OutputErrorFile = {
2 message: string;
3 payload?: Record<string, unknown>;
4};

Here, payload is an optional object where you can include extra details about the error (for example, actual vs expected dimensions) and then catch them in your app via events or the API.

Sync validators

Best for simple, instant checks that use properties already on the file entry (for example, filename rules or basic pattern checks).

Example: Only allow spaces in filenames

1const noSpacesInFilename = {
2 runOn: "add",
3 validator: (entry) => {
4 if (entry.fileName?.includes(" ")) {
5 return { message: "Filenames cannot contain spaces" };
6 }
7 },
8};
9
10const config = document.querySelector("uc-config");
11config.fileValidators = [noSpacesInFilename];

Async validators

Best for heavier or deferred checks that run in the browser or on your server (for example, content heuristics).

Example: Only allow XLS tables with a specific sheet name

1const hasRequiredSheet = {
2 runOn: "add",
3 validator: async (entry, api, { signal }) => {
4 if (entry.mimeType !== "application/vnd.ms-excel") {
5 return;
6 }
7 // Call external API or use a library to inspect the file content
8 // Here we assume `checkForRequiredSheet` is a function that checks for the sheet
9 const hasSheet = await checkForRequiredSheet(entry.file, {
10 signal,
11 });
12 if (!hasSheet) {
13 return { message: 'Spreadsheet must contain a "Data" sheet' };
14 }
15 },
16};
17const config = document.querySelector("uc-config");
18config.fileValidators = [hasRequiredSheet];

In this example we use add to run the validator when the file is added only, because we rely on the file object, so running it again on change or upload would be redundant.

Async validators receive an AbortSignal as the third argument. The uploader aborts your validator when the file is removed or when it exceeds the configured timeout (set via validationTimeout).

If your validator was aborted due to these reasons, its result is ignored and treated as if it returned nothing (the file passes validation).

If your validator exceeds the timeout, the uploader won’t re-run it again for that file even if the runOn condition is met again.

If your validator throws an error (for example, due to a network failure), the uploader treats it as a successful validation (the file passes validation).

Errors API

When a file fails validation, the uploader shows the error message to users in the UI. The same error details are also available via events and the Uploader API for logging or analytics.

Example: Read errors from the change event

1const ctx = document.querySelector("uc-upload-ctx-provider");
2ctx.addEventListener("change", (e) => {
3 const collectionState = e.detail;
4 collectionState.allEntries.forEach((entry) => {
5 if (entry.errors.length > 0) {
6 console.error("File errors:", entry.errors);
7 }
8 });
9});

See also: Events

Example: Read errors from the Uploader API

1const ctx = document.querySelector("uc-upload-ctx-provider");
2const api = ctx.getAPI();
3const collectionState = api.getCollectionState();
4collectionState.allEntries.forEach((entry) => {
5 if (entry.errors.length > 0) {
6 console.error("File errors:", entry.errors);
7 }
8});

See also: Uploader API

Each error from custom validators has the following shape:

1type OutputErrorFile = {
2 type: "CUSTOM_ERROR";
3 message: string;
4 payload?: Record<string, unknown>;
5};

Where message and payload come from your validator return value.

Error localization

Show error messages in the user’s language:

  • Single-language projects: return plain strings from your validators.
  • Multi-language projects: define localization keys, extend your locales, and reference those keys from validators (for example via the API helper).

Learn how to set up keys and extend locales: Localization

Validation queue

Validation for each file runs through a shared queue. This keeps the UI responsive when many files are added at once and prevents heavy checks from spiking CPU usage.

How it works:

  • Every file validators run (sync or async) is scheduled as a task in the queue.
  • The queue is shared across files; tasks are interleaved fairly.
  • The number of tasks that can run in parallel is controlled by validationConcurrency.
  • If a task runs too long, it can be aborted based on validationTimeout (async validators receive an AbortSignal).

This queue applies to both your custom validators and built-in checks (e.g., images-only, file size, and file type). That way, all validation work follows the same scheduling rules.

Caveats

  • It’s expected that validator function won’t change by reference during the uploader lifecycle. If you’re using some reactive framework (React, Vue, etc.), make sure to memoize your validator functions.
  • OutputFileEntry available properties could vary based on the file status and upload source:
    • For example, fileInfo is only available after a successful upload (from local file or remote sources like URL or social)
    • file (the original File object) is only available for local files and not for remote sources.
    • cdnUrl and cdnUrlModifiers are only available after a successful upload and could be changed by image editing. If you need to validate these, use runOn: 'change' to re-validate after edits.

Best practices

  • Choose the right runOn for your rule to avoid redundant checks:
    • Use add for checks that depend only on the file itself (for example, filename rules or basic pattern checks).
    • Use upload for checks that depend on fileInfo that appears only after upload (for example, image dimensions or EXIF metadata).
    • Use change for checks that should run whenever the file is added, uploaded, or edited (for example, if you want to re-validate after image edits).
  • Adjust concurrency based on your validator complexity (default is 100):
    • Use lower concurrency for CPU or network heavy work (uploading files to the server, WASM, deep inspection).
    • Use higher concurrency for lightweight rules to speed up validation for large selections.
  • Adjust timeout based on your async validator average execution time (default is 15 seconds)