Create Your First Extension

Let's learn together how to create an extension from scratch using Extension.js.

Take a common task for some developers: searching on GitHub.

The problem: I want to search on GitHub in the most convenient way. Imagine searching GitHub projects directly from your browser's URL bar.

The solution? Meet github_search, a tool that makes this possible.

howiwant.png

The Plan

Our goal is to make searching GitHub projects as easy as searching on Google. To avoid irrelevant searches when the user decides to search for something else, let's reserve a keyword for our extension: if the user types "gh," followed by a tab click, it will activate our extension to trigger the search.

search.png

The interface that we are creating here.

Step 1 - Create the extension

Let's use the Extension.js create command to bootstrap a minimal extension for us. Let's call it: github-search

npm
pnpm
yarn
npx extension@latest create github-search --template=init

Step 2 - Create the manifest file

Step 2 Demo

Every extension starts with a manifest file. It tells the browser information about the extension's metadata, permissions, and files that the extension needs to run properly. Based on the plan above, we want a custom search shortcut "gh" that will link us to GitHub search. We are also adding a service worker script to handle user events logic.

{
  "manifest_version": 3,
  "name": "GitHub Search",
  "version": "1.0",
  "omnibox": { "keyword": "gh" },
  "background": {
    "service_worker": "service_worker.js"
  }
}
  • omnibox.keyword: When the keyword gh is set, an event will be fired.
  • background.service_worker: Will listen to the event that we just fired.

Step 3 - Create the Background Service Worker

In the context of a browser extension, the background service worker is where the extension can set listeners for various browser events.

In our case, we need to add a script that listens to input events in the omnibox, so once the user types what they want to search, GitHub can return the correct data.

Let's create a service_worker.js file for this purpose:

When the user has accepted what is typed into the omnibox.
chrome.omnibox.onInputEntered.addListener((text) => {
  Convert any special character (spaces, &, ?, etc)
  into a valid character for the URL format.
  const encodedSearchText = encodeURIComponent(text);
  const url = `https://github.com/search?q=${encodedSearchText}&type=issues`;

  chrome.tabs.create({ url });
});

The script above will open a new tab with GitHub search results whenever you enter something after "gh" in the URL bar.

Step 4 - Load Your Extension

If you take a look at your package.json file now, it looks more or less like this:

{
  "scripts": {
    "dev": "extension@latest dev",
    "start": "extension start",
    "build": "extension@latest build"
  },
  "devDependencies": {
    "extension": "latest"
  }
}

These scripts are the main scripts used by Extension.js for development mode. To preview your extension, let's run it for the first time.

npm
pnpm
yarn
npm run dev

If all goes well, you should see a screen like the following:

That's it! You created your first browser extension that searches on GitHub!

Step 5 - Making It Better

To improve our search results, let's add search suggestions directly in the URL bar by listening to input changes in the omnibox.

Update the service_worker.js file to fetch suggestions from GitHub and display them as you type.

service_worker.js

Create a debounce function to avoid excessive
calls to the GitHub API while the user is still
typing the search query.
function debounce(fn, delay) {
  let timeoutID;
  return function (...args) {
    if (timeoutID) clearTimeout(timeoutID);
    timeoutID = setTimeout(() => fn(...args), delay);
  };
}

When the user has changed what is typed into the omnibox.
chrome.omnibox.onInputChanged.addListener(
  debounce(async (text, suggest) => {
    const response = await fetch(
      `https://api.github.com/search/issues?q=${text}`,
    );
    const data = await response.json();
    const suggestions = data.items.map((issue) => ({
      content: issue.html_url,
      description: issue.title,
    }));

    suggest(suggestions);
  }, 250),
);

When the user has accepted what is typed into the omnibox.
chrome.omnibox.onInputEntered.addListener((text) => {
  Convert any special character (spaces, &, ?, etc)
  into a valid character for the URL format.
  const encodedSearchText = encodeURIComponent(text);
  const url = `https://github.com/search?q=${encodedSearchText}&type=issues`;

  chrome.tabs.create({ url });
});

Adding this code will let you see live search suggestions from GitHub right in your URL bar, making the search experience even smoother.

Updated result

You are done!

Conclusion

Congratulations! You've built a GitHub search extension. Experiment with it, improve it, and make it your own. This is just the beginning of what you can do with browser extensions.

Next Steps