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: