How to create your first Chrome extension
Tags: javascript, webdev, tutorial, beginners
Published: 2020-11-19 18:04:39

---
title: How to create your first Chrome extension
published: true
date: 2020-11-19 18:04:00 UTC
tags: javascript, webdev, tutorial, beginners
canonical_url: https://freshman.tech/first-chrome-extension/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/cs9yroepkut706whv1yl.jpeg
description: In this article, you'll learn how Chrome extensions work and how to create your own for the first time
---

*I originally [posted this on my blog](https://freshman.tech/first-chrome-extension/) a month ago. If it's interesting to you, I post new tutorials on freshman.tech couple of times a month.*

Chrome extensions are programs that enhance the functions of the browser in some way. You probably already use an extension or two, maybe a password manager or some privacy addons. Have you ever wanted to create your own extensions but wondered how difficult the process would be? With the help of this tutorial, you will get a first-hand experience of just how easy it can be.

This article will walk you through the process of creating your first Chrome extension. We'll build an extension that replaces the new tab page in the browser with a random photo from [Unsplash](https://unsplash.com). It's a miniature version of my [Stellar Photos](https://chrome.google.com/webstore/detail/unsplash-instant/pejkokffkapolfffcgbmdmhdelanoaih?hl=en) extension which I built a few years ago when I first learned to build Chrome extensions. Here's a demonstration of how the finished extension will look like:

{% vimeo 481317330 %}

I've also included some tips for debugging Chrome extensions as well as links to resources where you can learn how to submit your extension to the Chrome web store. This will come in handy later on when you're making your own extensions. The complete code for this demo project can be found [here](https://github.com/Freshman-tech/freshtab/).

## Prerequisites

You need to have a basic knowledge of <abbr title="HyperText Markup Language">HTML</abbr>, <abbr title="Cascading Style Sheets">CSS</abbr>, JavaScript and the command-line to follow through with this tutorial. You also need the latest version of Chrome installed on your computer. I tested the code used for this project on Chrome 85 but it should keep working on any later version.

## Grab the starter files

The starter files for this tutorial are on [GitHub](https://github.com/freshman-tech/freshtab-starter-files). The repository includes all the markup and styles for the extension we’ll be building. You can run the command below in your terminal to clone the repository to your filesystem or [download the zip file](https://github.com/Freshman-tech/freshtab-starter-files/archive/master.zip) and extract it on your computer.

```
$ git clone https://github.com/Freshman-tech/freshtab-starter-files.git
```

Once the repository has been downloaded, `cd` into it in your terminal and use the `tree` command (if you have it installed on your computer) to inspect the directory structure.

```
$ cd freshtab-starter-files
$ tree
.
├── css
│   └── styles.css
├── demo.jpg
├── icons
│   ├── 128.png
│   ├── 16.png
│   ├── 32.png
│   ├── 48.png
│   └── 64.png
├── index.html
├── js
│   ├── background.js
│   ├── index.js
│   └── popup.js
├── LICENCE
├── manifest.json
├── popup.html
└── README.md
```

If you don't have the `tree` command, you can navigate to the directory in your file manager and inspect its contents that way.

## Anatomy of a Chrome extension

Chrome extensions are composed of different files depending on the nature of the extension. Usually, you'll see a manifest file, some icons, and several HTML, CSS, and JavaScript files which compose the different interfaces of the extension. Let's take a quick look at the files contained in the project directory to see what they all do.

### The manifest

This file (`manifest.json`) defines the structure of the extension, the permissions it needs, and other [details](https://developer.chrome.com/extensions/manifest) such as name, icons, description, supported browser versions, e.t.c.

### Background scripts

Background scripts are those that run in the background, listening for events and reacting to messages sent from other scripts that make up the extension. These scripts are defined in the manifest file. Our project has just one background script: the aptly named `background.js` file in the `js` folder.

### Popup window

A popup is the small window displayed when a user clicks the toolbar icon in the browser interface. It is an <abbr>HTML</abbr> file that can include other resources such as stylesheets and scripts, but inline scripts are not allowed.

![Lastpass Chrome extension popup](https://dev-to-uploads.s3.amazonaws.com/i/uf04h8twrmlbgee56etg.png)

To use a popup in your extension, you need to define it in the manifest first. The popup file for this extension is `popup.html` which links to the `popup.js` in the `js` folder.

### Override pages

Extensions can override browser pages such as the new tab page, history or bookmarks but only one at a time. All you need to do is specify an HTML file in the manifest and the page to be replaced (`newtab`, `bookmarks`, or `history`). In this case, the `index.html` file will override the new tab page.

### Extension icons

It's necessary to include at least one icon in the extension manifest to represent it otherwise a generic one will be used instead. The icons for our extension are in the `icons` directory.

### Content scripts

Content scripts are those that will be executed in web pages loaded in your browser. They have full access to the <abbr title="Document Object Model">DOM</abbr> and can communicate with other parts of the extension through the <a href="https://developer.chrome.com/extensions/messaging">messaging <abbr title="Application Programming Interface">API</abbr></a>. We don't need a content script for this particular project, but extensions that need to modify the <abbr>DOM</abbr> of other web pages do.

## Update the manifest file

Let's start building the Chrome extension by defining the required fields in the `manifest.json` file. Open up this file in your text editor and update it with the following code:

```json
{
  "manifest_version": 2,
  "name": "freshtab",
  "version": "1.0.0",
  "description": "Experience a beautiful photo from Unsplash every time you open a new tab.",
  "icons": {
    "16": "icons/16.png",
    "32": "icons/32.png",
    "48": "icons/48.png",
    "64": "icons/64.png",
    "128": "icons/128.png"
  },
  "chrome_url_overrides": {
    "newtab": "index.html"
  },
  "browser_action": {
    "default_popup": "popup.html"
  },
  "permissions": ["storage", "unlimitedStorage"],
  "background": {
    "scripts": ["js/background.js"],
    "persistent": false
  },
  "minimum_chrome_version": "60"
}
```

Here's a breakdown of each field in the manifest file:

### Required fields
- `manifest_version`: this key specifies the version of the `manifest.json` used by this extension. Currently, this must always be 2.
- `name`: the extension name.
- `version`: the extension version.

### Optional but recommended fields
- `description`: the extension description.
- `icons`: this specifies icons for your extension in different sizes.

### Optional
- `chrome_url_overrides`: used to provide a custom replacement for browser pages.
In this case,the new tab page is being replaced with the `index.html` file.
- `browser_action`: used to define settings for the button that the extension adds to the browser toolbar, including a popup file if any.
- `permissions`: used to define the [permissions](https://developer.chrome.com/extensions/declare_permissions) required by the extension. We need the `storage` permission to access the [Chrome storage API](https://developer.chrome.com/extensions/storage), and `unlimitedStorage` to get an unlimited quota for storing client side data (instead of the default 5MB).
- `background`: used to register background scripts. Setting the `persistent` key to `false` keeps the script from being retained in memory when not in use.
- `minimum_chrome_version`: The minimum version required by your extension. Users on Chrome versions earlier than the specified one will be unable to install the extension.

## Load the extension in Chrome

Open up your Chrome browser and enter `chrome://extensions` in the address bar. Ensure **Developer mode** is enabled, then click the **Load unpacked** button and select the extension directory. Once the extension is loaded, it will appear in the first position on the page.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/399vtjbrubxtyleswohv.png)

At this point, the browser's new tab page will be replaced by the one defined in our extension manifest (`index.html`). Try it out by opening a new tab. You should see a blank page as shown in the screenshot below:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dylbk5d43p2ym8wl075x.png)

## Get your Unsplash access key

Before you can use the Unsplash API, you need to create a free
account on their website first. Follow the instructions on [this
page](https://unsplash.com/documentation#creating-a-developer-account) to do so, and [register a new application](https://unsplash.com/documentation#registering-your-application).
Once your app is created, take note of the access key string in the application settings page.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ikca5y0kd6impimk5cva.png)

## Fetch the background image

The first step is to fetch a random image from Unsplash. An <abbr>API</abbr> [endpoint](https://unsplash.com/documentation#get-a-random-photo) exists for this purpose:

```
https://api.unsplash.com/photos/random
```

This endpoint accepts a number of [query parameters](https://unsplash.com/documentation#parameters-10) for the purpose of narrowing the pool of photos from which a random one will be chosen. For example, we can use the `orientation` parameter to limit the results to landscape images only.

```
https://api.unsplash.com/photos/random?orientation=landscape
```

Let's use the `fetch` <abbr>API</abbr> to retrieve a single random photo from Unsplash. Add the following code to your `js/background.js` file:

```javascript
// Replace <your unsplash access key> with the Access Key retrieved
// in the previous step.
const UNSPLASH_ACCESS_KEY = '<your unsplash access key>';

function validateResponse(response) {
  if (!response.ok) {
    throw Error(response.statusText);
  }

  return response;
}

async function getRandomPhoto() {
  const endpoint = 'https://api.unsplash.com/photos/random?orientation=landscape';

  // Creates a new Headers object.
  const headers = new Headers();
  // Set the HTTP Authorization header
  headers.append('Authorization', `Client-ID ${UNSPLASH_ACCESS_KEY}`);

  let response = await fetch(endpoint, { headers });
  const json = await validateResponse(response).json();

  return json;
}

async function nextImage() {
  try {
    const image = await getRandomPhoto();
    console.log(image);
  } catch (err) {
    console.log(err);
  }
}

// Execute the `nextImage` function when the extension is installed
chrome.runtime.onInstalled.addListener(nextImage);
```

The `/photos/random` endpoint requires [authentication](https://unsplash.com/documentation#public-authentication) via
the <abbr title="HyperText Transfer protocol">HTTP</abbr> Authorization header. This is done by setting the `Authorization` header to `Client-ID ACCESS_KEY` where `ACCESS_KEY` is your application's Access Key. Without this header, the request will result in a 401 Unauthorized response.

Once this request is made and a response is received, the `validateResponse()` function is executed to check if the response has a status code of 200 OK. If so, the response is read as <abbr title="JavaScript Object Notation">JSON</abbr>, and automatically wraps it in a resolved promise. Otherwise, an error is thrown and `getRandomPhoto()` photo rejects with an error.

You can try this out by reloading the extension on the `chrome://extensions` page after saving the file, then click the **background page** link to inspect the console for the script.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4i9jz9mthlcllc4lwl7i.png)

**Note**: Make sure you always reload the extension after making a change to the `background.js` file so that the new changes are effected.

You should see the <abbr>JSON</abbr> object received from Unsplash in the console. This object contains a lot of info about the image including its width and height, number of downloads, photographer information, download links, e.t.c.

We need to save this object in the Chrome storage and use it to set the background image whenever a new tab is opened. Let's tackle that in the next step.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/bhwzlr331syfke9cvvc2.png)

## Save the image object locally

We cannot set the background image on our new tab pages directly from the `background.js`, but we need a way to access the Unsplash object from the new tab pages.

One way to share data between a background script and the other scripts that make up the extension is to save the data to a location which is accessible to all the scripts in the extension. We can use the browser <a href="https://developer.mozilla.org/en/docs/Web/API/Window/localStorage">localStorage <abbr>API</abbr></a> or [chrome.storage](https://developer.chrome.com/extensions/storage) which is specific to Chrome extensions. We'll opt for the latter in this tutorial.

Modify the `nextImage()` function in your `background.js` file as shown below:

```javascript
async function nextImage() {
  try {
    const image = await getRandomPhoto();
    // Save the `image` object to chrome's local storage area
    // under the `nextImage` key
    chrome.storage.local.set({ nextImage: image });
  } catch (err) {
    console.log(err);
  }
}
```

To store data for your extension, you can use either `chrome.storage.sync` or `chrome.storage.local`. The former should be used if you want the data to be synced any Chrome browser that the user is logged into, provided the user has sync enabled. We don't need to sync the data here so the latter option is more appropriate here.

## Set the background image on each new tab page

Now that the Unsplash object is being saved to the extension's local storage, we can access it from the new tab page. Update your `js/index.js` file as shown below:

```javascript
function setImage(image) {
  document.body.setAttribute(
    'style',
    `background-image: url(${image.urls.full});`
  );
}

document.addEventListener('DOMContentLoaded', () => {
  // Retrieve the next image object
  chrome.storage.local.get('nextImage', data => {
    if (data.nextImage) {
      setImage(data.nextImage);
    }
  });
});
```

Once the <abbr>DOM</abbr> is loaded and parsed, the data stored in the `nextImage` key is retrieved from the Chrome's local storage compartment for extensions. If this data exists, the `setImage()` function is executed with the `nextImage` object as it's sole argument. This function sets the `background-image` style on the `<body>` element to the <abbr>URL</abbr> in the `image.urls.full` property.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9orlq97p1pdzbu8s62j6.png)

At this point, opening a new tab will load a background image on the screen but the image loads slowly at first because it is being fetch freshly from the server when the tab is opened. This problem can be solved by saving the image data itself to the local storage instead of a link to the image. This will cause the background image to load instantly when a new tab is opened, because it will
be fetched from the local storage not the Unsplash servers. To save image data to local storage, we need to encode it to
[Base64](https://en.wikipedia.org/wiki/Base64) format. For example, here's the Base64 encoding of [this image](https://images.unsplash.com/photo-1476725994324-6f6833ea0631?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1000&q=85):

{% gist https://gist.github.com/ayoisaiah/f3e43b496019c5866e6e2c1064b403fb %}

Encoding an image into the Base64 format produces a string that represents entire image data. You can test this by pasting the Base64 string in your browser's address bar. It should load the image represented by the string as demonstrated below:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/t7rdkcmplewugbi8ygun.png)

What we need to do next is convert each image received from the Unsplash <abbr>API</abbr> to a Base64 string and attach it to the image object before storing it in the local storage. Once a new tab is opened, the Base64 string will be retrieved and used in the `background-image` property instead of the image <abbr>URL</abbr>.

To convert an image to a Base64 string, we need to retrieve the image data first. Here's how:

```javascript
async function getRandomPhoto() {
  let endpoint = 'https://api.unsplash.com/photos/random?orientation=landscape';

  const headers = new Headers();
  headers.append('Authorization', `Client-ID ${UNSPLASH_ACCESS_KEY}`);

  let response = await fetch(endpoint, { headers });
  const json = await validateResponse(response).json();
  // Fetch the raw image data. The query parameters are used to control the size
  // and quality of the image:
  // q - compression quality
  // w - image width
  // See all the suported parameters: https://unsplash.com/documentation#supported-parameters
  response = await fetch(json.urls.raw + '&q=85&w=2000');
  // Verify the status of the response (must be 200 OK)
  // and read a Blob object out of the response.
  // This object is used to represent binary data and
  // is stored in a new `blob` property on the `json` object.
  json.blob = await validateResponse(response).blob();

  return json;
}
```

The `raw` <abbr>URL</abbr> consists of a base image <abbr>URL</abbr> which we can add additional [image
parameters](https://unsplash.com/documentation#supported-parameters) to control the size, quality and format of the image. The query parameters `&q=85&w=2000` will produce an image with a width of 2000px and 85% quality compared to the original. This should represent a good enough quality for most screen sizes.

To read the image data from the response, the `blob()` method is used. This returns a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) object that represents the image data. This object is then set on a new `blob` property on the `json` object. The next step is to encode the blob object into a Base64 string so that it may be saved to local storage. Modify the `nextImage()`
function in your `background.js` file as shown below:

```javascript
async function nextImage() {
  try {
    const image = await getRandomPhoto();

    // the FileReader object lets you read the contents of
    // files or raw data buffers. A blob object is a data buffer
    const fileReader = new FileReader();
    // The readAsDataURL method is used to read
    // the contents of the specified blob object
    // Once finished, the binary data is converted to
    // a Base64 string
    fileReader.readAsDataURL(image.blob);
    // The `load` event is fired when a read
    // has completed successfully. The result
    // can be found in `event.target.result`
    fileReader.addEventListener('load', event => {
      // The `result` property is the Base64 string
      const { result } = event.target;
      // This string is stored on a `base64` property
      // in the image object
      image.base64 = result;
      // The image object is subsequently stored in
      // the browser's local storage as before
      chrome.storage.local.set({ nextImage: image });
    });
  } catch (err) {
    console.log(err);
  }
}
```

The `FileReader` <abbr>API</abbr> is how we convert the image blob to a Base64 encoded string. The `readAsDataURL()` method reads the contents of the `image.blob` property. When the read is completed, the `load` event is fired and the result of the operation can be accessed under `event.target.result` as shown above. This `result` property is a Base64 encoded string which is then stored on the `image` object in a new `base64` property and the entire object is
subsequently stored in Chrome's local storage area for extensions.

**Note**: Make sure to reload the extension after saving your `background.js` file

The next step is to update the value of the background style used to set the body background in `setImage` function. Replace `image.urls.full` with `image.base64` as shown below:

```javascript
function setImage(image) {
  document.body.setAttribute(
    'style',
    `background-image: url(${image.base64});`
  );
}
```

If you open a new tab, you will observe that the background image loads instantly. This is because the image is being retrieved from the local storage in its Base64 string form instead of being freshly loaded from Unsplash servers like we were doing earlier.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gtyzpvtjop103nrh46gh.png)

## Load new images on each tab

At the moment, the `nextImage()` function is invoked only when the extension is first installed or reloaded. This means that the only way to cause a new image to load is to reload the extension in the extensions page. In this section, we'll figure out a way to invoke `nextImage()` each time a new tab is opened so that a new image is fetched in the background to replace the previous one without having to reload the extension each time.

```javascript
// This line is what causes the nextImage() function to be
// executed when the extension is freshly installed or reloaded.
chrome.runtime.onInstalled.addListener(nextImage);
```

The `background.js` script is not aware of when a new tab is open, but this `index.js` script is because it is a part of the custom new tab page. To [communicate](https://developer.chrome.com/extensions/messaging) between the two scripts we need to send a message from one script and listen for the message in another script.

We will use the `chrome.runtime.sendMessage` and `chrome.runtime.onMessage` functions to add communication between the background script and new tab script. The former will be used in our `index.js` file to notify the background script that a new image should be fetched in the background. Modify your `index.js` file as shown below:

```javascript
document.addEventListener('DOMContentLoaded', () => {
  chrome.storage.local.get('nextImage', (data) => {
    if (data.nextImage) {
      setImage(data.nextImage);
    }
  });
   
  // Add the line below
  chrome.runtime.sendMessage({ command: 'next-image' });
});
```

Each time a new tab page loads, a message will be sent with the message object shown above. This message object can be any valid <abbr>JSON</abbr> object. You can also add an optional callback function as a second argument to `sendMessage()` if you need to handle a response from the other end but we don't need that here.

The next step is to use the `chrome.runtime.onMessage` method in our background script to listen for message events and react appropriately when triggered. Add the code to the bottom of your `background.js` file:

```javascript
chrome.runtime.onInstalled.addListener(nextImage);

// Add the lines below
chrome.runtime.onMessage.addListener((request) => {
  if (request.command === 'next-image') {
    nextImage();
  }
});
```

The `onMessage` function is used to register a listener that listens for messages sent by `chrome.runtime.sendMessage`. The `addListener` method takes a single callback function which can take up to three parameters:

- `request`: The message object from the sender
- `sender`: The sender of the request
- `sendResponse`: A function to call if you want to respond to the sender

We are not using `sender` or `sendResponse` in this case so I've left it out of the callback function. In the body of the function, an `if` statement is used to check the message object. If it corresponds to the message object from the new tab script, the `nextImage()` function is executed, causing a new image to replace the previous one.

Reload the extension and open a few new tab pages. You should see a new background image in the tabs each time. If you see the same image multiple times, it could be due to any of the two reasons below:

- The next image is still loading in the background. The speed at which a new image can be retrieved and saved is mostly limited by your internet connection.
- The same image is returned consecutively from Unsplash. Since images are fetched at random, there is no guarantee that a different image will be received each time. However, the pool of images from which a random one is selected is so large (except you restrict it to specific Unsplash collections) that this is unlikely to happen often.

{% vimeo 481317330 %}

## Restrict images to user defined collections

At the moment, the pool of images from which a random one is selected is only limited by orientation according to the value of the `endpoint` variable in `getRandomPhoto()`:

```
https://api.unsplash.com/photos/random?orientation=landscape
```

We can use any of the other available [query parameters](https://unsplash.com/documentation#parameters-10) to further limit the pool of images. For example, we can filter images by collection:

```
https://api.unsplash.com/photos/random?orientation=landscape&collection=998309,317099
```

You can retrieve a collection <abbr>ID</abbr> by heading to the [collections page](https://unsplash.com/collections) and selecting the <abbr>ID</abbr> from any collection <abbr>URL</abbr> as shown below:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/98di0dxh4yql5tdhb3e7.png)

Let's add the ability for a user to optionally restrict the pool of images to those from a specific collection. We'll create a way to do that through thepopup window which is a common way through with basic extension settings are configured. Here's how the popup window is setup at the moment:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/5og51l06du6z45oglnjd.png)

If you don't see the extension icon in the top bar, make sure the icon is pinned as demonstrated in the screenshot below:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wkxcmvjgogjd9gvrq4tp.png)

The popup window has a single input where a user may enter one or more collection <abbr>ID</abbr>s. The markup for this window is in the `popup.html` file if you want to inspect it. Our first task is to validate and save any custom collection <abbr>IDs</abbr> to the local storage. Open up the `js/popup.js` file in your text editor, and populate its contents with the following code:

```javascript
const input = document.getElementById('js-collections');
const form = document.getElementById('js-form');
const message = document.getElementById('js-message');

const UNSPLASH_ACCESS_KEY = '<your unsplash access key>';

async function saveCollections(event) {
  event.preventDefault();
  const value = input.value.trim();
  if (!value) return;

  try {
    // split the string into an array of collection IDs
    const collections = value.split(',');
    for (let i = 0; i < collections.length; i++) {
      const result = Number.parseInt(collections[i], 10);
      // Ensure each collection ID is a number
      if (Number.isNaN(result)) {
        throw Error(`${collections[i]} is not a number`);
      }

      message.textContent = 'Loading...';
      const headers = new Headers();
      headers.append('Authorization', `Client-ID ${UNSPLASH_ACCESS_KEY}`);

      // Verify that the collection exists
      const response = await fetch(
        `https://api.unsplash.com/collections/${result}`,
        { headers }
      );

      if (!response.ok) {
        throw Error(`Collection not found: ${result}`);
      }
    }

    // Save the collecion to local storage
    chrome.storage.local.set(
      {
        collections: value,
      },
      () => {
        message.setAttribute('class', 'success');
        message.textContent = 'Collections saved successfully!';
      }
    );
  } catch (err) {
    message.setAttribute('class', 'error');
    message.textContent = err;
  }
}

form.addEventListener('submit', saveCollections);

document.addEventListener('DOMContentLoaded', () => {
  // Retrieve collecion IDs from the local storage (if present)
  // and set them as the value of the input
  chrome.storage.local.get('collections', (result) => {
    const collections = result.collections || '';
    input.value = collections;
  });
});
```

Although it's a sizeable chunk of code, it's nothing you haven't seen before. When the <kbd>Enter</kbd> key is pressed on the input, the form is submitted and `saveCollections()` is executed. In this function, the collection <abbr>ID</abbr>s are processed and eventually saved to chrome's local storage for extensions. Don't forget to replace the `<your unsplash access key>` placeholder with your actual access key.

{% vimeo 481334271 %}

The next step is to use any saved collection <abbr>ID</abbr>s in the request for a random image. Open up your `background.js` file and update it as shown below:

```javascript
function getCollections() {
  return new Promise((resolve) => {
    chrome.storage.local.get('collections', (result) => {
      const collections = result.collections || '';
      resolve(collections);
    });
  });
}

async function getRandomPhoto() {
  const collections = await getCollections();

  let endpoint = 'https://api.unsplash.com/photos/random?orientation=landscape';

  if (collections) {
    endpoint += `&collections=${collections}`;
  }

  const headers = new Headers();
  headers.append('Authorization', `Client-ID ${UNSPLASH_ACCESS_KEY}`);

  let response = await fetch(endpoint, { headers });
  const json = await validateResponse(response).json();
  response = await fetch(json.urls.raw + '&q=85&w=2000');
  json.blob = await validateResponse(response).blob();

  return json;
}
```

The `getCollections()` function retrieves any saved collection <abbr>ID</abbr>s. If any one has been specified by the user, it is appended to the endpoint via the `&collections` query parameter. That way, the random image will be fetched from the specified collections instead of the entire Unsplash catalogue.

## Tips for debugging

Chrome extensions use the same debugging workflow as regular web pages, but they have some unique properties you need to be aware of. To debug your background script, head to the chrome extensions page at `chrome://extensions` and ensure **Developer mode** is enabled. Next, find your extension and click **background page** under inspect views. This will open a DevTools window for debugging purposes.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0ibeeq17pcd31d03hjtg.png)

Debugging a popup window can be done by right clicking on the popup icon and then clicking **Inspect popup**. This will launch a DevTools window for your popup. For the new tab page (or other override pages), debug them like you would do a regular webapage (using <kbd>Ctrl+Shift+I</kbd> to launch the DevTools panel).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/15ihf8uhyg1lyl57lau1.png)

During development, you may see an **Errors** button next to **Details** and **Remove** on your extension entry. This indicates that an error occurred somewhere in your extension code. Click this button to find out the exact line in your code where the error occurred.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ggw6j952fmg0te3repdf.png)

## Publishing your extension

Follow the steps detailed in [this guide](https://developer.chrome.com/webstore/publish) to publish your extension to the [Chrome web store](https://chrome.google.com/webstore/category/extensions). A Google account is required.

## Conclusion

Congratulations, you've successfully built your first Chrome extension. I hope you had fun building it! Feel free to leave a comment below if you have any questions or suggestions. If you want to see a more fully-fledged implementation of this particular type of Chrome extension, checkout [Stellar Photos](https://github.com/ayoisaiah/stellar-photos) on GitHub.

Thanks for reading, and happy coding!