Runtime permission checking and requesting for Chrome extensions. Typed, promise-based helpers for the chrome.permissions API with human-readable permission descriptions.
Installation · Quick Start · API Reference · Examples · Chrome Extension Guide · License
Managing optional permissions in Chrome extensions can be verbose and error-prone. This library provides:
- Human-readable descriptions for all 54 Chrome permission strings
- Promise-based API instead of callback-based chrome.permissions
- TypeScript support with full type inference
- Error handling with meaningful error messages
- Batch operations for checking or requesting multiple permissions at once
- Check Permissions — Verify if permissions are granted with descriptive results
- Request Permissions — Prompt users for optional permissions with proper error handling
- Remove Permissions — Revoke granted permissions programmatically
- Permission Descriptions — Get human-readable explanations for any Chrome permission
- Batch Operations — Check or request multiple permissions efficiently
- Full TypeScript — Complete type definitions with IntelliSense support
- Promise-based — Modern async/await API for all operations
- Tiny Bundle — Under 2KB minified and gzipped
npm install @theluckystrike/webext-permissionsOther package managers
# pnpm
pnpm add @theluckystrike/webext-permissions
# yarn
yarn add @theluckystrike/webext-permissionsimport {
checkPermission,
requestPermission,
removePermission,
describePermission,
} from "@theluckystrike/webext-permissions";
// Check if a permission is granted
const hasTabs = await checkPermission("tabs");
console.log(hasTabs);
// { permission: "tabs", granted: true, description: "Read information about open tabs" }
// Request a permission (must be called from user gesture)
const result = await requestPermission("bookmarks");
if (result.granted) {
console.log("Bookmarks permission granted!");
} else {
console.log("User denied or error:", result.error);
}
// Get human-readable description for any permission
console.log(describePermission("activeTab"));
// "Access the currently active tab when you click the extension"
// Remove a permission when no longer needed
await removePermission("tabs");interface PermissionResult {
permission: string;
granted: boolean;
description: string;
}
interface RequestResult {
granted: boolean;
error?: string;
}Returns a human-readable description for any Chrome permission string. Unknown permissions get a generic fallback description.
describePermission("activeTab");
// "Access the currently active tab when you click the extension"
describePermission("tabs");
// "Read information about open tabs"
describePermission("unknownPermission");
// 'Use the "unknownPermission" API'Returns all 54 known Chrome permissions with their descriptions. The granted field is always false in this static listing. Use checkPermissions for live data.
const allPermissions = listPermissions();
console.log(allPermissions.length); // 54
console.log(allPermissions[0]);
// { permission: "activeTab", granted: false, description: "Access the currently active tab..." }Checks whether a single permission is currently granted. Returns a Promise with the permission details including its human-readable description.
const result = await checkPermission("storage");
console.log(result);
// { permission: "storage", granted: true, description: "Store and retrieve data locally" }Batch-checks multiple permissions at once. Returns results in the same order as the input array.
const results = await checkPermissions(["tabs", "storage", "cookies"]);
results.forEach(({ permission, granted }) => {
console.log(`${permission}: ${granted ? "granted" : "not granted"}`);
});Requests a single optional permission from the user. Must be called from a user gesture (click handler, keyboard shortcut, etc.).
const result = await requestPermission("bookmarks");
if (result.granted) {
console.log("Permission granted!");
} else if (result.error) {
console.error("Request failed:", result.error);
} else {
console.log("User denied the request");
}Requests multiple permissions in a single browser prompt. All or nothing — if the user denies any, none are granted.
const result = await requestPermissions(["tabs", "bookmarks", "history"]);
if (result.granted) {
console.log("All permissions granted!");
}Removes a previously granted optional permission. Returns true if successful.
const removed = await removePermission("tabs");
if (removed) {
console.log("Permission removed");
}Returns all permissions currently granted to the extension, with human-readable descriptions.
const granted = await getGrantedPermissions();
granted.forEach(({ permission, description }) => {
console.log(`${permission}: ${description}`);
});import { checkPermissions } from "@theluckystrike/webext-permissions";
async function doSomethingRequiringTabs() {
const results = await checkPermissions(["tabs", "history"]);
const hasPermissions = results.every((r) => r.granted);
if (!hasPermissions) {
console.log("Missing permissions:", results.filter((r) => !r.granted));
return;
}
// ... do something with tabs and history
}import { listPermissions, checkPermissions, requestPermission, removePermission } from "@theluckystrike/webext-permissions";
async function renderPermissionList(container: HTMLElement) {
// Get all known permissions
const allPerms = listPermissions();
// Check which are currently granted
const permissions = allPerms.map((p) => p.permission);
const status = await checkPermissions(permissions);
// Merge and render
container.innerHTML = status
.map(
({ permission, granted, description }) => `
<div class="permission-item">
<input type="checkbox" ${granted ? "checked" : ""} data-perm="${permission}">
<label>${description}</label>
<button ${!granted ? "disabled" : ""} data-remove="${permission}">Remove</button>
</div>
`
)
.join("");
// Add event listeners
container.querySelectorAll("input[data-perm]").forEach((input) => {
input.addEventListener("change", async (e) => {
const perm = (e.target as HTMLInputElement).dataset.perm!;
await requestPermission(perm);
});
});
container.querySelectorAll("button[data-remove]").forEach((btn) => {
btn.addEventListener("click", async (e) => {
const perm = (e.target as HTMLButtonElement).dataset.remove!;
await removePermission(perm);
renderPermissionList(container); // re-render
});
});
}import { requestPermission } from "@theluckystrike/webext-permissions";
document.getElementById("enable-feature")?.addEventListener("click", async () => {
const result = await requestPermission("notifications");
if (result.error) {
// Handle specific errors
switch (result.error) {
case "Must be called from a user gesture":
alert("Please click the button directly, not via keyboard shortcut");
break;
case "The extension does not have permission.":
alert("This permission is not declared in manifest.json");
break;
default:
alert(`Error: ${result.error}`);
}
} else if (!result.granted) {
console.log("User chose not to grant the permission");
} else {
console.log("Permission granted! Enabling feature...");
enableNotifications();
}
});import { getGrantedPermissions, describePermission } from "@theluckystrike/webext-permissions";
document.addEventListener("DOMContentLoaded", async () => {
const list = document.getElementById("granted-list")!;
const granted = await getGrantedPermissions();
if (granted.length === 0) {
list.innerHTML = "<p>No additional permissions granted.</p>";
return;
}
list.innerHTML = granted
.map(
({ permission, description }) => `
<li>
<code>${permission}</code>
<span>${description}</span>
</li>
`
)
.join("");
});import { checkPermission } from "@theluckystrike/webext-permissions";
async function initFeature(featureName: "bookmarks" | "history" | "downloads") {
const hasPermission = await checkPermission(featureName);
if (!hasPermission.granted) {
const result = await requestPermission(featureName);
if (!result.granted) {
console.log(`Feature ${featureName} requires permission`);
return null;
}
}
// Load the feature
return await import(`./features/${featureName}.js`);
}Looking for more help building Chrome extensions? Check out these resources:
- Chrome Extensions Documentation — Official Chrome extension development docs
- Manifest V3 — Current manifest version
- Permissions — Available permissions reference
This package is part of the @zovo/webext family — typed, modular utilities for Chrome extension development:
| Package | Description |
|---|---|
| webext-storage | Typed storage with schema validation |
| webext-messaging | Type-safe message passing |
| webext-tabs | Tab query helpers |
| webext-cookies | Promise-based cookies API |
| webext-i18n | Internationalization toolkit |
When working with permissions in Chrome extensions:
- Request only what's needed — Only ask for permissions when users need them
- Use optional permissions — Declare in manifest, request at runtime
- Handle denials gracefully — Provide alternative functionality
- Explain why you need it — Show users what the permission enables
- Remove when unnecessary — Revoke permissions users no longer need
- Chrome or Chromium-based browser (Chrome, Edge, Brave, etc.)
- Manifest V3 extension
- TypeScript 5.0+ (optional, for TypeScript projects)
Contributions are welcome! Please open an issue or submit a pull request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
# Install dependencies
npm install
# Run tests
npm test
# Build
npm run build
# Type check
npm run typecheckMIT License — see LICENSE for details.
Built by theluckystrike · zovo.one