Resolving DataError When Sharing Files
This guide is part of File System Access API Read and Write Workflows, which itself is part of Native Device Integration Patterns.
Sharing files with navigator.share({ files }) fails in two distinct ways, and the fix depends on which one you hit. A TypeError is thrown synchronously when the payload is structurally invalid — a bare Blob with no name, or an empty array. A DataError is thrown when the payload is well-formed but the target platform refuses the content, typically an unsupported MIME type or an oversized file. This guide walks through pre-validating every file payload with navigator.canShare(), constructing proper File objects, enforcing size limits, and re-reading bytes that have gone stale after a service-worker restart.
Feature Detection Gate
File sharing needs navigator.canShare in addition to navigator.share. Detect all three prerequisites — including the secure context — before you spend effort constructing a payload:
export function canShareFiles() {
return (
window.isSecureContext === true &&
typeof navigator.share === 'function' &&
typeof navigator.canShare === 'function'
);
}
navigator.canShare shipped later and separately from navigator.share, so a browser can support text sharing but not files. If canShareFiles() is false, skip the file path entirely and route to a download.
Solution Walkthrough
Step 1 — Turn Bytes into a Named File Object
The most common TypeError comes from passing a Blob straight into the files array. A Blob has a type but no name, and several platforms need the filename to label and route the attachment. Wrap the bytes in a File with an explicit name and MIME type. If you are pulling payloads out of storage — for example ArrayBuffers persisted in IndexedDB — reconstruct the File at share time:
export function toShareableFile(bytes, name, type) {
// bytes: ArrayBuffer | Blob | Uint8Array
const blob = bytes instanceof Blob ? bytes : new Blob([bytes], { type });
// Always produce a File — never share a bare Blob.
return new File([blob], name, { type: type || blob.type || 'application/octet-stream' });
}
Give the file a real extension that matches its MIME type (report.pdf with application/pdf, not report alone). A mismatched or missing extension is a frequent cause of a target app rejecting the file even when canShare passed.
Step 2 — Enforce a Size Ceiling Before Validating
Oversized payloads can surface as a DataError at share() time, or worse, as a silent failure on memory-constrained devices. Check the byte length before you go any further and reject early with a clear reason:
const MAX_SHARE_BYTES = 25 * 1024 * 1024; // conservative cross-platform ceiling
export function withinSizeLimit(files) {
const total = files.reduce((sum, f) => sum + f.size, 0);
return total > 0 && total <= MAX_SHARE_BYTES;
}
Platform ceilings vary and are not formally specified — iOS commonly tolerates roughly 10–50 MB per file depending on the target app, and Chromium is similar. A single conservative limit keeps behaviour predictable across devices. For genuinely large media, prefer a link to a hosted file, as covered in handling large file uploads with the File System Access API.
Step 3 — Pre-flight with canShare, Then Share
Now validate the fully-formed payload with navigator.canShare({ files }). This is the check that prevents DataError: if the platform cannot accept these files, canShare returns false and you must not call share(). Route failures to the download fallback:
export async function shareFiles(files, meta = {}) {
if (!canShareFiles()) return downloadFallback(files);
if (!withinSizeLimit(files)) return downloadFallback(files);
const payload = { files, title: meta.title, text: meta.text };
// canShare is the gate that prevents DataError at share() time.
if (!navigator.canShare(payload)) {
return downloadFallback(files);
}
try {
await navigator.share(payload);
return { status: 'shared' };
} catch (err) {
if (err.name === 'AbortError') return { status: 'cancelled' };
// DataError/NotAllowedError/etc: recover with a download.
return downloadFallback(files);
}
}
function downloadFallback(files) {
const file = files[0];
const url = URL.createObjectURL(file);
try {
const a = document.createElement('a');
a.href = url;
a.download = file.name;
a.click();
} finally {
URL.revokeObjectURL(url);
}
return { status: 'downloaded' };
}
Note the finally that revokes the object URL — omitting it leaks memory on every fallback. And note that AbortError is treated as a normal cancel, not a failure, so it does not trigger a download the user did not ask for.
Step 4 — Re-read Stale Files After a Worker Restart
If your share flow runs inside or alongside a service worker — for instance, replaying a queued share — a File you captured earlier may be backed by bytes that no longer resolve after the worker is evicted and restarted. Re-materialize the file from durable storage at the moment of sharing rather than holding a long-lived reference:
export async function materializeFromStore(record) {
// record.bytes is an ArrayBuffer persisted in IndexedDB — always fresh.
if (!(record.bytes instanceof ArrayBuffer) || record.bytes.byteLength === 0) {
throw new Error('stale or empty payload; re-fetch before sharing');
}
return toShareableFile(record.bytes, record.name, record.type);
}
Storing the raw ArrayBuffer rather than a File or an object URL sidesteps the staleness problem entirely, because you rebuild a valid File on demand.
Failure Modes and Recovery
| Error / Symptom | Root Cause | Minimal Fix |
|---|---|---|
TypeError thrown synchronously |
Bare Blob (no name) or empty files array |
Wrap bytes in new File([blob], name, { type }) |
DataError at share() |
MIME type unsupported on the target platform | Gate with navigator.canShare({ files }); fall back to download |
DataError on large media |
Payload exceeds the platform ceiling | Enforce a size limit; share a hosted link instead |
| Unnamed / wrong-typed attachment | Missing extension or type/extension mismatch | Give the File a correct name and matching MIME type |
| Share fails after SW restart | File/handle references stale bytes |
Store ArrayBuffer; rebuild the File at share time |
| Memory grows after fallbacks | URL.createObjectURL never revoked |
Revoke in a finally block after .click() |
Browser and Platform Caveat
File sharing via navigator.share({ files }) is supported on Chromium (Android and desktop), Safari on iOS and macOS, and Edge, but the set of accepted MIME types differs by platform and by the specific target app the user picks. navigator.canShare({ files }) is the only reliable way to ask “will this particular payload be accepted here” before invoking, and it must be feature-detected because some browsers expose navigator.share without it. Because the accepted-type list and size ceiling are not standardized, always keep the download fallback wired — it is the guaranteed path when canShare returns false or a DataError slips through on an edge-case target.
FAQ
Why does navigator.share() throw DataError with a valid-looking file?
DataError usually means the target platform cannot accept that file — most often an unsupported MIME type, or a payload that navigator.canShare({ files }) would have rejected. Call canShare first; if it returns false, do not call share() and route to a download fallback instead of pushing an unshareable payload.
What is the difference between DataError and TypeError when sharing files?
TypeError is thrown synchronously when the payload is structurally wrong — a bare Blob without a name, or an empty files array. DataError is thrown when the payload is well-formed but the platform refuses the content, such as an unsupported type. Fix TypeError by constructing proper File objects; fix DataError by validating with canShare and offering a fallback.
Do I have to pass File objects, or will a Blob work?
Pass File objects. A Blob has no name, and several platforms need a filename to route and label the shared file, so a bare Blob can produce a TypeError or an unnamed attachment. Wrap your Blob with new File([blob], 'name.ext', { type }) before adding it to the files array.
Why does a file share fail after my service worker restarts?
A File backed by a handle or object URL can become stale when the service worker is evicted and restarted, leaving you sharing a reference to bytes that no longer resolve. Re-read the underlying bytes into a fresh File at share time — storing the raw ArrayBuffer in IndexedDB and rebuilding the File on demand avoids the problem entirely.