File Uploads Without a Visible File Input

A hands-on tour of classic file inputs, the File System Access API, folder picking, filtered image selection, progressive fallback, and runtime restrictions.

showOpenFilePicker Checking...
showDirectoryPicker Checking...
Secure context Checking...

1. Traditional input[type=file]

Code:

<label for="classicUpload" class="button-label">Choose images</label>
<input id="classicUpload" type="file" accept="image/*" multiple hidden>
<div id="classicOutput" class="output"></div>

<script>
const classicUpload = document.getElementById('classicUpload');
const classicOutput = document.getElementById('classicOutput');

classicUpload.addEventListener('change', function () {
    const files = Array.from(classicUpload.files);
    classicOutput.textContent = files.length
        ? files.map(file => `${file.name} - ${file.type || 'unknown type'}`).join('\n')
        : 'No files selected.';
});
</script>

Result:

2. Open the file picker from an ordinary button

Code:

<button id="openFileButton">Select a file</button>
<div id="openFileOutput" class="output"></div>

<script>
const openFileButton = document.getElementById('openFileButton');
const openFileOutput = document.getElementById('openFileOutput');

openFileButton.addEventListener('click', async function () {
    if (!('showOpenFilePicker' in window)) {
        openFileOutput.textContent = 'showOpenFilePicker is not supported in this browser.';
        return;
    }

    try {
        const handles = await window.showOpenFilePicker();
        const file = await handles[0].getFile();
        openFileOutput.textContent = `Selected: ${file.name}\nSize: ${file.size} bytes`;
    } catch (error) {
        openFileOutput.textContent = error.name === 'AbortError'
            ? 'Picker canceled.'
            : error.message;
    }
});
</script>

Result:

3. Select a folder with showDirectoryPicker()

Code:

<button id="directoryButton" class="teal">Select a folder</button>
<div id="directoryOutput" class="output"></div>

<script>
const directoryButton = document.getElementById('directoryButton');
const directoryOutput = document.getElementById('directoryOutput');

directoryButton.addEventListener('click', async function () {
    if (!('showDirectoryPicker' in window)) {
        directoryOutput.textContent = 'showDirectoryPicker is not supported in this browser.';
        return;
    }

    try {
        const directoryHandle = await window.showDirectoryPicker();
        const entries = [];

        for await (const [name, handle] of directoryHandle.entries()) {
            entries.push(`${handle.kind}: ${name}`);
            if (entries.length === 10) break;
        }

        directoryOutput.textContent = entries.length
            ? `Folder: ${directoryHandle.name}\n\n${entries.join('\n')}`
            : `Folder: ${directoryHandle.name}\n\nThis folder is empty.`;
    } catch (error) {
        directoryOutput.textContent = error.name === 'AbortError'
            ? 'Picker canceled.'
            : error.message;
    }
});
</script>

Result:

4. Restrict types and preview multiple images

Code:

<button id="imagePickerButton" class="violet">Select images</button>
<div id="imagePickerOutput" class="preview-grid"></div>

<script>
const imagePickerButton = document.getElementById('imagePickerButton');
const imagePickerOutput = document.getElementById('imagePickerOutput');

imagePickerButton.addEventListener('click', async function () {
    imagePickerOutput.replaceChildren();

    if (!('showOpenFilePicker' in window)) {
        imagePickerOutput.textContent = 'showOpenFilePicker is not supported in this browser.';
        return;
    }

    try {
        const arrFileHandle = await window.showOpenFilePicker({
            types: [{
                description: 'Images',
                accept: {
                    'image/*': ['.png', '.gif', '.jpeg', '.jpg', '.webp']
                }
            }],
            multiple: true
        });

        for (const fileHandle of arrFileHandle) {
            const fileData = await fileHandle.getFile();
            const buffer = await fileData.arrayBuffer();
            const src = URL.createObjectURL(new Blob([buffer], { type: fileData.type }));

            const card = document.createElement('figure');
            card.className = 'preview-card';
            card.innerHTML = `<img src="${src}" alt="${fileData.name}">
                <figcaption class="file-meta">${fileData.name}</figcaption>`;
            imagePickerOutput.append(card);
        }
    } catch (error) {
        imagePickerOutput.textContent = error.name === 'AbortError'
            ? 'Picker canceled.'
            : error.message;
    }
});
</script>

Result:

5. Use the new API with an input fallback

Code:

<button id="fallbackButton" class="amber">Pick images with fallback</button>
<div id="fallbackOutput" class="preview-grid"></div>

<script>
const fallbackButton = document.getElementById('fallbackButton');
const fallbackOutput = document.getElementById('fallbackOutput');

async function pickImages() {
    if ('showOpenFilePicker' in window) {
        const handles = await window.showOpenFilePicker({
            types: [{
                description: 'Images',
                accept: {
                    'image/*': ['.png', '.gif', '.jpeg', '.jpg', '.webp']
                }
            }],
            multiple: true
        });

        return Promise.all(handles.map(handle => handle.getFile()));
    }

    return new Promise(function (resolve, reject) {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = 'image/png,image/gif,image/jpeg,image/webp';
        input.multiple = true;
        input.hidden = true;

        input.addEventListener('change', function () {
            resolve(Array.from(input.files));
            input.remove();
        }, { once: true });

        input.addEventListener('cancel', function () {
            reject(new DOMException('Picker canceled.', 'AbortError'));
            input.remove();
        }, { once: true });

        document.body.append(input);
        input.click();
    });
}

fallbackButton.addEventListener('click', async function () {
    fallbackOutput.replaceChildren();

    try {
        const files = await pickImages();

        for (const file of files) {
            const src = URL.createObjectURL(file);
            const card = document.createElement('figure');
            card.className = 'preview-card';
            card.innerHTML = `<img src="${src}" alt="${file.name}">
                <figcaption class="file-meta">${file.name}</figcaption>`;
            fallbackOutput.append(card);
        }
    } catch (error) {
        fallbackOutput.textContent = error.name === 'AbortError'
            ? 'Picker canceled.'
            : error.message;
    }
});
</script>

Result:

6. Check the runtime restrictions

Code:

<button id="checkButton">Check this page</button>
<div id="checkOutput" class="facts"></div>

<script>
const checkButton = document.getElementById('checkButton');
const checkOutput = document.getElementById('checkOutput');

function renderChecks() {
    const checks = [
        ['Secure context', window.isSecureContext ? 'Yes' : 'No'],
        ['Inside iframe', window.self !== window.top ? 'Yes' : 'No'],
        ['File picker API', 'showOpenFilePicker' in window ? 'Available' : 'Unavailable']
    ];

    checkOutput.innerHTML = checks.map(function ([label, value]) {
        const good = value === 'Yes' || value === 'Available';
        return `<div class="fact">
            <span>${label}</span>
            <strong class="${good ? 'ok' : 'bad'}">${value}</strong>
        </div>`;
    }).join('');
}

checkButton.addEventListener('click', renderChecks);
renderChecks();
</script>

Result:

', index); const js = end === -1 ? source.slice(index) : source.slice(index, end); html += highlightJs(js); if (end === -1) break; html += highlightTag(''); index = end + ''.length; continue; } if (source[index] === '<') { const end = source.indexOf('>', index + 1); if (end !== -1) { html += highlightTag(source.slice(index, end + 1)); index = end + 1; continue; } } html += escapeHtml(source[index]); index += 1; } return html; } document.querySelectorAll('pre code').forEach(function (block) { const source = block.textContent; block.innerHTML = block.dataset.language === 'html' ? highlightHtml(source) : highlightJs(source); }); const topFileSupport = document.getElementById('topFileSupport'); const topDirectorySupport = document.getElementById('topDirectorySupport'); const topSecureSupport = document.getElementById('topSecureSupport'); topFileSupport.textContent = 'showOpenFilePicker' in window ? 'Available' : 'Unavailable'; topDirectorySupport.textContent = 'showDirectoryPicker' in window ? 'Available' : 'Unavailable'; topSecureSupport.textContent = window.isSecureContext ? 'Yes' : 'No'; const classicUpload = document.getElementById('classicUpload'); const classicOutput = document.getElementById('classicOutput'); classicUpload.addEventListener('change', function () { const files = Array.from(classicUpload.files); classicOutput.textContent = files.length ? files.map(file => `${file.name} - ${file.type || 'unknown type'}`).join('\n') : 'No files selected.'; }); const openFileButton = document.getElementById('openFileButton'); const openFileOutput = document.getElementById('openFileOutput'); openFileButton.addEventListener('click', async function () { if (!('showOpenFilePicker' in window)) { openFileOutput.textContent = 'showOpenFilePicker is not supported in this browser.'; return; } try { const handles = await window.showOpenFilePicker(); const file = await handles[0].getFile(); openFileOutput.textContent = `Selected: ${file.name}\nSize: ${file.size} bytes`; } catch (error) { openFileOutput.textContent = error.name === 'AbortError' ? 'Picker canceled.' : error.message; } }); const directoryButton = document.getElementById('directoryButton'); const directoryOutput = document.getElementById('directoryOutput'); directoryButton.addEventListener('click', async function () { if (!('showDirectoryPicker' in window)) { directoryOutput.textContent = 'showDirectoryPicker is not supported in this browser.'; return; } try { const directoryHandle = await window.showDirectoryPicker(); const entries = []; for await (const [name, handle] of directoryHandle.entries()) { entries.push(`${handle.kind}: ${name}`); if (entries.length === 10) break; } directoryOutput.textContent = entries.length ? `Folder: ${directoryHandle.name}\n\n${entries.join('\n')}` : `Folder: ${directoryHandle.name}\n\nThis folder is empty.`; } catch (error) { directoryOutput.textContent = error.name === 'AbortError' ? 'Picker canceled.' : error.message; } }); const imagePickerButton = document.getElementById('imagePickerButton'); const imagePickerOutput = document.getElementById('imagePickerOutput'); imagePickerButton.addEventListener('click', async function () { imagePickerOutput.replaceChildren(); if (!('showOpenFilePicker' in window)) { imagePickerOutput.textContent = 'showOpenFilePicker is not supported in this browser.'; return; } try { const arrFileHandle = await window.showOpenFilePicker({ types: [{ description: 'Images', accept: { 'image/*': ['.png', '.gif', '.jpeg', '.jpg', '.webp'] } }], multiple: true }); for (const fileHandle of arrFileHandle) { const fileData = await fileHandle.getFile(); const buffer = await fileData.arrayBuffer(); const src = URL.createObjectURL(new Blob([buffer], { type: fileData.type })); const card = document.createElement('figure'); card.className = 'preview-card'; card.innerHTML = `${fileData.name}
${fileData.name}
`; imagePickerOutput.append(card); } } catch (error) { imagePickerOutput.textContent = error.name === 'AbortError' ? 'Picker canceled.' : error.message; } }); const fallbackButton = document.getElementById('fallbackButton'); const fallbackOutput = document.getElementById('fallbackOutput'); async function pickImages() { if ('showOpenFilePicker' in window) { const handles = await window.showOpenFilePicker({ types: [{ description: 'Images', accept: { 'image/*': ['.png', '.gif', '.jpeg', '.jpg', '.webp'] } }], multiple: true }); return Promise.all(handles.map(handle => handle.getFile())); } return new Promise(function (resolve, reject) { const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/png,image/gif,image/jpeg,image/webp'; input.multiple = true; input.hidden = true; input.addEventListener('change', function () { resolve(Array.from(input.files)); input.remove(); }, { once: true }); input.addEventListener('cancel', function () { reject(new DOMException('Picker canceled.', 'AbortError')); input.remove(); }, { once: true }); document.body.append(input); input.click(); }); } fallbackButton.addEventListener('click', async function () { fallbackOutput.replaceChildren(); try { const files = await pickImages(); for (const file of files) { const src = URL.createObjectURL(file); const card = document.createElement('figure'); card.className = 'preview-card'; card.innerHTML = `${file.name}
${file.name}
`; fallbackOutput.append(card); } } catch (error) { fallbackOutput.textContent = error.name === 'AbortError' ? 'Picker canceled.' : error.message; } }); const checkButton = document.getElementById('checkButton'); const checkOutput = document.getElementById('checkOutput'); function renderChecks() { const checks = [ ['Secure context', window.isSecureContext ? 'Yes' : 'No'], ['Inside iframe', window.self !== window.top ? 'Yes' : 'No'], ['File picker API', 'showOpenFilePicker' in window ? 'Available' : 'Unavailable'] ]; checkOutput.innerHTML = checks.map(function ([label, value]) { const good = value === 'Yes' || value === 'Available'; return `
${label} ${value}
`; }).join(''); } checkButton.addEventListener('click', renderChecks); renderChecks();