@absoluteui/services/download
Downloads of Blobs, text, JSON, CSV, and URLs with progress, save pickers, and safe file names.
import { ... } from '@absoluteui/services/download';How to use
Section titled “How to use”Saves files to the user’s device: Blobs, text, JSON, CSV, data: URLs, and remote files, with safe file names, automatic object-URL cleanup, a save picker for large files, and status text for live regions. Every function returns a DownloadStatus instead of throwing for user-level outcomes. The Files and downloads guide covers CSV exports, remote downloads with progress, and the safety rules; the recipes below cover the remaining tasks.
How do I export data and tell screen-reader users it worked?
Section titled “How do I export data and tell screen-reader users it worked?”Every save returns a status; describeDownloadStatus turns it into a sentence for a polite live region. Override labels to translate.
import { describeDownloadStatus, downloadJson } from '@absoluteui/services/download';
const region = document.createElement('div');region.setAttribute('aria-live', 'polite');const status = downloadJson({ period: '2026-09', headcount: 214 }, 'headcount-2026-09.json', { pretty: true });region.textContent = describeDownloadStatus(status); // e.g. 'Download started: headcount-2026-09.json'import { describeDownloadStatus } from '@absoluteui/services/download';
describeDownloadStatus({ ok: false, method: 'none', fileName: 'payslip.pdf', reason: 'error' }); // => 'Download failed: payslip.pdf. Try again.'describeDownloadStatus({ ok: true, method: 'anchor', fileName: 'payslip.pdf' }, { labels: { started: 'Téléchargement lancé : {fileName}' } }); // => 'Téléchargement lancé : payslip.pdf'How do I save a large export straight to disk?
Section titled “How do I save a large export straight to disk?”saveWithPicker streams into the file the user picks (File System Access API) and falls back to a normal download elsewhere. Pass a function as the source so the export starts only after the user has chosen a file; call it from a click handler.
import { describeDownloadStatus, saveWithPicker } from '@absoluteui/services/download';
const button = document.createElement('button');button.textContent = 'Export all employees';button.addEventListener('click', async () => { const status = await saveWithPicker(async () => (await fetch('/api/exports/employees.csv')).blob(), { suggestedName: 'employees-2026-10-05.csv', types: [{ description: 'CSV file', accept: { 'text/csv': ['.csv'] } }], }); if (!status.cancelled) console.info(describeDownloadStatus(status));});How do I download several files at once?
Section titled “How do I download several files at once?”Browsers block or prompt for bursts of downloads. createDownloadQueue spaces them out (one at a time, 400 ms apart by default); for many files, offer one ZIP from @absoluteui/services/zip instead.
import { createDownloadQueue, downloadUrl } from '@absoluteui/services/download';
const months = ['2026-07', '2026-08', '2026-09'];const queue = createDownloadQueue({ concurrency: 1, delayMs: 500 });const statuses = await Promise.all(months.map((m) => queue.add(() => downloadUrl(`/api/payslips/${m}.pdf`), `payslip-${m}.pdf`)));const failed = statuses.filter((s) => !s.ok).map((s) => s.fileName);What goes wrong?
Section titled “What goes wrong?”- Names from servers and users are cleaned by
sanitizeDownloadName: path parts, control characters, and bidirectional overrides that disguise extensions are removed, and reserved Windows names are prefixed. downloadUrlrefuses non-HTTP URLs (SVC_DOWNLOAD_UNSAFE_URL) and other origins unless you passallowCrossOrigin: true(SVC_DOWNLOAD_CROSS_ORIGIN). Network and HTTP failures resolve to{ ok: false, reason: 'error' }rather than rejecting.
import { downloadUrl, sanitizeDownloadName } from '@absoluteui/services/download';
sanitizeDownloadName('../../payroll/September.csv'); // => 'September.csv'sanitizeDownloadName('CON.txt'); // => '_CON.txt' // reserved device names are changedawait downloadUrl('javascript:alert(1)'); // => throws SVC_DOWNLOAD_UNSAFE_URLawait downloadUrl('https://files.example.net/report.pdf'); // => throws SVC_DOWNLOAD_CROSS_ORIGINAPI reference
Section titled “API reference”Functions
Section titled “Functions”createDownloadQueue
Runs downloads with limited concurrency and a delay between starts.
function createDownloadQueue(options?: DownloadQueueOptions): DownloadQueue;Example:
const queue = createDownloadQueue();const files = [{ url: '/api/payslips/2026-08.pdf' }, { url: '/api/payslips/2026-09.pdf' }];for (const f of files) void queue.add(() => downloadUrl(f.url));await queue.onIdle();createObjectUrl
Creates a tracked object URL; revokeAll() releases every URL still alive.
function createObjectUrl(blob: Blob): ObjectUrlHandle;Example:
const blob = new Blob(['<svg xmlns="http://www.w3.org/2000/svg"/>'], { type: 'image/svg+xml' });const { url, revoke } = createObjectUrl(blob);const img = document.createElement('img');img.onload = revoke;img.src = url;describeDownloadStatus
Text for a live region describing a download status.
function describeDownloadStatus(value: DownloadStatus, options?: { readonly labels?: Partial<DownloadStatusLabels>;}): string;Example:
describeDownloadStatus({ ok: true, method: 'anchor', fileName: 'report.csv' }); // => 'Download started: report.csv'describeDownloadStatus({ ok: true, method: 'picker', fileName: 'report.csv' }); // => 'Saved: report.csv'describeDownloadStatus({ ok: false, method: 'picker', fileName: 'report.csv', cancelled: true }); // => 'Download cancelled.'downloadBlob
Saves a Blob through a detached <a download> click. The object URL is revoked right after
the click; the anchor is never attached to the document and focus does not move.
function downloadBlob(blob: Blob, fileName: string, options?: DownloadBlobOptions): DownloadStatus;Example:
declare const blob: Blob; // for example from a report endpointconst status = downloadBlob(blob, 'report.pdf');console.info(describeDownloadStatus(status)); // e.g. 'Download started: report.pdf'downloadCsv
Saves rows as CSV. Formula-like cells are always neutralized (CSV injection protection).
function downloadCsv<T extends object>(rows: readonly T[] | readonly (readonly unknown[])[], fileName: string, options?: DownloadCsvOptions<T>): DownloadStatus;Example:
const employees = [{ name: 'Ada Lovelace', email: 'ada@example.com' }];downloadCsv(employees, 'employees.csv', { columns: [{ key: 'name', header: 'Name' }, 'email'] });downloadDataUrl
Saves a data: URL (decoded to a Blob first; top-level data: navigation is blocked by browsers).
function downloadDataUrl(dataUrl: string, fileName: string, options?: DownloadDataUrlOptions): DownloadStatus;Example:
const canvas = document.createElement('canvas');downloadDataUrl(canvas.toDataURL('image/png'), 'chart.png');downloadJson
Saves a value as JSON using safeJsonStringify (cycles, BigInt, Map and Set are handled).
Throws SVC_DOWNLOAD_NOT_SERIALIZABLE for values JSON cannot represent.
function downloadJson(value: unknown, fileName: string, options?: DownloadJsonOptions): DownloadStatus;Example:
downloadJson({ theme: 'dark', density: 'compact' }, 'settings.json', { pretty: true });downloadText
Saves text as a file.
function downloadText(text: string, fileName: string, options?: DownloadTextOptions): DownloadStatus;Example:
downloadText('2026-09-30 09:00 Sync started', 'log.txt');downloadUrl
Fetches a URL with progress and abort, then saves it. Same-origin only unless
allowCrossOrigin; only http(s). The file name comes from fileName, then a sanitized
Content-Disposition name, then the URL, then download. Invalid or disallowed URLs throw;
network, HTTP and abort outcomes resolve to a failed status carrying error.
function downloadUrl(url: string, options?: DownloadUrlOptions): Promise<DownloadStatus>;Example:
const bar = document.createElement('progress');bar.max = 100;const controller = new AbortController();const status = await downloadUrl('/api/reports/42/pdf', { onProgress: (p) => { bar.value = p.percent ?? 0; }, signal: controller.signal });revokeAll
Revokes every object URL created by createObjectUrl that is still alive (for example on route change).
function revokeAll(): void;Example:
window.addEventListener('pagehide', revokeAll);sanitizeDownloadName
Makes a name safe to save: strips bidirectional overrides (extension spoofing), path separators, control characters and reserved device names, and limits the length while preserving the extension.
function sanitizeDownloadName(name: string, options?: SanitizeDownloadNameOptions): string;Example:
sanitizeDownloadName('invoice\u202Efdp.exe'); // => 'invoicefdp.exe'sanitizeDownloadName('../../report.csv'); // => 'report.csv'saveWithPicker
Saves through showSaveFilePicker, streaming into the file (no full copy in memory), with an
anchor fallback. Dismissing the picker resolves to { ok: false, cancelled: true }. Call from
a user gesture; pass a function as source to start fetching only after the user chose a file.
function saveWithPicker(source: SaveSource, options: SaveWithPickerOptions): Promise<DownloadStatus>;Example:
const status = await saveWithPicker(async () => (await fetch('/export')).body!, { suggestedName: 'export.csv' });Constants
Section titled “Constants”DEFAULT_DOWNLOAD_LABELS
const DEFAULT_DOWNLOAD_LABELS: DownloadStatusLabels;Interfaces
Section titled “Interfaces”DownloadBlobOptions
interface DownloadBlobOptions { readonly signal?: AbortSignal; readonly revokeDelayMs?: number;}DownloadCsvOptions
interface DownloadCsvOptions<T> extends DownloadBlobOptions { readonly columns?: readonly (string | CsvColumn<T>)[]; readonly bom?: boolean; readonly delimiter?: string; readonly eol?: '\r\n' | '\n'; readonly header?: boolean;}DownloadDataUrlOptions
interface DownloadDataUrlOptions extends DownloadBlobOptions { readonly maxBytes?: number;}DownloadJsonOptions
interface DownloadJsonOptions extends DownloadBlobOptions { readonly pretty?: boolean;}DownloadProgress
Download progress. total and percent are undefined without Content-Length.
interface DownloadProgress { readonly loaded: number; readonly total: number | undefined; readonly percent: number | undefined;}DownloadQueue
interface DownloadQueue { add(task: DownloadTask, fileName?: string): Promise<DownloadStatus>; onIdle(): Promise<void>; clear(): void; readonly pending: number; readonly running: number;}DownloadQueueOptions
interface DownloadQueueOptions { readonly concurrency?: number; readonly delayMs?: number;}DownloadStatus
Outcome of a download, for announcements (describeDownloadStatus).
interface DownloadStatus { readonly ok: boolean; readonly method: DownloadMethod; readonly fileName: string; readonly cancelled?: boolean; readonly reason?: DownloadFailureReason; readonly error?: Error; readonly bytes?: number;}DownloadStatusLabels
Live-region messages; {fileName} is replaced. Override for localization.
interface DownloadStatusLabels { readonly started: string; readonly saved: string; readonly cancelled: string; readonly aborted: string; readonly unsupported: string; readonly failed: string;}DownloadTextOptions
interface DownloadTextOptions extends DownloadBlobOptions { readonly type?: string; readonly bom?: boolean;}DownloadUrlOptions
interface DownloadUrlOptions extends DownloadBlobOptions { readonly fileName?: string; readonly onProgress?: (progress: DownloadProgress) => void; readonly fetchInit?: Omit<RequestInit, 'signal'>; readonly allowCrossOrigin?: boolean; readonly maxBytes?: number; readonly fetch?: typeof fetch;}ObjectUrlHandle
An object URL with an idempotent revoke.
interface ObjectUrlHandle { readonly url: string; revoke(): void;}SanitizeDownloadNameOptions
interface SanitizeDownloadNameOptions { readonly maxLength?: number; readonly fallback?: string;}SavePickerType
showSaveFilePicker accept types, as in the File System Access API.
interface SavePickerType { readonly description?: string; readonly accept: Readonly<Record<string, readonly string[]>>;}SaveWithPickerOptions
interface SaveWithPickerOptions { readonly suggestedName: string; readonly types?: readonly SavePickerType[]; readonly signal?: AbortSignal; readonly fallback?: boolean;}DownloadFailureReason
Why a download did not complete.
type DownloadFailureReason = 'cancelled' | 'aborted' | 'unsupported' | 'error';DownloadMethod
How the file was saved.
type DownloadMethod = 'anchor' | 'picker' | 'none';DownloadTask
A download task: any function returning a status (for example () => downloadUrl(url)).
type DownloadTask = () => DownloadStatus | Promise<DownloadStatus>;SaveSource
Data to save: a Blob, a stream, or a function producing one (called after the picker closes).
type SaveSource = Blob | ReadableStream<Uint8Array<ArrayBuffer>> | (() => Promise<Blob | ReadableStream<Uint8Array<ArrayBuffer>>>);