AvaloniaUI/Avalonia · error · Error

Permissions denied

Error message

Permissions denied

What it means

Thrown by StorageItem.verifyPermissions when handle.requestPermission({mode}) resolves to 'denied'. After queryPermission is not 'granted', the code requests permission; if the user denies the browser permission prompt (or a prior denial is sticky), this error aborts the operation.

Source

Thrown at src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts:181

        return await ((item.handle as any).move(destination /*, newName */) as Promise<any>);
    }

    private async verifyPermissions(mode: "read" | "readwrite"): Promise<void | never> {
        if (!this.handle) {
            return;
        }

        // If we are using polyfill, let it decide permissions by itself, we can't request anything in this case.
        if (!Caniuse.hasNativeFilePicker()) {
            return;
        }

        if (await this.handle.queryPermission({ mode }) === "granted") {
            return;
        }

        if (await this.handle.requestPermission({ mode }) === "denied") {
            throw new Error("Permissions denied");
        }
    }

    public static async saveBookmark(item: StorageItem): Promise<string | null> {
        // If file was previously bookmarked, just return old one.
        if (item.bookmarkId) {
            return item.bookmarkId;
        }

        // Bookmarks are not supported with polyfill.
        if (!item.handle || !Caniuse.hasNativeFilePicker()) {
            return null;
        }

        const connection = await avaloniaDb.connect();
        try {
            const key = await connection.put(fileBookmarksStore, item.handle, item.generateBookmarkId());
            return key as string;

View on GitHub (pinned to 11c5427268)

Solutions

  1. Catch the error and surface a clear UI asking the user to grant permission, then retry.
  2. Check handle.queryPermission first and only call the operation when it is 'granted', requesting permission via a user gesture.
  3. Ensure the permission request originates from a user gesture (click), as browsers may auto-deny non-gesture requests.
  4. Fall back to a non-persistent path (download/upload) if permission is persistently denied.

Example fix

// before
const file = await StorageItem.openRead(item); // user denied -> throws

// after
const status = await item.handle?.queryPermission({ mode: 'read' });
if (status !== 'granted') { showPermissionPrompt(); return; }
try { const file = await StorageItem.openRead(item); }
catch (e) { if (e.message === 'Permissions denied') showRetryUI(); else throw e; }
Defensive patterns

Strategy: try-catch

Validate before calling

async function ensurePermission(handle: FileSystemHandle, mode: 'read' | 'readwrite'): Promise<boolean> {
  if (await handle.queryPermission({ mode }) === 'granted') return true;
  return await handle.requestPermission({ mode }) === 'granted';
}

Type guard

function hasQueryablePermission(handle: any): boolean {
  return !!handle && typeof handle.queryPermission === 'function';
}

Try / catch

try { return await StorageItem.openRead(item); }
catch (e) {
  if (e instanceof Error && e.message === 'Permissions denied') { showPermissionRetryUI(); return null; }
  throw e;
}

Prevention

When it happens

Trigger: Any storage operation (openRead/openWrite/createFile/createFolder/moveAsync) that calls verifyPermissions('read'|'readwrite') and the user dismisses or denies the permission prompt, or requestPermission returns 'denied' because the site lacks persistence/engagement.

Common situations: User clicks 'Block' on the file-system permission prompt; the origin is in a denied state from a previous session; calling readwrite without the handle being granted; ephemeral/private browsing where prompts auto-deny.

Related errors


AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13). Data as JSON: /api/errors/2f0b8df073555a37. Report an issue: GitHub.