A small, dependency-free Chrome extension (Manifest V3) that switches the browser proxy in one click: system, direct, manual HTTP/HTTPS/SOCKS or PAC script. The whole UI is bilingual — English and Persian (فارسی) with full RTL support — and everything runs locally.
Both pages open without installing anything — see
Try the UI without installing — and these pictures are taken from
them by npm run shots, so they are the interface itself rather than a drawing of it.
- Four modes in one click — system proxy, direct (no proxy), a saved server, or a PAC script.
- Saved servers — name, scheme (HTTP/HTTPS/SOCKS4/SOCKS5), host, port, optional credentials.
Paste a whole proxy URL (
socks5://user:pass@127.0.0.1:1080, or justhost:8080) into the Host field and scheme, host, port and credentials fill themselves. - Server search — a search box appears above the list once it grows past four servers, and
/jumps to it from anywhere in the popup. Deleting a server is confirmed inside the row rather than by a blocking dialog, so the popup never loses its place. - Proxy authentication — optional automatic answers to proxy login prompts, only for the server you are actually using.
- Automatic failover — when the active server stops answering, the extension checks the connection once more and, if the server really is down, switches to the next server in your list. One round visits every server once and then stops, so a network outage cannot make it flip back and forth. Toggle it on the settings page (it defaults to on).
- Switch notification — an automatic switch is never silent: a system notification names the server that took over, and the toolbar icon wears its name for a few seconds. Clicking it opens your server list, and its Back to … button undoes the switch. Only switches the extension makes on its own are announced, and the notification can be switched off on the settings page.
- Domain routing — in PAC mode, tick Route only the sites I list and give it one domain per line: those sites and their subdomains go through your servers — the active one first, the others as fallback — while everything else stays direct. The list is compiled into a PAC script inside the extension — nothing to host, nothing to download — and a listed site is never sent direct, so a server that is down fails loudly instead of leaking it. The fallback chain follows what the extension has seen: a server that recently answered is tried before one nobody has looked at, and one that recently failed goes last. Each listed site can also name a server of its own — a row appears under the list with a picker for every rule — so one site leaves through Frankfurt while another leaves through Amsterdam. A named server still heads a chain rather than a single hop: that server is tried first and the rest stay behind it, so even a site with its own server is never sent direct.
- Never fall back to a direct connection — an optional switch for the one case where the
extension's promise is weakest: a mode whose route cannot be built, because no server is saved or
no PAC script is set. Left off — the shipped behaviour — it fails open and connects directly, which
is what a browser is expected to do. Turned on, the browser is pointed at
127.0.0.1:1instead, where nothing listens, so the request fails loudly rather than leaving unwatched. A direct connection you asked for is untouched: Direct mode, and every host on the bypass list, still go direct — a choice is not a fallback. - “This site” in the popup — the popup shows the host of the tab it was opened over and offers three choices: Through the proxy, Directly, or Follow the mode. The chips write the two site lists, and the line under them says what the current mode actually does with that host, so the control never promises routing the mode will not do. Choosing Through the proxy turns domain routing on when the mode would otherwise ignore the list (never in manual mode, where everything is already proxied, and never without a server to route through). A Why? line under the card's answer opens the same reasoning the route check gives, for the tab you are looking at.
- Route check — the domain-routing panel takes any host, listed or not (
www.example.comunder a rule forexample.comis the question a rule list leaves you with) and answers with the mode in force, the rule that matched, the server that rule named, and the order the servers will be tried in. Nothing is sent anywhere: the answer comes from your own configuration, and whether a route exists at all is decided by building the same script the browser is handed, so the explanation cannot drift from what the extension does. - Bypass list — one rule per line (
<local>,localhost,*.internal.example.com, …). - Master switch — instantly go direct without losing the mode you configured.
- Context menu — right-click the toolbar icon to switch mode or server.
- Live badge — the toolbar icon shows
SYS,ON,PAC,OFForERR, and the popup repeats it as a pill next to the status text. - Connection test — one click tells you whether traffic really flows, through which host and
how many milliseconds it took. Only a
204from the probe endpoint counts as success, so a captive portal or a block page that answers200is reported as a failure instead of a false “works”. - Test all servers — one button next to Add server looks at every saved server in turn, not
just the one the mode is using, so the row verdicts and the generated chain's order refresh from a
single click. The pass says where it is (
3 of 8 tested…), ends with5 worked · 2 no answer · 1 skipped, and a server it could not check is counted as skipped rather than guessed about. It works in the modes whose routing a probe can reproduce (manual and domain routing) and says why when it cannot, and it runs in the service worker, so closing the popup does not stop it. - Background checks — optional, off by default: while the extension is routing, it looks at one server every few minutes so what it knows about each of them stays recent — which is what orders the generated chain. A check installs your routing with a single difference: only the extension's own probe travels through the server being tested, so your browsing keeps the route you configured, and a server that is down fails the check rather than a page you were loading. Turn it on with Check my servers in the background on the settings page.
- Server list that says what the extension knows — under each address the row reports the last
verdict and how long ago it was taken (
answered in 42 ms · 3 min ago,no answer · 12 min ago), with a coloured dot for the tone. A server nobody has looked at says nothing, and a verdict too old to order the chain is dimmed rather than dropped, so the list and the chain agree. - Traffic meter — the popup shows how much has gone down and up today, and the settings page the
same for today and in total, with a reset button. Chrome tells an extension nothing about the size
of a request, so the meter adds up what each one declares before its bytes move: the
Content-Lengthof the request and of its response. That makes the figures a floor rather than a bill — a streamed video, an event stream or a chunked page declares nothing and is not counted, and a response Chrome answers out of its own cache is not counted at all — and the settings page says so next to the numbers. Counting happens on your device, only while the proxy is on, and the counters live in their own storage key, so an exported settings file never carries them. Count traffic on the settings page turns it off. - Live speed — under today's figures the popup shows what is moving right now (
↓ 4.6 MB/s · ↑ 120 KB/s) beside a dot that goes quiet when nothing is, and the settings page has the same reading as Right now. Every second of counting is written on its own and reports the span its own bytes covered, so the line moves while a transfer moves, a transfer that is still going reads its own rate rather than a fraction of it, and one that has stopped reads nothing moving instead of leaving the tail of a burst on screen as if it were a speed. - Four traffic display templates — Display on the settings page lays the same three readings out four ways: the shipped Classic (today with the live speed under it), a one-line Compact for a small popup, three Cards for right now, today and the total, and Speed first, where the live reading is the headline. The popup wears whichever you pick, and a backup carries it.
- Keyboard shortcuts —
Alt+Shift+Pturns the proxy on or off,Alt+Shift+Dgoes direct. - Bilingual UI — switch language from the popup; Persian is rendered RTL.
- Theme — automatic (follows your operating system), light or dark. Set it on the settings page, or click the ◐ button in the popup to cycle through the three options without leaving it.
- Accent colour — five palettes (emerald, ocean, violet, amber and rose) recolour the interface. Pick one on the settings page; both pages wear it, and a backup carries it. Mode and status colours never change, so a green “reachable” hint stays green in every palette.
- Spacing and text size — two more appearance settings on the same card. Spacing chooses
comfortable or compact: the same layout with less air between the cards, and nothing hidden — no
card, figure or button disappears. Text size moves every text size on both pages at once through
one type scale (
--type-scaleinbase.css), so the popup can be made readable without zooming the whole browser. Both travel in a backup. - Backup & restore — export/import the whole configuration as JSON.
- No analytics, no telemetry, no remote code. The only requests the extension itself ever makes
are the optional Connection test/Test all buttons and the background checks it can run on a
timer — the same bare
generate_204probes, carrying nothing. See PRIVACY.md.
| Browser | Minimum version | Archive |
|---|---|---|
| Chrome, Edge, Brave, Opera, Vivaldi | 116+ | proxy-switch-vX.Y.Z.zip |
Every release carries that one archive, which is what the Chrome Web Store takes:
MV3 with a service worker background. There is no Firefox build: v1.6.1 shipped one beside it under
manifest.firefox.json, and it has since been removed (see CHANGELOG.md).
- Download
proxy-switch-vX.Y.Z.zipfrom the releases page. - Unzip it somewhere permanent.
- Open
chrome://extensions, enable Developer mode, click Load unpacked and pick the unzipped folder.
git clone https://github.com/NigaByte031/Proxy-Switch.git
cd Proxy-Switch
npm run package # writes dist/proxy-switch-v<version>.zip (optional)Then load the repository folder itself with Load unpacked — the manifest points at src/, so
no build step is required.
src/__preview_popup.html and src/__preview_options.html are generated copies of the real pages
with a mocked chrome.* API (backed by localStorage). Open either file in any browser tab to
click through the interface — no extension install, no Chrome needed.
npm run preview # regenerate them after editing popup.html/options.html
npm run preview -- --check # verify they are in sync| Mode | What Chrome does |
|---|---|
| System | Follows the proxy configured in your operating system. |
| Direct | No proxy at all — everything connects directly. |
| Manual | Routes every request through the server you select. |
| PAC | Downloads a proxy auto-config script and obeys it. |
- Open the popup from the toolbar.
- Pick a mode, or add a server and click it — clicking a server switches to it immediately.
- Flip the master switch off to go direct temporarily; your mode is remembered.
- Press Test connection when you want proof: it sends one tiny request through the mode that is currently applied and reports the host that answered and the round-trip time. Hover the result to see the raw reason when it fails. The Test all button next to the server list asks the same question of every saved server, one after another.
- The ◐ button switches between the automatic, light and dark themes; the ⚙ button (or
chrome://extensions→ Details → Extension options) opens the settings page, where you manage servers, the bypass list, the language, the theme and accent colour, automatic failover and JSON backups.
If a mode has nothing to work with (no server selected, empty PAC URL) the extension fails open: traffic goes direct and the popup shows a warning instead of leaving you without a connection.
The toolbar badge is honest about what is really in force: it shows ERR when Chrome refused the
change (another extension or a policy owns the proxy settings) or when the proxy stopped answering,
and the popup spells out which of the two happened. The settings page repeats that reason next to
the mode chips, with a Try again button that asks the service worker to apply the mode once more.
| Shortcut | Action |
|---|---|
Alt+Shift+P |
Turn the proxy on or off (the master switch). |
Alt+Shift+D |
Go direct without forgetting the configured mode. |
Chrome may report a shortcut as unassigned if another extension already owns it; you can always
rebind both on chrome://extensions/shortcuts.
| Permission | Why |
|---|---|
proxy |
Change the browser's proxy configuration — the entire point of the extension. |
storage |
Keep your servers, credentials and settings in chrome.storage.local. |
webRequest + webRequestAuthProvider |
Answer proxy 407 challenges with the saved credentials of the active server (Manifest V3 supports blocking listeners for onAuthRequired only), and read the size each request and response declares for the traffic meter. Observation only — no request is ever modified. |
contextMenus |
The right-click menu on the toolbar icon. |
notifications |
The system notification that names the server an automatic switch moved to. |
alarms |
The timer behind the periodic background check — set only while that option is on. |
<all_urls> |
Required to route traffic and to see proxy authentication challenges. |
The extension has no content scripts and injects nothing into pages.
Requirements: Node.js 22+ (only for the tests, the preview generator, the screenshots, packaging and the release audit — the extension itself has zero dependencies and no build step).
npm test # unit tests (state, proxy config, i18n coverage, manifest, zip writer)
npm run preview # regenerate the offline preview pages
npm run shots # retake docs/screenshots/*.png (needs the preview server running)
npm run package # build dist/proxy-switch-v<version>.zip for the Web Store
npm run releases # audit the published release pages (reads GH_TOKEN or GITHUB_TOKEN)npm run releases reads every release page and flags two kinds of drift: an archive that is not
the one proxy-switch-v<tag>.zip, and notes that still name a browser the project no longer
builds for. It changes nothing on its own and exits non-zero while anything is open, so it works
as a check. The same audit runs weekly in the Audit releases workflow (and on demand), so drift
is caught without anyone remembering to look. Adding -- --prune deletes the extra archives. -- --strip-notes drops only the
stale lines it can remove without cutting a sentence in half — because the notes wrap mid-line,
a mention inside a wrapped sentence is reported for a rewrite instead of being deleted — and
-- --from <dump.json> audits a saved releases dump with no token at all.
Project layout:
manifest.json MV3 manifest
icons/ 16/32/48/128 px icons
src/
background.js service worker: applies the proxy, badge, menus, auth
popup.html|js toolbar popup
options.html|js settings page
lib/
model.js state shape, validation, import/export (pure, tested)
theme.js theme, accent, spacing and type scale onto <html> (pure, tested)
failover.js auto-failover policy: strikes, rounds, cooldown (pure, tested)
notice.js the wording of the automatic-switch notification (pure, tested)
pac.js builds a PAC script from the domain list (pure, tested)
site-route.js the popup's "this site": host matching, effect, actions (pure, tested)
server-health.js recent per-server verdicts, and the chain order they imply (pure, tested)
server-probe.js the policy of the periodic background check (pure, tested)
proxy.js builds the chrome.proxy config + status text (pure, tested)
auth.js auto-auth policy: who may answer a proxy challenge (pure, tested)
health.js connection probe: timing and verdict (pure, tested)
health-ui.js the shared "Test connection" control
test-all-ui.js the shared "Test all servers" control (pure core, tested)
storage.js chrome.storage.local wrapper
i18n.js English/Persian dictionaries, RTL helpers (pure, tested)
mode-ui.js shared mode chips + PAC row + per-site servers
servers-ui.js shared server list + form
styles/ base design tokens, popup, options
__preview_*.html generated offline previews (not shipped)
tests/ node --test suites
tools/ preview generator + dependency-free zip packager
Design decisions worth knowing:
- One writer. The popup and settings page only write state; the service worker is the only code
that calls
chrome.proxy.settings.set, so there is a single place where the proxy is applied. - Credentials never sync. Everything lives in
chrome.storage.local, neverchrome.storage.sync. - Pure logic is separated from the Chrome APIs (
lib/model.js,lib/proxy.js,lib/i18n.js) so it can be unit tested in plain Node. - A verdict is only recorded where it means something. The chain order comes from the extension's own probes, and a probe is attributed to a server only in manual mode, where that server is the whole route. In PAC mode a chain hides which hop answered, so nothing is guessed — a server nobody has looked at simply keeps its place in the list.
- Theming is one file. Every colour, radius and shadow is a custom property in
src/styles/base.css— the light palette on:root, the dark one on:root[data-theme='dark'], one block per accent palette for each theme, all chosen bylib/theme.jsand applied asdata-theme/data-accenton<html>— so the themes, the accent palettes and the per-mode accents (emerald for a saved server, indigo for PAC, slate for the system proxy, amber when something is missing) can be re-tuned without touching any other stylesheet.
-
npm run package, then uploaddist/proxy-switch-v<version>.zipfrom the Chrome Web Store developer dashboard (a 128×128 icon is already included; screenshots can be taken from the preview pages). - Tag the release —
git tag v<version> && git push origin v<version>. The release workflow refuses a tag that does not matchmanifest.json, runs the tests and attaches the ZIP to the GitHub release. - Retake the screenshots (
npm run shots) if the interface changed — the version they show is the version the README claims.
- Import from common formats (
SwitchyOmegabackups).
Vulnerabilities go through the Security tab rather than the issue tracker — SECURITY.md says what counts as one here (a route that leaks direct, credentials that answer for the wrong server, state that outlives Delete everything) and what does not.

