Checking File Support with navigator.canShare
This guide is part of Sharing Files and Media Payloads, which itself is part of Native Device Integration Patterns.
typeof navigator.share === 'function' tells you that sharing exists. It tells you nothing about whether this browser will accept these files. File support is a separate capability, evaluated per payload, and the only way to ask about it is navigator.canShare({ files }) — a method that is itself missing from some browsers that support sharing.
Feature Detection Gate
Three conditions, in order, and none of them optional.
// can-share-files.js
export function canShareFiles(files) {
if (window.isSecureContext !== true) return false;
if (typeof navigator.share !== 'function') return false;
if (typeof navigator.canShare !== 'function') return false;
if (!Array.isArray(files) || files.length === 0) return false;
if (!files.every((f) => f instanceof File)) return false;
try {
return navigator.canShare({ files });
} catch {
return false; // malformed payload — treat as unsupported
}
}
The instanceof File check is worth its line. canShare throws a TypeError rather than returning false when the array holds Blob objects, and a thrown error inside a render path takes the whole component with it.
Step 1 — Validate the Payload You Will Actually Send
The answer depends on the files. A probe file proves nothing about the real one.
// share-media.js
import { canShareFiles } from './can-share-files.js';
export async function shareMedia(files, meta = {}) {
if (!canShareFiles(files)) {
return { outcome: 'fallback', reason: 'files-unsupported' };
}
const full = { ...meta, files };
const payload = navigator.canShare(full) ? full : { files };
try {
await navigator.share(payload);
return { outcome: 'shared' };
} catch (err) {
if (err.name === 'AbortError') return { outcome: 'dismissed' };
return { outcome: 'fallback', reason: err.name };
}
}
Validating twice — once for files alone, once for files plus metadata — is deliberate. Some browsers accept { files } and reject { title, text, files }, and silently degrading to the narrower payload keeps the share working instead of dropping the user into a fallback they did not need.
Step 2 — Surface the Answer in the UI Before the Tap
A validation result that arrives after the click produces the worst experience: a Share button that turns into something else. Decide the label from the same function that decides the route.
// share-affordance.js
import { canShareFiles } from './can-share-files.js';
export function shareAffordance(files) {
if (canShareFiles(files)) {
return { label: 'Share', hint: null, action: 'native-files' };
}
if (typeof navigator.share === 'function') {
return { label: 'Share link', hint: 'Attachments are not supported here', action: 'native-link' };
}
return { label: 'Download', hint: null, action: 'download' };
}
Because canShareFiles is cheap and synchronous, this can run on every render without a measurable cost — no caching, no stale answers.
Step 3 — Probe Capability Without Guessing
When you genuinely need to know a platform’s ceiling — to decide whether to offer a “share as image” feature at all — probe with a small, representative file of the same type rather than with the eventual multi-megabyte asset.
// capability-probe.js
export function supportsImageSharing() {
if (typeof navigator.canShare !== 'function') return false;
const probe = new File([new Uint8Array(8)], 'probe.png', { type: 'image/png' });
try {
return navigator.canShare({ files: [probe] });
} catch {
return false;
}
}
Treat the result as a hint for feature discovery only. It answers “can this browser share PNGs at all?”, never “will this particular 24 MB PNG succeed?” — a distinction that matters because size limits live at the target, as working around share sheet file size limits covers.
What canShare Actually Evaluates
It helps to know what the method is and is not looking at, because the boundary explains most surprising results.
canShare inspects the shape and types of the payload. It asks whether this browser, on this platform, is willing to hand a payload with these members to the operating system: are files supported at all, are these MIME types on the accepted list, is this combination of title, text, url and files one the implementation will forward? It answers synchronously from a static table, which is why it costs nothing to call repeatedly.
It does not inspect the bytes. File size is invisible to it, file contents are never read, and — most importantly — it has no knowledge of which application the user will pick from the sheet. That choice happens after your code has finished running, and the chosen target enforces its own rules about size, type and count. A true followed by a DataError is therefore not a contradiction; it is the two different authorities disagreeing.
Nor does it evaluate user activation. A payload can validate perfectly and the subsequent share() still fail with NotAllowedError because the gesture window closed while you were preparing files. Validation and activation are independent concerns, and only one of them is testable in advance.
// what-canshare-knows.js
export function explainRejection(files) {
if (typeof navigator.canShare !== 'function') return 'browser has no canShare — cannot validate';
if (!files.every((f) => f instanceof File)) return 'payload contains a Blob rather than a File';
if (!navigator.canShare({ files })) return 'this browser will not forward these types';
const bytes = files.reduce((total, f) => total + f.size, 0);
if (bytes > 20_000_000) return 'validated, but large enough that a target may refuse it';
return 'validated — remaining risk is the chosen target';
}
Framing the outcome this way keeps error reporting accurate: the difference between “this browser cannot” and “that app would not” is the difference between hiding a feature and offering a retry.
Failure Modes and Recovery
| Symptom | Cause | Minimal fix |
|---|---|---|
TypeError: canShare is not a function |
Called unguarded on older Safari | typeof check before calling |
canShare throws instead of returning |
files contains Blob, not File |
Wrap each blob in a File with a name and type |
canShare({files: []}) returns false everywhere |
Empty array carries no type information | Validate a representative or the real file |
| Support cached at startup, share fails later | Answer is payload-specific | Re-validate immediately before each share |
| Share succeeds without the caption | Target dropped the metadata | Validate {...meta, files} and degrade to {files} |
canShare true, share() raises DataError |
Target applies its own limits | Catch DataError and route to download |
Browser and Platform Caveat
navigator.canShare is present alongside share in current Chrome, Edge and Safari, but the pairing is not guaranteed historically — earlier Safari builds exposed share without it, which is why the typeof guard is not defensive noise. The method is synchronous and cheap, so calling it per render or per attempt costs nothing measurable. What it cannot see is the receiving application: it reflects the browser’s own view of the payload, so a true result followed by a DataError from a target that refuses a 40 MB video is expected behaviour, not a contradiction. On desktop Safari the set of accepted types is noticeably narrower than on iOS, so the same code legitimately routes macOS users to a download while iPhone users get the sheet.
FAQ
Does canShare({files: []}) tell me whether file sharing works?
No. An empty array carries no type information, and browsers commonly return false for it regardless of their real capability. Build at least one representative File and validate that, or validate the actual payload you are about to send.
Can I call canShare once at startup and reuse the answer?
No. The result is a function of the specific payload, so a true for a small PNG says nothing about a 30 MB video or a HEIC image. Call it immediately before each share, with the files you are actually sending.
Why does canShare throw instead of returning false?
It throws a TypeError when the argument is malformed — most often because files contains Blob objects rather than File objects. That is a programming error rather than a support signal, so wrap the call in a try-catch and treat a throw as unsupported.