A Chrome extension for inspecting, editing, saving, and replaying URL query parameters on the active tab. Built for developers and QA engineers.
Install from the Chrome Web Store
- Live parameter editor — parses the active tab's URL, shows each query param as an editable row, with one-click navigation to the edited URL.
- Type-aware inputs —
true/falsevalues render as a toggle switch next to the literal value (original casing preserved, soTRUEtoggles toFALSE); JSON objects and arrays get a drill-down editor; everything else is a monospace text field. - Filter parameters — live substring search over parameter keys, for URLs with dozens of params.
- Highlighted URL preview — full URL shown at the top with parameter keys emphasized and separators muted; editable directly.
- Structured JSON editing — drill into nested objects and arrays with a breadcrumb stack, or edit the raw JSON.
- Save & group — persist URLs into named groups (stored locally via
chrome.storage.local), reload them into the editor with one click. - Copy / Apply / Reset — copy the current URL, replace the active tab's URL, or snap back to the URL that was open when the popup first loaded.
- Accent color — Settings offers ten accent swatches that tint the logo, parameter badge, URL keys, toggles and primary buttons; "Reset to monochrome" returns to the base look. Persisted locally.
- Follows your system appearance — light and dark switch with the OS, live. There is no theme setting to get out of sync.
- Full keyboard navigation — every interactive element is reachable via Tab, with visible focus rings and sensible shortcuts.
- Screen-reader friendly — semantic HTML, a single ARIA live region (the toast doubles as it, so nothing is announced twice), role-correct toggle/switch/radiogroup/dialog semantics.
- Company-specific query filter rules — support for complex, configurable filters tailored to the needs of individual companies. The configuration delivery mechanism is still being explored (inline config, file import, or remote fetch). Currently available as a beta on the
DLS-betabranch — build it yourself to try it out.
| Shortcut | Action |
|---|---|
Enter in a row field |
Commit / advance focus |
Cmd/Ctrl + Enter |
Apply edited URL to the active tab |
Cmd/Ctrl + S |
Open the saved URLs drawer |
Esc |
Close the open panel / step one level out of an expanded JSON value |
Space on a BOOL toggle |
Flip the value |
- Manifest V3 (service worker,
action, minimal permissions) - React 18 with TypeScript (strict mode)
- Vite +
@crxjs/vite-pluginfor HMR during popup development - Zustand for state (small footprint, test-friendly)
- CSS Modules + CSS custom properties for theming, with an
oklchmonochrome token set insrc/styles/tokens.css - Vitest + React Testing Library (unit tests cover URL parsing, param typing, structured values, storage, and the store)
The extension requests only what it needs:
| Permission | Why |
|---|---|
activeTab |
Read and update the URL of the currently focused tab (granted per user gesture — no standing access to browsing history) |
storage |
Persist saved links, groups, and the accent color locally |
No <all_urls>, no clipboardWrite (popups are secure contexts and a user-gesture click is enough for navigator.clipboard.writeText).
- Node.js 20+
- npm 10+ (or pnpm / yarn if you prefer)
npm installnpm run devThis starts Vite with the @crxjs plugin, which rebuilds on save and writes the unpacked extension into dist/.
- Open
chrome://extensions. - Toggle Developer mode (top-right).
- Click Load unpacked and select the
dist/folder. - Click the QueryCraft icon in the toolbar on any
http/httpspage.
npm run buildThe production build lands in dist/, ready to be zipped and uploaded to the Chrome Web Store.
npm run build # runs `tsc --noEmit` before Vite
npm run lint # ESLint with react + jsx-a11y rules
npm run test # Vitest (jsdom, with chrome.* mocks preconfigured)Releases are automated via GitHub Actions and publish directly to the Chrome Web Store.
| Trigger | What happens |
|---|---|
PR merged into release branch |
Automatic deploy |
Manual workflow_dispatch |
Deploy with a chosen version bump |
The workflow bumps manifest.json version automatically before building:
| Type | Example |
|---|---|
patch (default) |
1.0.0 → 1.0.1 |
minor |
1.0.0 → 1.1.0 |
major |
1.0.0 → 2.0.0 |
The following secrets must be set in the repository settings:
| Secret | Description |
|---|---|
EXTENSION_ID |
Chrome Web Store extension ID |
CLIENT_ID |
Google OAuth client ID |
CLIENT_SECRET |
Google OAuth client secret |
REFRESH_TOKEN |
Google OAuth refresh token |
- Merge your feature branch into
main. - Open a PR from
main→release. - Merge the PR — the workflow bumps the version, builds, zips, uploads to the store, and tags the release automatically.
The UI follows a monochrome, Linear-inspired design (see src/styles/tokens.css).
| Aspect | Value |
|---|---|
| Popup size | 380 × 600 px |
| Palette | oklch with zero chroma — hierarchy from lightness, border alpha, and weight |
| Radii | 6–7px controls · 8px larger inputs · 10px cards · 20px pills |
| Type | Inter (UI) · JetBrains Mono (URLs and values) |
| Icons | Inline Feather/Lucide-style outlines: 24-unit viewBox, 2px stroke, round caps |
Three deliberate deviations from the design handoff:
- Height is 600px, not 640px. Chrome hard-caps extension popups at 800 × 600; a 640px panel is simply clipped. The layout is a flex column with a scrolling middle band, so it renders faithfully at 600.
- Reset does not open a
window.confirm()dialog. Modal JS dialogs are suppressed inside MV3 extension popups, so the confirmation would never appear and Reset would silently do nothing. The handoff lists this as a tweakable behavior; it is off. - Accent labels are not fixed to near-white. The handoff specifies
#FAFAFAtext on the accent-colored buttons, but every swatch in the palette fails WCAG AA that way — from 3.4:1 (Violet) down to 2.2:1 (Lime), against a 4.5:1 requirement for 13px text. The label color is therefore chosen per swatch by luminance, and accent-as-text (logo, badge, URL keys) is darkened or lightened just enough to clear 4.5:1 on the current panel. Solid fills keep the exact swatch the user picked, so a button always matches its swatch. Seesrc/lib/contrast.ts.
Fonts are referenced by family name and fall back to the system stack — the extension ships no webfonts, because the MV3 content security policy blocks remote font CDNs. To match the reference typography exactly, self-host the Inter and JetBrains Mono woff2 files under public/ and add an @font-face block.
- Interactive controls are ≥ 24×24 CSS px (WCAG 2.2 SC 2.5.8 minimum), with header and footer actions at 28px and larger.
- The reference design signals focus only by lightening an input border. That is kept, and a
:focus-visibleoutline is layered on top so keyboard users get a real indicator;outline: noneis never used without a replacement. - Every input has a
<label>(visible or.visually-hidden), never bare placeholders as labels. - The drawer uses
role="dialog",aria-modal="true", and a focus trap that restores focus to the trigger on close. While closed it isvisibility: hidden, which keeps it out of both the tab order and the accessibility tree. - Status changes (URL applied, copied, saved, reset, param added/removed) are announced via a single polite
aria-liveregion — the visible toast is that region, so nothing is announced twice. prefers-reduced-motionis honored globally — animations collapse to a near-instant transition.- Color is never the sole channel for status: the boolean switch always renders its literal value next to it, and destructive actions pair tone with an icon and label.
color-schemeis declared per theme so Chrome does not apply its own auto-dark-mode inversion to form controls, and native scrollbars and select popups match the theme.
chrome.*APIs are accessed only through adapters insrc/lib/storage.tsandsrc/lib/tabs.ts, so tests can swap a single module instead of the globalchromenamespace.- URL parsing lives in pure functions (
parseUrl,serializeUrl,isEditableUrl,createParam) with no React or Chrome dependencies. - Components accept behavior via props, not singletons, so they can be rendered in isolation with a test harness.
src/test/setup.tsships withchrome.storage.local,chrome.tabs,navigator.clipboard, andmatchMediamocks preconfigured.
MIT