Skip to content

@absoluteui/services/file

File reading, magic-byte type detection, validation with accessible messages, images, checksums, and pickers.

import { ... } from '@absoluteui/services/file';

Everything between “the user chose a file” and “the file is safe to use”: reading with size limits, detecting the real type from its first bytes, validating against your rules, resizing images, checksums, dropped folders, pasted files, and the file picker. The Files and downloads guide walks through validation, streaming reads of large files, and images; the recipes below cover the remaining everyday tasks.

Names and declared types come from the user’s machine and can be wrong. detectFileType reads the first bytes; active flags content that can run code (SVG, HTML, executables).

import { detectFileType } from '@absoluteui/services/file';
const pdfBytes = Uint8Array.of(0x25, 0x50, 0x44, 0x46, 0x2d, 0x31, 0x2e, 0x37); // '%PDF-1.7'
const renamed = new File([pdfBytes], 'photo.png', { type: 'image/png' });
const type = await detectFileType(renamed);
[type?.mime, type?.active]; // => ['application/pdf', false]
await detectFileType(new File(['plain words'], 'notes.bin')); // => { mime: 'text/plain', extension: 'txt', confidence: 'low', active: false }

How do I show validation errors in the user’s language?

Section titled “How do I show validation errors in the user’s language?”

validateFiles returns stable codes and parameters with every error. Pass messages to translate; return undefined to keep the English default. Messages never contain the file name, so you place it where your layout needs it.

import { validateFiles, type FileMessageCatalog } from '@absoluteui/services/file';
const de: FileMessageCatalog = (code, p) =>
code === 'SVC_FILE_TOO_LARGE' ? `Die Datei ist größer als ${String(p['limitText'])}.` : undefined;
const cv = new File(['x'.repeat(2_000)], 'lebenslauf.txt', { type: 'text/plain' });
const { rejected } = await validateFiles([cv], { maxFileSize: 1_000, locale: 'de', messages: de });
rejected[0]?.errors.map((e) => e.code); // => ['SVC_FILE_TOO_LARGE']
const lines = rejected.map((r) => `${r.file.name}: ${r.errors.map((e) => e.message).join(' ')}`); // e.g. ['lebenslauf.txt: Die Datei ist größer als 1 Kilobyte.']

How do I shrink a photo before uploading it?

Section titled “How do I shrink a photo before uploading it?”

compressImage resizes and re-encodes until the result fits maxBytes. Orientation metadata is applied, and re-encoding removes EXIF data such as GPS location.

import { compressImage, formatFileSize } from '@absoluteui/services/file';
declare const photo: File; // profile photo chosen by the employee
const { blob, width, height, withinLimit } = await compressImage(photo, { maxBytes: 300_000, maxWidth: 800, maxHeight: 800, type: 'image/webp' });
const hint = withinLimit ? `${String(width)} × ${String(height)}, ${formatFileSize(blob.size, { style: 'long' })}` : 'The photo is still too large.';

How do I verify a file against a published checksum?

Section titled “How do I verify a file against a published checksum?”

fileChecksum hashes in slices, so large files do not have to fit in memory; onProgress feeds a progress bar.

import { fileChecksum } from '@absoluteui/services/file';
const export2026 = new File(['employee_id,name\n42,Ada Lovelace\n'], 'employees.csv', { type: 'text/csv' });
const bar = document.createElement('progress');
const crc = await fileChecksum(export2026, 'crc32', { onProgress: (done, total) => { bar.value = done / total; } });
crc.length; // => 8
(await fileChecksum(export2026, 'SHA-256')).length; // => 64
  • Every reader has a size limit (DEFAULT_MAX_READ_BYTES); larger files reject with SVC_FILE_TOO_LARGE instead of exhausting memory. Use readChunks or readLines for big files.
  • A name that disagrees with the content is SVC_FILE_TYPE_MISMATCH, not a pass: an executable renamed to .png is rejected.
  • Image functions need createImageBitmap; files the browser cannot decode (for example HEIC in most browsers) fail with SVC_FILE_IMAGE_UNREADABLE in validation.
import { readAsText } from '@absoluteui/services/file';
const log = new File(['x'.repeat(2_048)], 'audit.log');
await readAsText(log, { maxBytes: 1_024 }); // => throws SVC_FILE_TOO_LARGE

fileChecksum

Lowercase hex checksum of a file. CRC-32 is computed incrementally over 1 MiB chunks; SHA-* use crypto.subtle (secure contexts) within maxBytes.

function fileChecksum(blob: Blob, algorithm?: ChecksumAlgorithm, options?: FileChecksumOptions): Promise<string>;

Example:

const file = new File(['hello'], 'a.txt', { type: 'text/plain' });
await fileChecksum(file, 'SHA-256'); // => '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
const bar = document.createElement('progress');
await fileChecksum(file, 'crc32', { onProgress: (n, total) => { bar.value = n / total; } }); // => '3610a686'

DEFAULT_SHA_MAX_BYTES

Default in-memory limit for SHA checksums (256 MiB).

const DEFAULT_SHA_MAX_BYTES: number;

FileChecksumOptions

interface FileChecksumOptions {
readonly maxBytes?: number;
readonly signal?: AbortSignal;
readonly onProgress?: (loaded: number, total: number) => void;
}

ChecksumAlgorithm

type ChecksumAlgorithm = 'SHA-256' | 'SHA-384' | 'SHA-512' | 'crc32';

detectFileType

Detects a Blob’s (or bytes’) real type from magic bytes, reading only the first 64 KiB.

function detectFileType(input: Blob | Uint8Array | ArrayBuffer): Promise<DetectedFileType | undefined>;

Example:

const file = new File([Uint8Array.of(0x25, 0x50, 0x44, 0x46, 0x2d, 0x31, 0x2e, 0x37)], 'payslip.pdf'); // '%PDF-1.7'
const type = await detectFileType(file);
type?.mime; // => 'application/pdf'
if (type?.active) console.warn('Active content is not allowed.');

detectFileTypeFromBytes

Detects a type from the first bytes of a file (pass at least DETECT_SAMPLE_BYTES for OOXML; 4 KiB covers everything else). Returns undefined for unknown binary data.

function detectFileTypeFromBytes(bytes: Uint8Array): DetectedFileType | undefined;

Example:

detectFileTypeFromBytes(Uint8Array.of(0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a));
// => { mime: 'image/png', extension: 'png', confidence: 'high', active: false }

DETECT_SAMPLE_BYTES

Bytes needed by detectFileTypeFromBytes for every rule (the OOXML scan uses up to 64 KiB).

const DETECT_SAMPLE_BYTES: number;

DetectedFileType

A detected type. active marks content that can execute code (SVG, HTML, executables).

interface DetectedFileType {
readonly mime: string;
readonly extension: string;
readonly confidence: 'high' | 'low';
readonly active: boolean;
}

collectDroppedFiles

Collects files from a drop, walking dropped folders (webkitGetAsEntry) with relative paths and limits. Call it synchronously inside the drop handler: the browser empties the DataTransfer after the handler returns, so entries are captured before the first await.

function collectDroppedFiles(dataTransfer: DataTransferLike, options?: CollectDroppedFilesOptions): Promise<CollectDroppedFilesResult>;

Example:

const zone = document.createElement('div');
zone.addEventListener('drop', async (e: DragEvent) => {
e.preventDefault();
if (!e.dataTransfer) return;
const { files, truncated } = await collectDroppedFiles(e.dataTransfer, { maxFiles: 500 });
});

filesFromClipboardEvent

Files pasted with Ctrl+V (screenshots, copied files).

function filesFromClipboardEvent(event: {
readonly clipboardData: DataTransferLike | null;
}): File[];

Example:

const editor = document.createElement('textarea');
const attachments: File[] = [];
editor.addEventListener('paste', (e: ClipboardEvent) => { attachments.push(...filesFromClipboardEvent(e)); });

openFilePicker

Opens the system file picker; resolves [] when cancelled. Must be called from a user gesture (click, key press). Uses showOpenFilePicker when available (not for folders), else a hidden &lt;input type=file> that is created, clicked and removed. Focus returns to the previously focused element afterwards.

function openFilePicker(options?: OpenFilePickerOptions): Promise<File[]>;

Example:

const button = document.createElement('button');
button.addEventListener('click', async () => {
const files = await openFilePicker({ accept: 'image/*', multiple: true });
});

CollectDroppedFilesOptions

interface CollectDroppedFilesOptions {
readonly maxFiles?: number;
readonly maxDepth?: number;
readonly signal?: AbortSignal;
}

CollectDroppedFilesResult

interface CollectDroppedFilesResult {
readonly files: DroppedFile[];
readonly truncated: boolean;
}

DataTransferLike

The parts of DataTransfer used here.

interface DataTransferLike {
readonly items?: ArrayLike<ItemLike> | null;
readonly files?: ArrayLike<File> | null;
}

DroppedFile

A dropped file with its path relative to the drop (folder names included).

interface DroppedFile {
readonly file: File;
readonly path: string;
}

OpenFilePickerOptions

interface OpenFilePickerOptions {
readonly accept?: string;
readonly multiple?: boolean;
readonly directory?: boolean;
readonly signal?: AbortSignal;
}

compressImage

Re-encodes an image to fit maxBytes: binary search over quality, then bounded downscaling. Default type is JPEG (every browser encodes it; unsupported types silently become PNG). Returns the input unchanged when it already fits and force is false.

function compressImage(blob: Blob, options: CompressImageOptions): Promise<CompressedImage>;

Example:

declare const photo: File;
const { blob, withinLimit } = await compressImage(photo, { maxBytes: 500_000, maxWidth: 2000, maxHeight: 2000 });

getImageDimensions

Width and height in pixels after EXIF orientation. Throws SVC_FILE_IMAGE_DECODE for non-images.

function getImageDimensions(blob: Blob): Promise<ImageDimensions>;

Example:

declare const file: File; // an image chosen by the user
const { width, height } = await getImageDimensions(file);

resizeImage

Resizes and re-encodes an image with EXIF orientation applied. Re-encoding strips metadata, which removes EXIF GPS location (a privacy benefit for uploads).

function resizeImage(blob: Blob, options?: ResizeImageOptions): Promise<ProcessedImage>;

Example:

declare const photo: File;
const { blob } = await resizeImage(photo, { maxWidth: 1600, maxHeight: 1600, type: 'image/webp', quality: 0.8 });

CompressedImage

Result of compressImage; withinLimit is false when the target could not be reached.

interface CompressedImage extends ProcessedImage {
readonly quality: number | undefined;
readonly withinLimit: boolean;
}

CompressImageOptions

interface CompressImageOptions extends Omit<ResizeImageOptions, 'quality'> {
readonly maxBytes: number;
readonly minQuality?: number;
readonly maxQuality?: number;
readonly maxIterations?: number;
readonly maxScaleSteps?: number;
readonly force?: boolean;
}

ImageDimensions

interface ImageDimensions {
readonly width: number;
readonly height: number;
}

ProcessedImage

A re-encoded image.

interface ProcessedImage extends ImageDimensions {
readonly blob: Blob;
}

ResizeImageOptions

interface ResizeImageOptions {
readonly maxWidth?: number;
readonly maxHeight?: number;
readonly fit?: 'contain' | 'cover';
readonly type?: ImageOutputType;
readonly quality?: number;
readonly enlarge?: boolean;
readonly background?: string;
}

ImageOutputType

type ImageOutputType = 'image/webp' | 'image/jpeg' | 'image/png';

formatFileSize

Human-readable file size via formatBytes; use style: 'long' for screen readers.

function formatFileSize(bytes: number, options?: FormatBytesOptions): string;

Example:

formatFileSize(2_400_000); // => '2.4 MB'
formatFileSize(2_400_000, { style: 'long' }); // => '2.4 megabytes'

getExtension

Lowercase extension without the dot (tar.gz kept together); undefined when none.

function getExtension(name: string): string | undefined;

Example:

getExtension('Photo.JPG'); // => 'jpg'
getExtension('backup.TAR.GZ'); // => 'tar.gz'

splitFileName

Splits a name into base and extension, keeping compound extensions (tar.gz) together. Dotfiles (.env) have no extension. Directory parts are dropped.

function splitFileName(name: string): FileNameParts;

Example:

splitFileName('archive.tar.gz'); // => { base: 'archive', extension: 'tar.gz' }
splitFileName('.env'); // => { base: '.env', extension: '' }

withExtension

Replaces (or removes, with '') the extension.

function withExtension(name: string, extension: string): string;

Example:

withExtension('report.csv', 'xlsx'); // => 'report.xlsx'
withExtension('archive.tar.gz', '.zip'); // => 'archive.zip'

FileNameParts

A file name split into base and extension.

interface FileNameParts {
readonly base: string;
readonly extension: string;
}

readAsArrayBuffer

Reads a Blob into an ArrayBuffer, enforcing maxBytes.

function readAsArrayBuffer(blob: Blob, options?: ReadOptions): Promise<ArrayBuffer>;

Example:

const file = new File(['hello'], 'a.txt', { type: 'text/plain' });
const buffer = await readAsArrayBuffer(file);
buffer.byteLength; // => 5

readAsBytes

Reads a Blob into a Uint8Array, enforcing maxBytes.

function readAsBytes(blob: Blob, options?: ReadOptions): Promise<Uint8Array<ArrayBuffer>>;

Example:

const file = new File(['hello'], 'a.txt', { type: 'text/plain' });
const bytes = await readAsBytes(file.slice(0, 2));
Array.from(bytes); // => [104, 101]

readAsDataUrl

Reads a Blob as a base64 data: URL (default limit 10 MiB; prefer createObjectUrl for previews).

function readAsDataUrl(blob: Blob, options?: ReadOptions): Promise<string>;

Example:

const file = new File(['hello'], 'a.txt', { type: 'text/plain' });
await readAsDataUrl(file, { maxBytes: 2_000_000 }); // => 'data:text/plain;base64,aGVsbG8='

readAsText

Reads a Blob as text. A UTF-8 or UTF-16 byte-order mark selects the encoding and is removed.

function readAsText(blob: Blob, options?: ReadAsTextOptions): Promise<string>;

Example:

const file = new File(['id,name\n1,Ada'], 'employees.csv', { type: 'text/csv' });
const text = await readAsText(file, { maxBytes: 5_000_000, fatal: true }); // => 'id,name\n1,Ada'

readChunks

Yields consecutive slices as Uint8Arrays without loading the whole file.

function readChunks(blob: Blob, options?: ReadChunksOptions): AsyncGenerator<Uint8Array<ArrayBuffer>, void, undefined>;

Example:

const file = new File(['hello world'], 'a.txt');
const sizes: number[] = [];
for await (const chunk of readChunks(file, { chunkSize: 4 })) sizes.push(chunk.length);
sizes; // => [4, 4, 3]

readLines

Yields lines (without terminators) from a text file, streaming. Handles \n, \r\n and \r, including terminators and multi-byte characters split across chunks. A final empty line after a trailing terminator is not yielded.

function readLines(blob: Blob, options?: ReadLinesOptions): AsyncGenerator<string, void, undefined>;

Example:

const logFile = new File(['INFO start\nERROR disk full\nERROR retry failed\n'], 'app.log');
let errors = 0;
for await (const line of readLines(logFile)) if (line.includes('ERROR')) errors++;
errors; // => 2

streamFile

Exposes a Blob as a back-pressured ReadableStream of fixed-size chunks (for uploads or pipeTo).

function streamFile(blob: Blob, options?: ReadChunksOptions): ReadableStream<Uint8Array<ArrayBuffer>>;

Example:

const file = new File(['hello'], 'a.txt');
let total = 0;
const writable = new WritableStream<Uint8Array>({ write(chunk) { total += chunk.length; } });
await streamFile(file, { chunkSize: 256 * 1024 }).pipeTo(writable);
total; // => 5

DEFAULT_MAX_READ_BYTES

Default size limit for whole-file reads (64 MiB). Use readChunks/readLines for larger files.

const DEFAULT_MAX_READ_BYTES: number;

ReadAsTextOptions

interface ReadAsTextOptions extends ReadOptions {
readonly encoding?: string;
readonly fatal?: boolean;
}

ReadChunksOptions

interface ReadChunksOptions {
readonly chunkSize?: number;
readonly start?: number;
readonly end?: number;
readonly signal?: AbortSignal;
}

ReadLinesOptions

interface ReadLinesOptions {
readonly encoding?: string;
readonly fatal?: boolean;
readonly maxLineLength?: number;
readonly chunkSize?: number;
readonly signal?: AbortSignal;
}

ReadOptions

interface ReadOptions {
readonly maxBytes?: number;
readonly signal?: AbortSignal;
}

defaultFileMessage

English default message for a file error code (for building catalogs or custom flows).

function defaultFileMessage(code: FileErrorCode, params: FileMessageParams): string;

Example:

defaultFileMessage('SVC_FILE_TOO_LARGE', { limitText: '10 megabytes' }); // => 'The file is larger than 10 megabytes.'

validateFiles

Validates files for upload: type (declared and magic bytes), sizes, count, total size, active content and image dimensions. Messages are actionable, never contain the file name, and use long size units (“10 megabytes”) so screen readers read them correctly.

function validateFiles<F extends File>(files: Iterable<F> | ArrayLike<F>, options?: ValidateFilesOptions): Promise<FileValidationResult<F>>;

Example:

const files = [
new File([Uint8Array.of(0x25, 0x50, 0x44, 0x46, 0x2d, 0x31, 0x2e, 0x37)], 'contract.pdf', { type: 'application/pdf' }),
new File(['name,email'], 'employees.csv', { type: 'text/csv' }),
];
const { accepted, rejected } = await validateFiles(files, { accept: 'image/*,.pdf', maxFileSize: 10_000_000, maxFiles: 5 });
accepted.map((f) => f.name); // => ['contract.pdf']
rejected.map((r) => r.file.name); // => ['employees.csv']
const summary = rejected.map((r) => `${r.file.name}: ${r.errors.map((e) => e.message).join(' ')}`);

FileRejection

interface FileRejection<F extends File = File> {
readonly file: F;
readonly errors: readonly FileValidationError[];
}

FileValidationError

interface FileValidationError {
readonly code: FileErrorCode;
readonly message: string;
readonly params: FileMessageParams;
}

FileValidationResult

interface FileValidationResult<F extends File = File> {
readonly accepted: F[];
readonly rejected: FileRejection<F>[];
}

ImageDimensionLimits

interface ImageDimensionLimits {
readonly minWidth?: number;
readonly maxWidth?: number;
readonly minHeight?: number;
readonly maxHeight?: number;
}

ValidateFilesOptions

interface ValidateFilesOptions {
readonly accept?: string | readonly string[];
readonly maxFileSize?: number;
readonly minFileSize?: number;
readonly maxTotalSize?: number;
readonly maxFiles?: number;
readonly allowEmpty?: boolean;
readonly rejectActiveContent?: boolean;
readonly imageDimensions?: ImageDimensionLimits;
readonly detectType?: boolean;
readonly locale?: LocaleInput;
readonly messages?: FileMessageCatalog;
}

FileErrorCode

Stable codes for file validation errors.

type FileErrorCode = 'SVC_FILE_TYPE_NOT_ALLOWED' | 'SVC_FILE_TYPE_MISMATCH' | 'SVC_FILE_ACTIVE_CONTENT' | 'SVC_FILE_EMPTY' | 'SVC_FILE_TOO_LARGE' | 'SVC_FILE_TOO_SMALL' | 'SVC_FILE_TOTAL_TOO_LARGE' | 'SVC_FILE_TOO_MANY' | 'SVC_FILE_IMAGE_TOO_SMALL' | 'SVC_FILE_IMAGE_TOO_LARGE' | 'SVC_FILE_IMAGE_UNREADABLE';

FileMessageCatalog

Returns a message for a code, or undefined to use the English default. Messages never include the file name; the UI prefixes it (“photo.png: The file is larger than 10 megabytes.”).

type FileMessageCatalog = (code: FileErrorCode, params: FileMessageParams) => string | undefined;

FileMessageParams

Values available to messages (limits are pre-formatted for the locale, sizes in the long style).

type FileMessageParams = Readonly<Record<string, string | number>>;