@absoluteui/services/file
File reading, magic-byte type detection, validation with accessible messages, images, checksums, and pickers.
import { ... } from '@absoluteui/services/file';How to use
Section titled “How to use”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.
How do I find out what a file really is?
Section titled “How do I find out what a file really is?”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 employeeconst { 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; // => 64What goes wrong?
Section titled “What goes wrong?”- Every reader has a size limit (
DEFAULT_MAX_READ_BYTES); larger files reject withSVC_FILE_TOO_LARGEinstead of exhausting memory. UsereadChunksorreadLinesfor big files. - A name that disagrees with the content is
SVC_FILE_TYPE_MISMATCH, not a pass: an executable renamed to.pngis rejected. - Image functions need
createImageBitmap; files the browser cannot decode (for example HEIC in most browsers) fail withSVC_FILE_IMAGE_UNREADABLEin 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_LARGEAPI reference
Section titled “API reference”Checksum
Section titled “Checksum”Functions
Section titled “Functions”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'Constants
Section titled “Constants”DEFAULT_SHA_MAX_BYTES
Default in-memory limit for SHA checksums (256 MiB).
const DEFAULT_SHA_MAX_BYTES: number;Interfaces
Section titled “Interfaces”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';Detect
Section titled “Detect”Functions
Section titled “Functions”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 }Constants
Section titled “Constants”DETECT_SAMPLE_BYTES
Bytes needed by detectFileTypeFromBytes for every rule (the OOXML scan uses up to 64 KiB).
const DETECT_SAMPLE_BYTES: number;Interfaces
Section titled “Interfaces”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;}DOM helpers
Section titled “DOM helpers”Functions
Section titled “Functions”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
<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 });});Interfaces
Section titled “Interfaces”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;}Functions
Section titled “Functions”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 userconst { 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 });Interfaces
Section titled “Interfaces”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';Functions
Section titled “Functions”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'Interfaces
Section titled “Interfaces”FileNameParts
A file name split into base and extension.
interface FileNameParts { readonly base: string; readonly extension: string;}Functions
Section titled “Functions”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; // => 5readAsBytes
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; // => 2streamFile
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; // => 5Constants
Section titled “Constants”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;Interfaces
Section titled “Interfaces”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;}Validate
Section titled “Validate”Functions
Section titled “Functions”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(' ')}`);Interfaces
Section titled “Interfaces”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>>;