WizardScript is Wizard Browser's native extension API. Write extensions in plain JavaScript using the wizard.* namespace — no build tools, no complex manifests, no Chrome compatibility headaches. Drop a folder into the Extensions panel and it goes live without a restart.
What is WizardScript?
When you install an extension in Wizard Browser, it gets access to the wizard global object — a curated set of APIs that let your extension interact with the browser, the current page, the browser UI chrome, persistent storage, the user's privacy settings, the network, and the built-in ad/tracker blocker.
Unlike Chrome extensions, WizardScript extensions are a single .js file paired with a wizard.json manifest. No background pages, no service workers, no popup HTML — just your script and the API.
Namespaces
Extension File Structure
my-extension/ ├── wizard.json ← manifest (required) └── index.js ← your extension code (required)
Settings → Extensions → Install from folder…. Changes apply live — no restart needed.
Build your first WizardScript extension in under 5 minutes.
1. Create your files
Make a folder with two files:
// wizard.json { "name": "Hello Wizard", "version": "1.0.0", "author": "yourname", "description": "My first extension", "script": "index.js", "permissions": ["page", "ui"], "match": ["*"] }
// index.js // Add a toolbar button — returns a handle const btn = wizard.ui.addButton({ icon: '🧙', tooltip: 'Click me', onClick: () => { wizard.ui.notify('Hello from my extension!'); } }); // Update its badge live let clicks = 0; btn.setBadge(''); // Log on every navigation wizard.page.onNavigate((url) => { clicks++; btn.setBadge(String(clicks)); console.log('Navigated to:', url); });
2. Install it
Open Wizard Browser
Launch the browser and go to
Settings → Extensions.Click "Install from folder…"
Pick your
my-extensionfolder. The browser validateswizard.jsonand copies it into the user-data extensions directory.Done
Your extension is live. The toolbar button appears immediately. Navigate to any page and watch the badge count climb.
Every extension needs a wizard.json manifest at its root. It tells Wizard Browser your extension's name, what it does, and what permissions it needs.
Full Example
{
"name": "Dark Mode Everything",
"version": "1.2.0",
"author": "yourname",
"description": "Force dark mode on every website",
"script": "index.js",
"icon": "icon.png",
"homepage": "https://github.com/yourname/dark-mode",
"permissions": ["page", "storage", "ui"],
"match": ["*"]
}
Fields
"1.0.0"). Updates with a newer version automatically replace the installed copy while preserving disabled state."index.js".page ui storage privacy net adblock
wizard.page hooks fire. Use "*" for every page, or scope to a domain like "https://github.com/*". Toolbar buttons added via wizard.ui remain visible regardless of match.["*"]Interact with the current page — inject styles or scripts, read the URL, and listen to navigation events. Hooks only fire on URLs matching your manifest's match patterns.
"page" in your manifest's permissions array.
Returns the current page URL.
const url = wizard.page.getURL(); console.log(url); // "https://github.com/NaniIsNano"
Returns the current page title.
Injects a CSS string into the current page. Applied immediately.
wizard.page.injectCSS(` body { background: #000 !important; color: #fff !important; } img { filter: invert(1) hue-rotate(180deg); } `);
Executes a JavaScript string in the context of the current page.
wizard.page.injectScript(` document.querySelectorAll('.ad').forEach(el => el.remove()); `);
Fires every time the user navigates to a new URL. Your callback receives the new URL. Only fires for URLs matching your manifest's match patterns.
wizard.page.onNavigate((url) => { if (url.includes('youtube.com')) { wizard.page.injectCSS('#masthead { display: none !important; }'); } });
Fires when a page finishes loading (DOM ready). Use this instead of onNavigate when you need the full DOM to be available before reading or injecting.
Add buttons to the browser toolbar with live badge counters, show toast notifications, and read the active theme to style your UI accordingly.
"ui" in your manifest's permissions array.
Adds a button to the browser toolbar. Returns a ButtonHandle you can use to update the badge, tooltip, or remove the button later.
Options
| Field | Type | Description |
|---|---|---|
| icon | string | Emoji or single character for the button face |
| tooltip | string | Tooltip shown on hover |
| onClick | () => void | Called when user clicks the button |
ButtonHandle methods
| Method | Signature | Description |
|---|---|---|
| setBadge | (label: string) => void | Update the badge text. Pass '' to clear it. |
| setTooltip | (text: string) => void | Update the hover tooltip. |
| remove | () => void | Remove the button from the toolbar. |
const btn = wizard.ui.addButton({ icon: '🛡', tooltip: 'Wizard Adblocker', onClick: () => wizard.ui.notify('Clicked!') }); // Live badge updates btn.setBadge('42'); btn.setTooltip('42 blocks this session'); // Remove when done btn.remove();
Shows a toast notification in the browser UI.
| Field | Type | Description |
|---|---|---|
| type | 'info' | 'success' | 'warn' | Visual style. Default 'info'. |
| duration | number | Milliseconds before auto-dismiss. Default 3000. |
wizard.ui.notify('Tracker blocked!', { type: 'success' }); wizard.ui.notify('Filter lists out of date', { type: 'warn', duration: 5000 });
Returns the active color scheme (e.g. 'default', 'frutiger', 'retrowave', 'rose', 'emerald'). Useful for matching your extension's styles to the browser theme.
const theme = await wizard.ui.getTheme(); if (theme === 'retrowave') { // adapt accent colours… }
Persist data across sessions. Storage is scoped per-extension — written to disk as storage.json in your extension's folder. Other extensions cannot read or write your data.
"storage" in your manifest's permissions array. Data persists across sessions but is wiped if the user uninstalls your extension.
Retrieves a stored value. Returns null if the key doesn't exist.
const enabled = await wizard.storage.get('darkMode'); if (enabled) applyDarkMode();
Stores a JSON-serializable value.
await wizard.storage.set('darkMode', true); await wizard.storage.set('config', { theme: 'purple', count: 42 });
Deletes a stored key.
Deletes all data stored by your extension.
Read the user's current privacy settings and query the tracker blocklist. Extensions can read privacy state but cannot change it — that's a user-controlled surface.
"privacy" in your manifest's permissions array.
Returns the user's current privacy settings.
const p = await wizard.privacy.getSettings(); // { // trackerBlocking: true, // doNotTrack: true, // canvasSpoofing: true, // webrtcProtection: true, // referrerStripping: true, // clearOnExit: true // }
Returns true if the given domain is on the tracker blocklist.
await wizard.privacy.isTrackerBlocked('doubleclick.net'); // true await wizard.privacy.isTrackerBlocked('github.com'); // false
Returns the total number of trackers blocked in the current session. For richer adblocker state, see wizard.adblock.
Make HTTP requests from your extension via the main process — bypasses page-level CORS restrictions.
"net" in your manifest's permissions array. Users see a warning that your extension can make outbound requests. 12 second default timeout.
Fetch a URL from the main process. Returns a response object with .ok, .status, and .body (string). Use JSON.parse(body) for JSON responses.
| Option | Type | Description |
|---|---|---|
| method | string | HTTP verb. Default 'GET'. |
| headers | object | Request headers. |
| body | string | object | Request body. Objects are auto-stringified to JSON. |
| timeout | number | Milliseconds. Default 12000. |
const res = await wizard.net.fetch( 'https://api.github.com/repos/NaniIsNano/wizard-browser' ); if (res.ok) { const data = JSON.parse(res.body); console.log(data.stargazers_count); }
POST JSON to a URL. Content-Type: application/json is set automatically; the body is auto-serialized.
const res = await wizard.net.post('https://api.example.com/events', { type: 'install', version: '1.0.0' });
Query Wizard Browser's built-in ad & tracker blocking engine. Read live blocked counts, engine status (Ghostery filter engine vs. real uBlock Origin), and open the dashboard. This is the same surface the built-in Wizard Adblocker extension uses for its toolbar badge.
"adblock" in your manifest's permissions array. The engine itself is owned by the browser; extensions can read state and open the dashboard but cannot modify filter lists.
Returns the current state of the blocking engine.
| Field | Type | Description |
|---|---|---|
| enabled | boolean | User has tracker blocking turned on in Settings → Privacy |
| ready | boolean | Filter lists are loaded and the engine is actively blocking |
| source | string | 'static' · 'cache' · 'network' · 'ublock-origin' |
| totalFilters | number | Total rule count across network + cosmetic filter buckets |
| lastUpdated | number | Epoch ms when filter lists were last fetched |
| message | string | null | Human-readable status if not ready |
const s = await wizard.adblock.getStatus(); if (s.enabled && s.ready) { console.log(`Active engine: ${s.source} · ${s.totalFilters.toLocaleString()} rules`); }
Total trackers/ads blocked in the current session. Counted across all engines (Ghostery + uBO + static fallback).
const n = await wizard.adblock.getBlockedCount(); btn.setBadge(n > 999 ? Math.floor(n / 1000) + 'k' : String(n));
Opens the Wizard-native adblocker dashboard. Returns true if the call succeeded. The dashboard talks to whichever engine is currently active (Ghostery or uBO) and shows live blocked-request logs.
if (!wizard.adblock.openDashboard()) { wizard.ui.notify('Dashboard unavailable on this build.', { type: 'warn' }); }
Returns the state of the real uBlock Origin Chrome extension (loaded via session.loadExtension). The state field is one of 'idle' · 'downloading' · 'extracting' · 'loading' · 'active' · 'failed' · 'unsupported'.
Fires whenever the blocking engine status changes — filter lists refreshed, user toggled tracker blocking, uBO installed, etc. Use this to keep a live tooltip or badge in sync.
wizard.adblock.onStatus((s) => { btn.setTooltip(s.ready ? `${s.totalFilters} rules · ${s.source}` : 'Adblocker loading…'); });
Fires on uBlock Origin lifecycle changes (download progress, install success/failure, auto-update).
Real, working WizardScript extensions you can copy and modify. The Wizard Adblocker example below is the actual built-in extension — exactly as shipped.
let cachedStatus = null; let lastRendered = -1; const btn = wizard.ui.addButton({ icon: '🛡', tooltip: 'Wizard Adblocker', onClick: async () => { // Route to the Wizard-native dashboard if (wizard.adblock.openDashboard()) return; const s = cachedStatus || await wizard.adblock.getStatus(); if (!s.enabled) { wizard.ui.notify('Tracker blocking is OFF.', { type: 'warn' }); return; } const blocked = await wizard.adblock.getBlockedCount(); wizard.ui.notify(`${blocked.toLocaleString()} blocked this session`, { type: 'success' }); } }); async function refreshBadge() { const n = await wizard.adblock.getBlockedCount(); if (n === lastRendered) return; lastRendered = n; const label = n < 1000 ? String(n) : n < 10000 ? (Math.floor(n / 100) / 10).toFixed(1) + 'k' : Math.floor(n / 1000) + 'k'; btn.setBadge(n > 0 ? label : ''); } wizard.adblock.onStatus((s) => { cachedStatus = s; btn.setTooltip(s.ready ? `Wizard Adblocker — ${s.totalFilters.toLocaleString()} rules` : 'Wizard Adblocker — loading…'); }); refreshBadge(); setInterval(refreshBadge, 1500);
let dark = false; const btn = wizard.ui.addButton({ icon: '🌙', tooltip: 'Toggle Dark Mode', onClick: async () => { dark = !dark; await wizard.storage.set('dark', dark); applyDark(); } }); function applyDark() { wizard.page.injectCSS(dark ? ` html { filter: invert(1) hue-rotate(180deg) !important; } img, video { filter: invert(1) hue-rotate(180deg); } ` : `html { filter: none !important; }`); } (async () => { dark = (await wizard.storage.get('dark')) ?? false; wizard.page.onNavigate(() => applyDark()); applyDark(); })();
wizard.ui.addButton({ icon: '📦', tooltip: 'Look up npm package', onClick: async () => { const pkg = prompt('Package name?'); if (!pkg) return; const res = await wizard.net.fetch(`https://registry.npmjs.org/${pkg}/latest`); if (!res.ok) { wizard.ui.notify('Package not found', { type: 'warn' }); return; } const data = JSON.parse(res.body); wizard.ui.notify(`${data.name} v${data.version}`, { type: 'success', duration: 5000 }); } });
// In wizard.json: // "match": ["https://github.com/*"] wizard.page.onLoad((url) => { // Only fires on github.com URLs because of the match pattern wizard.page.injectCSS(` .Header { background: #7c3aed !important; } .repository-content { max-width: 1400px !important; } `); });
The Wizard Extension Store is live. Here's how to get your extension reviewed and published.
Submission Checklist
Valid wizard.json
All required fields filled (name, version, author, description, script, permissions). Version is valid semver. Permissions array contains only namespaces your extension actually uses.
Icon
A 64×64 or 128×128 PNG icon. Clean, legible at small size. Optional but strongly recommended for store listings.
No malicious code
Extensions are reviewed by moderators before listing. No data exfiltration, no crypto mining, no obfuscated code, no
eval()on remote strings.WPL-1.0 compatible
Your extension code must be open source and compatible with the Wizard Public License.
How to Submit
Create an account
Sign up at wizardextensionstore.netlify.app as a developer. You'll need a GitHub profile for verification.
Submit New Extension
From your Developer Dashboard, click Submit New. Drag in your
.wizextfile (a zip of your extension folder, renamed) and fill in the listing.Wait for review
A moderator reviews your code, manifest, and listing — usually within 24 hours. Verified developers may bypass review for minor updates.
Go live
Once approved, your extension appears in the store and is installable by anyone running Wizard Browser.