WizardScript v1.1
The Wizard Browser Extension API

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.

WizardScript extensions run in an isolated context. Each extension gets its own scoped storage on disk and cannot read other extensions' data or your PIN.

Namespaces

wizard.page
Inject CSS/JS, read URL/title, listen to navigation events
wizard.ui
Toolbar buttons with live badges, notifications, theme
wizard.storage
Persistent key-value store, scoped per-extension
wizard.privacy
Read privacy settings, check tracker status
wizard.net
CORS-bypassing HTTP fetch via the main process
wizard.adblock
Live blocked count, engine status, uBO dashboard
NEW

Extension File Structure

my-extension/
├── wizard.json     ← manifest (required)
└── index.js        ← your extension code (required)
To install during development, drop your folder into Settings → Extensions → Install from folder…. Changes apply live — no restart needed.
Getting Started
Quick Start

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

  1. Open Wizard Browser

    Launch the browser and go to Settings → Extensions.

  2. Click "Install from folder…"

    Pick your my-extension folder. The browser validates wizard.json and copies it into the user-data extensions directory.

  3. Done

    Your extension is live. The toolbar button appears immediately. Navigate to any page and watch the badge count climb.

You can disable or uninstall any extension from the same panel. Built-in extensions can be disabled but not removed — they re-install on next boot.
Reference
wizard.json Manifest

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

"name"
string
Display name shown in the Extensions panel.
required
"version"
string
Semver version string (e.g. "1.0.0"). Updates with a newer version automatically replace the installed copy while preserving disabled state.
required
"author"
string
Your name or username.
required
"description"
string
Short one-line description shown in the store and Extensions panel.
required
"script"
string
Path to your main JS file, relative to the manifest. Usually "index.js".
required
"permissions"
string[]
Namespaces your extension uses. Valid values:
page ui storage privacy net adblock
required
"match"
string[]
URL patterns where your 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.
optional — defaults to ["*"]
"icon"
string
Path to a PNG icon (recommended 64×64). Shown in the Extensions panel and store listing.
optional
"homepage"
string
URL to your extension's homepage or GitHub repo.
optional
"builtIn"
boolean
Reserved for extensions shipped with the browser itself. User-submitted extensions cannot set this flag — it's stripped on install.
reserved
API Reference
wizard.page

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.

Requires "page" in your manifest's permissions array.
wizard.page.getURL() → string sync

Returns the current page URL.

const url = wizard.page.getURL();
console.log(url); // "https://github.com/NaniIsNano"
wizard.page.getTitle() → string sync

Returns the current page title.

wizard.page.injectCSS(css: string) → void sync

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); }
`);
wizard.page.injectScript(js: string) → void sync

Executes a JavaScript string in the context of the current page.

wizard.page.injectScript(`
  document.querySelectorAll('.ad').forEach(el => el.remove());
`);
wizard.page.onNavigate(callback: (url: string) => void) → void event

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; }');
  }
});
wizard.page.onLoad(callback: (url: string) => void) → void event

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.

API Reference
wizard.ui

Add buttons to the browser toolbar with live badge counters, show toast notifications, and read the active theme to style your UI accordingly.

Requires "ui" in your manifest's permissions array.
wizard.ui.addButton(options: ButtonOptions) → ButtonHandle sync

Adds a button to the browser toolbar. Returns a ButtonHandle you can use to update the badge, tooltip, or remove the button later.

Options

FieldTypeDescription
iconstringEmoji or single character for the button face
tooltipstringTooltip shown on hover
onClick() => voidCalled when user clicks the button

ButtonHandle methods

MethodSignatureDescription
setBadge(label: string) => voidUpdate the badge text. Pass '' to clear it.
setTooltip(text: string) => voidUpdate the hover tooltip.
remove() => voidRemove 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();
wizard.ui.notify(message: string, options?: NotifyOptions) → void sync

Shows a toast notification in the browser UI.

FieldTypeDescription
type'info' | 'success' | 'warn'Visual style. Default 'info'.
durationnumberMilliseconds before auto-dismiss. Default 3000.
wizard.ui.notify('Tracker blocked!', { type: 'success' });
wizard.ui.notify('Filter lists out of date', { type: 'warn', duration: 5000 });
wizard.ui.getTheme() → Promise<string> async

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…
}
API Reference
wizard.storage

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.

Requires "storage" in your manifest's permissions array. Data persists across sessions but is wiped if the user uninstalls your extension.
wizard.storage.get(key: string) → Promise<any> async

Retrieves a stored value. Returns null if the key doesn't exist.

const enabled = await wizard.storage.get('darkMode');
if (enabled) applyDarkMode();
wizard.storage.set(key: string, value: any) → Promise<void> async

Stores a JSON-serializable value.

await wizard.storage.set('darkMode', true);
await wizard.storage.set('config', { theme: 'purple', count: 42 });
wizard.storage.remove(key: string) → Promise<void> async

Deletes a stored key.

wizard.storage.clear() → Promise<void> async

Deletes all data stored by your extension.

API Reference
wizard.privacy

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.

Requires "privacy" in your manifest's permissions array.
wizard.privacy.getSettings() → Promise<PrivacySettings> async

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
// }
wizard.privacy.isTrackerBlocked(domain: string) → Promise<boolean> async

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
wizard.privacy.getBlockedCount() → Promise<number> async

Returns the total number of trackers blocked in the current session. For richer adblocker state, see wizard.adblock.

API Reference
wizard.net

Make HTTP requests from your extension via the main process — bypasses page-level CORS restrictions.

Requires "net" in your manifest's permissions array. Users see a warning that your extension can make outbound requests. 12 second default timeout.
wizard.net.fetch(url: string, options?: FetchOptions) → Promise<Response> async

Fetch a URL from the main process. Returns a response object with .ok, .status, and .body (string). Use JSON.parse(body) for JSON responses.

OptionTypeDescription
methodstringHTTP verb. Default 'GET'.
headersobjectRequest headers.
bodystring | objectRequest body. Objects are auto-stringified to JSON.
timeoutnumberMilliseconds. 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);
}
wizard.net.post(url: string, body: any, options?: FetchOptions) → Promise<Response> async

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'
});
API Reference · NEW
wizard.adblock

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.

Requires "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.
wizard.adblock.getStatus() → Promise<AdblockStatus> async NEW

Returns the current state of the blocking engine.

FieldTypeDescription
enabledbooleanUser has tracker blocking turned on in Settings → Privacy
readybooleanFilter lists are loaded and the engine is actively blocking
sourcestring'static' · 'cache' · 'network' · 'ublock-origin'
totalFiltersnumberTotal rule count across network + cosmetic filter buckets
lastUpdatednumberEpoch ms when filter lists were last fetched
messagestring | nullHuman-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`);
}
wizard.adblock.getBlockedCount() → Promise<number> async NEW

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));
wizard.adblock.openDashboard() → boolean sync NEW

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' });
}
wizard.adblock.getUboStatus() → Promise<UboStatus> async NEW

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'.

wizard.adblock.onStatus(callback: (status: AdblockStatus) => void) → void event NEW

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…');
});
wizard.adblock.onUboStatus(callback: (status: UboStatus) => void) → void event NEW

Fires on uBlock Origin lifecycle changes (download progress, install success/failure, auto-update).

Cookbook
Examples

Real, working WizardScript extensions you can copy and modify. The Wizard Adblocker example below is the actual built-in extension — exactly as shipped.

🛡 Wizard Adblocker (built-in)
Live blocked-request badge with engine-aware tooltip and dashboard launch
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);
🌙 Dark Mode Everything
Force dark mode on every website using a CSS filter, with persistent toggle
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();
})();
📦 npm Package Lookup
Show npm package info from a button click via CORS-bypassing fetch
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 });
  }
});
🎯 GitHub-only Enhancements
Use match patterns to scope your script to a single domain
// 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; }
  `);
});
Publishing
Submit to the Store

The Wizard Extension Store is live. Here's how to get your extension reviewed and published.

Submission Checklist

  1. 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.

  2. Icon

    A 64×64 or 128×128 PNG icon. Clean, legible at small size. Optional but strongly recommended for store listings.

  3. No malicious code

    Extensions are reviewed by moderators before listing. No data exfiltration, no crypto mining, no obfuscated code, no eval() on remote strings.

  4. WPL-1.0 compatible

    Your extension code must be open source and compatible with the Wizard Public License.

How to Submit

  1. Create an account

    Sign up at wizardextensionstore.netlify.app as a developer. You'll need a GitHub profile for verification.

  2. Submit New Extension

    From your Developer Dashboard, click Submit New. Drag in your .wizext file (a zip of your extension folder, renamed) and fill in the listing.

  3. Wait for review

    A moderator reviews your code, manifest, and listing — usually within 24 hours. Verified developers may bypass review for minor updates.

  4. Go live

    Once approved, your extension appears in the store and is installable by anyone running Wizard Browser.