File and Clipboard APIs

1. File API for File Object Manipulation

Property Type Description Read-Only
name string File name with extension (e.g., "photo.jpg"). Does not include path. Yes
size number File size in bytes. Yes
type string MIME type (e.g., "image/jpeg", "text/plain"). Empty string if unknown. Yes
lastModified number Last modified timestamp in milliseconds since epoch. Yes
lastModifiedDate Date Last modified date as Date object. Deprecated - use lastModified instead. Yes
Method Returns Description
slice(start, end, contentType) Blob Returns portion of file as new Blob. Parameters optional. Useful for chunked uploads.
text() Promise<string> Reads file content as text. Modern alternative to FileReader.
arrayBuffer() Promise<ArrayBuffer> Reads file content as ArrayBuffer. Modern alternative to FileReader.
stream() ReadableStream Returns ReadableStream for streaming file content.

Example: File input and validation

// HTML: <input type="file" id="fileInput" multiple accept="image/*">

const fileInput = document.getElementById("fileInput");

fileInput.addEventListener("change", (event) => {
  const files = event.target.files; // FileList object
  
  console.log(`${files.length} file(s) selected`);
  
  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    
    console.log("File info:");
    console.log(`  Name: ${file.name}`);
    console.log(`  Size: ${formatBytes(file.size)}`);
    console.log(`  Type: ${file.type}`);
    console.log(`  Last modified: ${new Date(file.lastModified)}`);
    
    // Validate file
    if (!validateFile(file)) {
      console.error("Invalid file:", file.name);
      continue;
    }
    
    // Process file
    processFile(file);
  }
});

// Validate file
function validateFile(file) {
  // Check file type
  const validTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
  if (!validTypes.includes(file.type)) {
    alert(`Invalid file type: ${file.type}`);
    return false;
  }
  
  // Check file size (max 5MB)
  const maxSize = 5 * 1024 * 1024;
  if (file.size > maxSize) {
    alert(`File too large: ${formatBytes(file.size)} (max ${formatBytes(maxSize)})`);
    return false;
  }
  
  // Check file name
  if (file.name.length > 255) {
    alert("File name too long");
    return false;
  }
  
  return true;
}

// Format bytes
function formatBytes(bytes) {
  if (bytes === 0) return "0 Bytes";
  const k = 1024;
  const sizes = ["Bytes", "KB", "MB", "GB"];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
}

Example: Modern file reading with promises

// Read file as text
async function readFileAsText(file) {
  try {
    const text = await file.text();
    console.log("File content:", text);
    return text;
  } catch (error) {
    console.error("Error reading file:", error);
  }
}

// Read file as ArrayBuffer
async function readFileAsArrayBuffer(file) {
  try {
    const buffer = await file.arrayBuffer();
    console.log("Buffer size:", buffer.byteLength);
    return buffer;
  } catch (error) {
    console.error("Error reading file:", error);
  }
}

// Read file as data URL for preview
async function readFileAsDataURL(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = () => reject(reader.error);
    reader.readAsDataURL(file);
  });
}

// Usage
fileInput.addEventListener("change", async (event) => {
  const file = event.target.files[0];
  
  if (file.type.startsWith("text/")) {
    const text = await file.text();
    console.log(text);
  } else if (file.type.startsWith("image/")) {
    const dataURL = await readFileAsDataURL(file);
    const img = document.createElement("img");
    img.src = dataURL;
    document.body.appendChild(img);
  }
});

// Slice file for chunked upload
function uploadFileInChunks(file, chunkSize = 1024 * 1024) {
  const chunks = Math.ceil(file.size / chunkSize);
  
  for (let i = 0; i < chunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.slice(start, end);
    
    console.log(`Chunk ${i + 1}/${chunks}: ${chunk.size} bytes`);
    uploadChunk(chunk, i);
  }
}

async function uploadChunk(chunk, index) {
  const formData = new FormData();
  formData.append("chunk", chunk);
  formData.append("index", index);
  
  await fetch("/upload-chunk", {
    "method": "POST",
    "body": formData
  });
}
Note: File objects are Blob subclasses with additional name and lastModified properties. Files from <input type="file"> are read-only. Use accept attribute to filter file types in picker. Modern methods (text(), arrayBuffer()) are Promise-based and simpler than FileReader.

2. FileReader API for File Content Reading

Method Description Result Format
readAsText(blob, encoding) Reads file as text string. Optional encoding (default UTF-8). string
readAsDataURL(blob) Reads file as data URL (base64-encoded). Good for images. data:image/jpeg;base64,...
readAsArrayBuffer(blob) Reads file as ArrayBuffer. Good for binary data. ArrayBuffer
readAsBinaryString(blob) Reads file as binary string. Deprecated - use readAsArrayBuffer instead. string
abort() Aborts ongoing read operation. Triggers abort event. -
Property Type Description
result string | ArrayBuffer File content after successful read. null before load event.
error DOMException Error object if read failed. null otherwise.
readyState number 0=EMPTY, 1=LOADING, 2=DONE
Event When Fired
loadstart Read operation started
progress During read (for progress tracking). event.loaded and event.total available.
load Read completed successfully. Result available in reader.result.
loadend Read completed (success or failure)
error Read failed. Error available in reader.error.
abort Read aborted with abort() method

Example: FileReader with event handlers

function readFile(file) {
  const reader = new FileReader();
  
  // Progress tracking
  reader.addEventListener("loadstart", () => {
    console.log("Reading started");
  });
  
  reader.addEventListener("progress", (event) => {
    if (event.lengthComputable) {
      const percent = (event.loaded / event.total) * 100;
      console.log(`Progress: ${percent.toFixed(2)}%`);
      updateProgressBar(percent);
    }
  });
  
  reader.addEventListener("load", () => {
    console.log("Reading completed");
    console.log("Result:", reader.result);
    
    // Display image
    if (file.type.startsWith("image/")) {
      const img = document.createElement("img");
      img.src = reader.result;
      document.body.appendChild(img);
    }
  });
  
  reader.addEventListener("error", () => {
    console.error("Read error:", reader.error);
  });
  
  reader.addEventListener("abort", () => {
    console.log("Read aborted");
  });
  
  reader.addEventListener("loadend", () => {
    console.log("Read ended");
  });
  
  // Start reading
  if (file.type.startsWith("image/")) {
    reader.readAsDataURL(file);
  } else if (file.type.startsWith("text/")) {
    reader.readAsText(file);
  } else {
    reader.readAsArrayBuffer(file);
  }
}

// Image preview
function previewImage(file) {
  const reader = new FileReader();
  
  reader.onload = (event) => {
    const img = new Image();
    img.src = event.target.result;
    img.onload = () => {
      console.log(`Image dimensions: ${img.width}x${img.height}`);
    };
    document.getElementById("preview").appendChild(img);
  };
  
  reader.readAsDataURL(file);
}

// Read CSV file
function readCSV(file) {
  const reader = new FileReader();
  
  reader.onload = (event) => {
    const text = event.target.result;
    const rows = text.split("\n").map((row) => row.split(","));
    console.log("CSV data:", rows);
  };
  
  reader.readAsText(file);
}

// Read binary file
function readBinary(file) {
  const reader = new FileReader();
  
  reader.onload = (event) => {
    const buffer = event.target.result;
    const view = new Uint8Array(buffer);
    console.log("First 10 bytes:", Array.from(view.slice(0, 10)));
  };
  
  reader.readAsArrayBuffer(file);
}
Note: FileReader is event-based and asynchronous. For modern code, prefer file.text() or file.arrayBuffer() which return Promises. readAsDataURL creates large base64 strings - consider object URLs for better performance. Each FileReader instance can only read one file at a time.

3. Clipboard API for Copy/Paste Operations

Method Description Browser Support
navigator.clipboard.writeText(text) Writes text to clipboard. Returns Promise. Requires user gesture or permission. Modern Browsers
navigator.clipboard.readText() Reads text from clipboard. Returns Promise<string>. Requires permission. Modern Browsers
navigator.clipboard.write(items) Writes ClipboardItem array to clipboard. Supports images, rich content. Modern Browsers
navigator.clipboard.read() Reads ClipboardItem array from clipboard. Returns Promise<ClipboardItem[]>. Modern Browsers
Legacy Method Description Status
document.execCommand("copy") Copies selected text to clipboard. Deprecated but widely supported. DEPRECATED
document.execCommand("cut") Cuts selected text to clipboard. Deprecated but widely supported. DEPRECATED
document.execCommand("paste") Pastes from clipboard. Deprecated and security restricted. DEPRECATED

Example: Modern clipboard API

// Copy text to clipboard
async function copyToClipboard(text) {
  try {
    await navigator.clipboard.writeText(text);
    console.log("Text copied to clipboard");
    return true;
  } catch (error) {
    console.error("Failed to copy:", error);
    return false;
  }
}

// Read text from clipboard
async function readFromClipboard() {
  try {
    const text = await navigator.clipboard.readText();
    console.log("Clipboard content:", text);
    return text;
  } catch (error) {
    console.error("Failed to read clipboard:", error);
    return null;
  }
}

// Copy button handler
document.querySelector(".copy-btn").addEventListener("click", async () => {
  const text = document.querySelector(".codeBlock").textContent;
  const success = await copyToClipboard(text);
  
  if (success) {
    // Show feedback
    showToast("Copied to clipboard!");
  }
});

// Paste handler
document.querySelector(".paste-btn").addEventListener("click", async () => {
  const text = await readFromClipboard();
  if (text) {
    document.querySelector("textarea").value = text;
  }
});

// Copy rich content (HTML, images)
async function copyRichContent() {
  const blob = new Blob(
    ["<h1>Hello</h1><p>Rich content</p>"],
    { "type": "text/html" }
  );
  
  const item = new ClipboardItem({
    "text/html": blob
  });
  
  try {
    await navigator.clipboard.write([item]);
    console.log("Rich content copied");
  } catch (error) {
    console.error("Failed to copy rich content:", error);
  }
}

// Copy image to clipboard
async function copyImageToClipboard(imageUrl) {
  try {
    const response = await fetch(imageUrl);
    const blob = await response.blob();
    
    const item = new ClipboardItem({
      [blob.type]: blob
    });
    
    await navigator.clipboard.write([item]);
    console.log("Image copied to clipboard");
  } catch (error) {
    console.error("Failed to copy image:", error);
  }
}

// Read clipboard with multiple formats
async function readClipboardData() {
  try {
    const items = await navigator.clipboard.read();
    
    for (const item of items) {
      console.log("Clipboard item types:", item.types);
      
      for (const type of item.types) {
        const blob = await item.getType(type);
        
        if (type.startsWith("text/")) {
          const text = await blob.text();
          console.log(`${type}:`, text);
        } else if (type.startsWith("image/")) {
          const url = URL.createObjectURL(blob);
          console.log(`${type}:`, url);
          
          const img = document.createElement("img");
          img.src = url;
          document.body.appendChild(img);
        }
      }
    }
  } catch (error) {
    console.error("Failed to read clipboard:", error);
  }
}

Example: Legacy execCommand fallback

// Copy with fallback
async function copyText(text) {
  // Try modern API
  if (navigator.clipboard && navigator.clipboard.writeText) {
    try {
      await navigator.clipboard.writeText(text);
      return true;
    } catch (error) {
      console.warn("Clipboard API failed, trying fallback");
    }
  }
  
  // Fallback to execCommand
  const textarea = document.createElement("textarea");
  textarea.value = text;
  textarea.style.position = "fixed";
  textarea.style.opacity = "0";
  document.body.appendChild(textarea);
  textarea.select();
  
  let success = false;
  try {
    success = document.execCommand("copy");
  } catch (error) {
    console.error("execCommand failed:", error);
  }
  
  document.body.removeChild(textarea);
  return success;
}

// Copy from element
function copyElementText(element) {
  const text = element.textContent || element.innerText;
  return copyText(text);
}

// Select and copy
function selectAndCopy(element) {
  const range = document.createRange();
  range.selectNodeContents(element);
  
  const selection = window.getSelection();
  selection.removeAllRanges();
  selection.addRange(range);
  
  const success = document.execCommand("copy");
  selection.removeAllRanges();
  
  return success;
}
Note: Clipboard API requires HTTPS (or localhost). writeText() requires user gesture on some browsers. readText() requires clipboard permission. ClipboardItem supports multiple MIME types. Always provide fallback for older browsers.
Warning: Reading clipboard requires user permission - prompt may appear. Don't spam clipboard writes - annoying UX. execCommand is deprecated but still needed for fallback. Never auto-read clipboard on page load - privacy violation.

4. Drag and Drop API with DataTransfer

Event Target When Fired Cancelable
dragstart Source element User starts dragging element Yes
drag Source element While dragging (fires continuously) Yes
dragend Source element Drag operation ends (drop or cancel) No
dragenter Drop target Dragged element enters drop target Yes
dragover Drop target Dragged element over drop target (fires continuously) Yes
dragleave Drop target Dragged element leaves drop target No
drop Drop target Element dropped on target. Must preventDefault() on dragover. Yes
DataTransfer Property Type Description
dropEffect string Visual feedback: "none", "copy", "move", "link". Set in dragover.
effectAllowed string Allowed operations: "none", "copy", "move", "link", "copyMove", "all", etc. Set in dragstart.
files FileList Files being dragged (from file system). Available in drop event.
items DataTransferItemList List of drag data items. More powerful than files.
types string[] Array of data format types available.
DataTransfer Method Description
setData(format, data) Sets drag data. Common formats: "text/plain", "text/html", "text/uri-list".
getData(format) Gets drag data. Only accessible in drop event.
clearData(format) Clears drag data. Optional format parameter.
setDragImage(element, x, y) Sets custom drag preview image. x, y are hotspot offsets.

Example: Basic drag and drop

// Make element draggable
const draggable = document.querySelector(".draggable");
draggable.draggable = true;

// Drag start
draggable.addEventListener("dragstart", (event) => {
  console.log("Drag started");
  
  // Set data
  event.dataTransfer.setData("text/plain", event.target.id);
  event.dataTransfer.setData("text/html", event.target.outerHTML);
  
  // Set effect
  event.dataTransfer.effectAllowed = "move";
  
  // Visual feedback
  event.target.style.opacity = "0.5";
});

// Drag end
draggable.addEventListener("dragend", (event) => {
  console.log("Drag ended");
  event.target.style.opacity = "1";
});

// Drop target
const dropZone = document.querySelector(".drop-zone");

// Prevent default to allow drop
dropZone.addEventListener("dragover", (event) => {
  event.preventDefault();
  event.dataTransfer.dropEffect = "move";
});

// Visual feedback
dropZone.addEventListener("dragenter", (event) => {
  event.preventDefault();
  dropZone.classList.add("drag-over");
});

dropZone.addEventListener("dragleave", () => {
  dropZone.classList.remove("drag-over");
});

// Handle drop
dropZone.addEventListener("drop", (event) => {
  event.preventDefault();
  dropZone.classList.remove("drag-over");
  
  // Get data
  const id = event.dataTransfer.getData("text/plain");
  console.log("Dropped element ID:", id);
  
  // Move element
  const element = document.getElementById(id);
  dropZone.appendChild(element);
});

Example: File drag and drop

const dropZone = document.getElementById("file-drop-zone");

// Prevent default browser behavior
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
  dropZone.addEventListener(eventName, (e) => {
    e.preventDefault();
    e.stopPropagation();
  });
});

// Visual feedback
["dragenter", "dragover"].forEach((eventName) => {
  dropZone.addEventListener(eventName, () => {
    dropZone.classList.add("highlight");
  });
});

["dragleave", "drop"].forEach((eventName) => {
  dropZone.addEventListener(eventName, () => {
    dropZone.classList.remove("highlight");
  });
});

// Handle dropped files
dropZone.addEventListener("drop", (event) => {
  const files = event.dataTransfer.files;
  console.log(`${files.length} file(s) dropped`);
  
  // Process files
  Array.from(files).forEach((file) => {
    console.log("File:", file.name, file.type, file.size);
    handleFile(file);
  });
  
  // Or use DataTransferItemList for more control
  const items = event.dataTransfer.items;
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    
    if (item.kind === "file") {
      const file = item.getAsFile();
      console.log("File from item:", file.name);
    }
  }
});

// Validate dropped files
function validateDrop(event) {
  const items = event.dataTransfer.items;
  
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    
    // Check if file
    if (item.kind !== "file") {
      return false;
    }
    
    // Check file type
    if (!item.type.startsWith("image/")) {
      alert("Only images allowed");
      return false;
    }
  }
  
  return true;
}

// Preview dropped images
async function handleFile(file) {
  if (!file.type.startsWith("image/")) return;
  
  const img = document.createElement("img");
  img.file = file;
  
  const reader = new FileReader();
  reader.onload = (e) => {
    img.src = e.target.result;
    img.style.maxWidth = "200px";
    dropZone.appendChild(img);
  };
  reader.readAsDataURL(file);
}
Note: Must call preventDefault() on dragover event to allow drop. getData() only works in drop event (security). Set draggable="true" attribute on elements to make draggable. Use event.dataTransfer.files for dropped files.
Warning: dragover fires very frequently - throttle expensive operations. Some browsers restrict file access from drag/drop for security. Always validate dropped files before processing. Mobile browsers have limited drag/drop support.

5. File System Access API for Local Files

Method Description Browser Support
window.showOpenFilePicker(options) Shows file picker. Returns Promise<FileSystemFileHandle[]>. Requires user gesture. Modern Browsers
window.showSaveFilePicker(options) Shows save dialog. Returns Promise<FileSystemFileHandle>. Requires user gesture. Modern Browsers
window.showDirectoryPicker(options) Shows directory picker. Returns Promise<FileSystemDirectoryHandle>. Requires user gesture. Modern Browsers
FileSystemFileHandle Method Description
getFile() Returns Promise<File> with file contents.
createWritable() Returns Promise<FileSystemWritableFileStream> for writing.
queryPermission(descriptor) Checks permission status. Returns "granted", "denied", or "prompt".
requestPermission(descriptor) Requests permission. Returns "granted" or "denied".

Example: Open and read file

// Open file picker
async function openFile() {
  try {
    const [fileHandle] = await window.showOpenFilePicker({
      "types": [
        {
          "description": "Text Files",
          "accept": {
            "text/plain": [".txt", ".md"],
            "text/html": [".html", ".htm"]
          }
        }
      ],
      "multiple": false
    });
    
    // Get file
    const file = await fileHandle.getFile();
    console.log("File:", file.name, file.size);
    
    // Read content
    const content = await file.text();
    console.log("Content:", content);
    
    // Store handle for later
    window.currentFileHandle = fileHandle;
    
    return content;
  } catch (error) {
    if (error.name === "AbortError") {
      console.log("File picker cancelled");
    } else {
      console.error("Error opening file:", error);
    }
  }
}

// Open multiple files
async function openMultipleFiles() {
  try {
    const fileHandles = await window.showOpenFilePicker({
      "multiple": true,
      "types": [
        {
          "description": "Images",
          "accept": {
            "image/*": [".png", ".jpg", ".jpeg", ".gif", ".webp"]
          }
        }
      ]
    });
    
    for (const handle of fileHandles) {
      const file = await handle.getFile();
      console.log("Selected:", file.name);
    }
  } catch (error) {
    console.error("Error:", error);
  }
}

Example: Save file

// Save file
async function saveFile(content) {
  try {
    const handle = await window.showSaveFilePicker({
      "suggestedName": "document.txt",
      "types": [
        {
          "description": "Text Files",
          "accept": {
            "text/plain": [".txt"]
          }
        }
      ]
    });
    
    // Create writable stream
    const writable = await handle.createWritable();
    
    // Write content
    await writable.write(content);
    
    // Close file
    await writable.close();
    
    console.log("File saved");
  } catch (error) {
    if (error.name === "AbortError") {
      console.log("Save cancelled");
    } else {
      console.error("Error saving file:", error);
    }
  }
}

// Save with current handle (no picker)
async function saveToCurrentFile(content) {
  if (!window.currentFileHandle) {
    return saveFile(content);
  }
  
  try {
    const writable = await window.currentFileHandle.createWritable();
    await writable.write(content);
    await writable.close();
    console.log("File updated");
  } catch (error) {
    console.error("Error updating file:", error);
  }
}

// Write binary data
async function saveBinaryFile(blob) {
  try {
    const handle = await window.showSaveFilePicker({
      "suggestedName": "image.png",
      "types": [
        {
          "description": "PNG Image",
          "accept": { "image/png": [".png"] }
        }
      ]
    });
    
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
  } catch (error) {
    console.error("Error saving image:", error);
  }
}
Note: File System Access API requires user gesture (click event). Only works in secure contexts (HTTPS). Browser shows permission prompt for each file access. File handles can be persisted in IndexedDB for later use. Very limited browser support (mainly Chrome).

6. Directory Handle and File Handle APIs

FileSystemDirectoryHandle Method Description
getFileHandle(name, options) Gets file in directory. Options: create: true to create if missing.
getDirectoryHandle(name, options) Gets subdirectory. Options: create: true to create if missing.
removeEntry(name, options) Removes file/directory. Options: recursive: true for directories.
values() Returns async iterator of entries in directory.
keys() Returns async iterator of entry names.
entries() Returns async iterator of [name, handle] pairs.

Example: Directory operations

// Pick directory
async function pickDirectory() {
  try {
    const dirHandle = await window.showDirectoryPicker();
    console.log("Directory:", dirHandle.name);
    
    // List contents
    await listDirectory(dirHandle);
    
    return dirHandle;
  } catch (error) {
    console.error("Error picking directory:", error);
  }
}

// List directory contents
async function listDirectory(dirHandle) {
  console.log(`Contents of ${dirHandle.name}:`);
  
  for await (const entry of dirHandle.values()) {
    console.log(`  ${entry.kind}: ${entry.name}`);
    
    if (entry.kind === "file") {
      const file = await entry.getFile();
      console.log(`    Size: ${file.size} bytes`);
    }
  }
}

// Read all files in directory
async function readAllFiles(dirHandle) {
  const files = [];
  
  for await (const entry of dirHandle.values()) {
    if (entry.kind === "file") {
      const file = await entry.getFile();
      const content = await file.text();
      files.push({ name: file.name, content });
    }
  }
  
  return files;
}

// Create file in directory
async function createFileInDirectory(dirHandle, fileName, content) {
  try {
    const fileHandle = await dirHandle.getFileHandle(fileName, {
      "create": true
    });
    
    const writable = await fileHandle.createWritable();
    await writable.write(content);
    await writable.close();
    
    console.log(`Created: ${fileName}`);
  } catch (error) {
    console.error("Error creating file:", error);
  }
}

// Create subdirectory
async function createSubdirectory(dirHandle, dirName) {
  try {
    const subDirHandle = await dirHandle.getDirectoryHandle(dirName, {
      "create": true
    });
    console.log(`Created directory: ${dirName}`);
    return subDirHandle;
  } catch (error) {
    console.error("Error creating directory:", error);
  }
}

// Delete file
async function deleteFile(dirHandle, fileName) {
  try {
    await dirHandle.removeEntry(fileName);
    console.log(`Deleted: ${fileName}`);
  } catch (error) {
    console.error("Error deleting file:", error);
  }
}

// Recursively list directory tree
async function listDirectoryTree(dirHandle, indent = "") {
  for await (const [name, handle] of dirHandle.entries()) {
    console.log(`${indent}${handle.kind === "directory" ? "šŸ“" : "šŸ“„"} ${name}`);
    
    if (handle.kind === "directory") {
      await listDirectoryTree(handle, indent + "  ");
    }
  }
}

// Search for files
async function findFiles(dirHandle, pattern) {
  const results = [];
  
  for await (const entry of dirHandle.values()) {
    if (entry.kind === "file" && entry.name.includes(pattern)) {
      results.push(entry);
    } else if (entry.kind === "directory") {
      const subResults = await findFiles(entry, pattern);
      results.push(...subResults);
    }
  }
  
  return results;
}
Warning: File System Access API has very limited browser support (mainly Chrome/Edge). Always check if ("showOpenFilePicker" in window). Requires user permission for each directory access. Writing files requires explicit user consent. Not available in iframes or insecure contexts.

File and Clipboard API Best Practices

  • Validate file type and size before processing - check file.type and file.size
  • Use modern file.text() and file.arrayBuffer() instead of FileReader when possible
  • Use object URLs (URL.createObjectURL) instead of data URLs for better performance
  • Always revoke object URLs with URL.revokeObjectURL() when done to free memory
  • For clipboard, provide fallback to execCommand for older browser support
  • Request clipboard permission with clear explanation - users are wary of clipboard access
  • For drag and drop, always preventDefault() on dragover to enable drop
  • Validate dropped files immediately - check type, size, count before processing
  • File System Access API requires user gesture - call from click handler
  • Store file handles in IndexedDB to reuse without showing picker again
  • Always check browser support for File System Access API - very limited
  • Handle user cancellation gracefully - catch AbortError on picker APIs