downloads Permission Reference

12 min read

downloads Permission Reference

What It Does

This is a medium-warning permission — users will see a prompt when installing your extension.

Manifest Configuration

Basic downloads permission

{
  "permissions": ["downloads"]
}

With file opening capability

{
  "permissions": ["downloads", "downloads.open"]
}

The downloads permission is required for all download functionality. The additional downloads.open permission is needed only if you want to open files after download completes.

Using with @theluckystrike/webext-permissions

import { checkPermission, PERMISSION_DESCRIPTIONS } from "@theluckystrike/webext-permissions";

const result = await checkPermission("downloads");
console.log(result.description); // "Manage downloads"
console.log(result.granted);     // true (if in manifest)

PERMISSION_DESCRIPTIONS.downloads; // "Manage downloads"

Using with @theluckystrike/webext-messaging

Pattern: Popup or content script triggers downloads via background:

// shared/messages.ts
type Messages = {
  downloadFile: {
    request: { url: string; filename?: string };
    response: { downloadId: number };
  };
  getDownloadProgress: {
    request: { downloadId: number };
    response: { state: string; bytesReceived?: number; totalBytes?: number };
  };
  searchDownloads: {
    request: { query?: string };
    response: chrome.downloads.DownloadItem[];
  };
  cancelDownload: {
    request: { downloadId: number };
    response: { cancelled: boolean };
  };
};

// background.ts
import { createMessenger } from "@theluckystrike/webext-messaging";

const msg = createMessenger<Messages>();

msg.onMessage({
  downloadFile: async ({ url, filename }) => {
    const downloadId = await chrome.downloads.download({
      url,
      filename,
      saveAs: true,
    });
    return { downloadId };
  },
  getDownloadProgress: async ({ downloadId }) => {
    const item = await chrome.downloads.search({ id: downloadId });
    if (!item.length) return { state: "unknown" };
    const [download] = item;
    return {
      state: download.state,
      bytesReceived: download.bytesReceived,
      totalBytes: download.totalBytes,
    };
  },
  searchDownloads: async ({ query }) => {
    return chrome.downloads.search(query ? { query: [query] } : {});
  },
  cancelDownload: async ({ downloadId }) => {
    await chrome.downloads.cancel(downloadId);
    return { cancelled: true };
  },
});

Download event forwarding

// background.ts - Forward download events to popup
chrome.downloads.onChanged.addListener((downloadDelta) => {
  // Broadcast to all contexts that need download progress
  msg.broadcast({ type: "downloadProgress", payload: downloadDelta });
});

Using with @theluckystrike/webext-storage

Track download preferences and maintain download history:

import { defineSchema, createStorage } from "@theluckystrike/webext-storage";

// Schema for download preferences and history
const schema = defineSchema({
  defaultDownloadPath: "" as string,
  autoOpenDownloads: false as boolean,
  maxConcurrentDownloads: 3 as number,
  downloadHistory: [] as Array<{
    id: number;
    url: string;
    filename: string;
    completedAt: number;
    bytes: number;
  }>,
});

const storage = createStorage({ schema });

// Log completed downloads via onChanged listener
chrome.downloads.onChanged.addListener(async (downloadDelta) => {
  if (downloadDelta.state?.current === "complete") {
    const items = await chrome.downloads.search({ id: downloadDelta.id });
    if (items.length) {
      const item = items[0];
      const history = await storage.get("downloadHistory");
      history.push({
        id: item.id,
        url: item.url,
        filename: item.filename || "",
        completedAt: Date.now(),
        bytes: item.bytesReceived || 0,
      });
      // Keep only last 100 downloads
      if (history.length > 100) history.shift();
      await storage.set("downloadHistory", history);
    }
  }
});

// React to preference changes
storage.watch("autoOpenDownloads", async (autoOpen) => {
  if (autoOpen) {
    chrome.downloads.onChanged.addListener(openOnComplete);
  } else {
    chrome.downloads.onChanged.removeListener(openOnComplete);
  }
});

async function openOnComplete(downloadDelta: chrome.downloads.DownloadDelta) {
  if (downloadDelta.state?.current === "complete") {
    await chrome.downloads.open(downloadDelta.id);
  }
}

Key API Methods

Method Description
downloads.download(options) Start a new download, returns downloadId
downloads.search(query) Find downloads matching criteria
downloads.pause(downloadId) Pause an in-progress download
downloads.resume(downloadId) Resume a paused download
downloads.cancel(downloadId) Cancel an in-progress download
downloads.erase(query) Remove downloads from history
downloads.open(downloadId) Open downloaded file (requires downloads.open permission)
downloads.show(downloadId) Show downloaded file in system file manager
downloads.showDefaultFolder() Open default downloads folder
downloads.onCreated Event — fires when a download starts
downloads.onChanged Event — fires when download state or progress changes
downloads.onErased Event — fires when download is removed from history
downloads.onDeterminingFilename Event — modify filename before download starts

Download Options

interface DownloadOptions {
  // Required
  url: string;
  
  // Optional
  filename?: string;        // Relative to downloads directory
  saveAs?: boolean;         // Show "Save As" dialog
  conflictAction?: string; // "uniquify", "overwrite", "prompt"
  method?: string;          // "GET" (default) or "POST"
  headers?: HeaderPair[];   // Custom HTTP headers
  body?: string;            // Request body for POST
  incognito?: boolean;      // Download in incognito mode
}

interface HeaderPair {
  name: string;
  value: string;
}

conflictAction options

Common Patterns

Bulk downloader

async function downloadAll(urls: string[], folder: string) {
  const queue = [...urls];
  const results: number[] = [];
  
  while (queue.length > 0) {
    const batch = queue.splice(0, 3); // 3 concurrent
    const promises = batch.map((url, i) => 
      chrome.downloads.download({
        url,
        filename: `${folder}/${i + 1}`,
        saveAs: false,
      })
    );
    const ids = await Promise.all(promises);
    results.push(...ids);
  }
  return results;
}

Download manager UI

// In popup or options page
async function renderDownloadManager() {
  const downloads = await chrome.downloads.search({});
  
  const container = document.getElementById("downloads");
  container.innerHTML = downloads.map(d => `
    <div class="download-item" data-id="${d.id}">
      <div class="filename">${d.filename}</div>
      <div class="progress">
        <div class="bar" style="width: ${getProgressPercent(d)}%"></div>
      </div>
      <div class="status">${d.state}</div>
      <div class="actions">
        ${d.state === "in_progress" ? `<button data-action="pause">Pause</button>` : ""}
        ${d.state === "in_progress" ? `<button data-action="cancel">Cancel</button>` : ""}
        ${d.state === "complete" ? `<button data-action="open">Open</button>` : ""}
      </div>
    </div>
  `).join("");
}

function getProgressPercent(download: chrome.downloads.DownloadItem): number {
  if (!download.totalBytes) return 0;
  return Math.round((download.bytesReceived / download.totalBytes) * 100);
}

Export data as file

function exportAsJson(data: unknown, filename: string) {
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
  const url = URL.createObjectURL(blob);
  
  return chrome.downloads.download({
    url,
    filename,
    saveAs: true,
  });
}

// Usage
await exportAsJson({ users: ["alice", "bob"] }, "export/users.json");

Gotchas

API Reference

Frequently Asked Questions

How do I download a file in Chrome extension?

Use chrome.downloads.download() to initiate downloads from your extension. You can specify the URL, filename, and other options.

Can extensions download files to a custom location?

Yes, but users will be prompted to choose a location, or you can use the downloads API’s “saveAs” option set to false with appropriate permissions. —

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.

No previous article
No next article