Skip to main content
You will build an Omnibox (address bar) shortcut. Type gh in the address bar, enter a query, and land on GitHub search results. Along the way you will wire a manifest.json and handle input in a background service worker. You will also practice the dev loop (createdevbuild) that every project follows.

What you will build

The plan

Make GitHub search as fast as a native browser shortcut. The extension reserves the keyword gh; after you type gh and a query, it opens GitHub search results.

Step 1: create the extension

Use the Extension.js create command to scaffold an extension named github-search. The default template is a working sidebar starter, so the scaffold already contains src/manifest.json and src/background.ts. The next two steps replace those two files. Everything you write in this tutorial goes in src/, because Extension.js prefers src/manifest.json when it is present.
Using Yarn? The yarn dlx command requires Yarn 2 or later. Yarn 1 does not include dlx and fails with a “Command not found” error. On Yarn 1, use the npm tab (npx) instead.
Using Deno? Deno caches npm packages aggressively, including the @latest tag, so npm:extension@latest can keep resolving to an older cached release. If a scaffold or dev build fails with an error that a newer version already fixed (for example manifest.json references files that were not emitted to disk), refresh the cache, or pin an exact release in place of @latest:
After deno install, confirm the resolved version (extension should match the latest release) before running deno task dev.

What create generates

create scaffolds a complete project and initializes a git repository. The default template produces this tree:
The TypeScript and React templates add tsconfig.json and extension-env.d.ts for typed extension APIs. Two files anchor the layout:
  • package.json marks the project root. Special folders (pages/, scripts/, public/) and the dist/ output resolve from that directory, never from src/.
  • manifest.json marks the extension source. It can sit at the root or in src/. When both exist, src/manifest.json wins.
What to edit and what to delete when you adapt the scaffold:

Step 2: create the manifest file

Every extension starts with a manifest file. It defines metadata, permissions, and runtime files. Based on the plan above, set the gh shortcut and add a service worker for user events. Open the src/manifest.json the scaffold created and replace its contents with:
  • omnibox.keyword: When you type gh, the browser fires an event.
  • background.service_worker: Listens to the event you triggered.

Step 3: create the background service worker

In browser extensions, the background service worker (a script that runs independently of any visible page) handles browser events. For this example, add a script that listens to Omnibox input and routes the query to GitHub search. Create src/service_worker.js, and delete the src/background.ts the scaffold shipped so nothing else claims the background entry:
The script above opens a new tab with GitHub search results whenever you type something after “gh” in the address bar.

Step 4: load your extension

Move into the project and install its dependencies. create scaffolds the project but does not install for you, and the dev script runs the local extension binary:
Your package.json file now looks like this, with extension pinned to the release you scaffolded with:
These scripts are the default Extension.js commands. Run the extension for the first time:
First run. extension dev targets chromium by default and runs your extension in a version-pinned Chrome for Testing with an isolated profile, so it never touches the browser you use every day. When that browser is not on the machine yet, the first run asks to download it once and continues into the dev session as soon as it lands.Answer n and nothing is downloaded. Run npx extension install chromium whenever you want it, reach a browser you already have with --browser=edge or --browser=brave, pin any binary with --chromium-binary <path>, or start the dev server alone with --no-browser. A non-interactive shell, CI included, is never asked: it prints the install command and stops. See why Extension.js downloads a browser and skip the managed download.
If your setup is correct, Extension.js launches Chrome with a fresh profile, loads github-search as an unpacked extension, and prints a ready banner in your terminal. The Chrome address bar now recognizes gh as a keyword. Type gh followed by a space, enter extension.js, press Enter. A new tab opens to https://github.com/search?q=extension.js&type=issues. You now have a working browser extension that searches on GitHub.

Step 5: make it better

Improve the search experience by adding suggestions directly in the address bar with an Omnibox input listener. Update service_worker.js to fetch GitHub suggestions and display them while typing.
service_worker.js
This code adds live GitHub suggestions directly in the address bar. You now have a working GitHub search extension. Iterate on it and adapt it to your own workflow.

Next steps