Create a Google Chrome Extension Part 1: Image Grabber
Tags: webdev, javascript, tutorial, productivity
Published: 2022-02-06 23:04:44

# Table Of Contents

[Introduction](#introduction)
[Basic extension structure](#basic-extension-structure)
&nbsp;&nbsp;&nbsp;&nbsp;[Minimal Chrome extension](#minimal-chrome-extension)
[Install Chrome Extension](#install-chrome-extension)
[Add extension icons](#add-extension-icons)
[Create the extension interface](#create-the-extension-interface)
[Implement the "GRAB NOW" function](#implement-the-grab-now-function)
&nbsp;&nbsp;&nbsp;&nbsp;[Obtain required permissions](#obtain-required-permissions)
&nbsp;&nbsp;&nbsp;&nbsp;[Get information about the active browser tab](#get-information-about-the-active-browser-tab)
&nbsp;&nbsp;&nbsp;&nbsp;[Grab images from the current page](#grab-images-from-the-current-page)
&nbsp;&nbsp;&nbsp;&nbsp;[Code cleanup](#code-cleanup)
[Conclusion](#conclusion)


## Introduction

Chrome Extensions are small programs, that can be installed to Google Chrome web browser to enrich its features. Usually, to install a Chrome Extension, a user should open [Chrome Web Store](https://chrome.google.com/webstore), find the required extension, and install it from there. 

In this article, I will show, how to create a Chrome Extension from scratch. The extension which we will create today will use Chrome APIs to get access to the content of web pages, which opened in a web browser and extract different information from them. Using these APIs you can not only read content from web pages but also write content to them and interact with these pages, like, for example, automatically pressing buttons or following links. This feature can be used for a wide range of browser automation tasks like scrapping required information from websites or automating web surfing, which can be useful to automate user interface testing.

In this article, I will guide you through the process of building an extension named `Image Grabber`. The resulting extension will provide an interface to connect to a website, read all images from it, grab their absolute URLs and copy these URLs to a clipboard. During this process, you will know about foundational parts of the Google Chrome extension that can be reused to build an extension of any kind.

By the end of this article, you will build an extension that looks and works as shown in this video.

{% youtube whdKJ7o46n0 %}

This is only the first part of this tutorial. In the second part I will show how to extend the interface of the extension to select and download grabbed images as a ZIP archive and then explain how to publish the extension to Google Chrome WebStore. This is a link to the second part: https://dev.to/andreygermanov/create-a-google-chrome-extension-part-2-image-grabber-47h7.

## Basic extension structure

Google Chrome Extension is a web application, that can contain any number of HTML pages, CSS stylesheets, JavaScript files, images, any other files and a `manifest.json` file in the same folder, which defines how this particular extension will look and work.

### Minimal Chrome extension

Minimal Chrome Extension consists only of the `manifest.json` file. This is an example of a basic `manifest.json` file that can be used as a template when start creating any new Chrome extension:

```json
{
    "name": "Image Grabber",
    "description": "Extract all images from current web page",
    "version": "1.0",
    "manifest_version": 3,
    "icons": {},
    "action": {},
    "permissions": [],
    "background":{}
}
```
The only required parameters are `name`, `description`, `version` and `manifest_version`. `manifest_version` should be equal to 3. Values of other parameters are up to you, they should clearly describe your extension and its version. In this example, I described the `Image Grabber` extension, which will extract links of all images from a current browser page.  

You can see a full list of options that can be specified in the `manifest.json` file in the [official documentation](https://developer.chrome.com/docs/extensions/mv3/manifest/).

A folder with a single `manifest.json` file is a minimal runnable Chrome Extension that can be packaged, installed to Chrome, and distributed. This minimal extension will have a default look and will do nothing until we define other parameters: `icons`, `action`, `permissions`, and `background`.

So, let's create the `image_grabber` folder and put the `manifest.json` file with that default content. Then, let's just install this extension to Chrome.

## Install Chrome Extension

When you develop an extension, it has a form of a folder with files. In Chrome extensions terms it is called `unpacked extension`. After you finish development, you will need to `pack` the extension folder to an archive with a `.crx` extension using the Chrome extensions manager. This archive then can be used to upload to `Chrome Web Store` from which users can install your extension to their browsers.

To test and debug extension during development, you can install `unpacked extension` to Chrome. To do this, type `chrome://extensions` in the browser's URL string to open the Chrome Extensions Manager.

![Extensions list](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztx17ghck8sy8jr7ufvu.png)

To install and debug extensions during development turn on the `Developer mode` switch on the right side of the Extensions panel. It will show extensions management panel:

![Extensions list in dev mode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9yoi0x88s5xi0sctznie.png)

Then press the `Load unpacked` button and select a folder with the extension. Point it to our minimal `image_grabber` extension. Right after this, a panel for the Image Grabber extension will appear in a list of installed extensions:

![Installed extension](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28vfa7fr30zuoeoqz7fa.png)

The `Image Grabber` extension panel shows a unique ID, description, and version of the extension. Every time when changing the `manifest.json` file, you need to press the `Reload` icon on the extension panel to reload the updated extension:

![Reload extension](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6ye68jlbxurjmq1ftrj.png)

To use the extension in the browser, you can find it in a list of Chrome installed extensions. To see this list, press the `Extensions` icon button

![Extensions button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mz1q7uxcv6xmbkpiuwf1.png) 

on the right side of the Chrome URL bar and find the 'Image Grabber' item in the dropdown list. You can also press the "Pin" icon button at the right side of the extension to place an icon of the extension to the browser toolbar on the same line with other common extensions:

![Extensions List](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0w4saw7bu47i7gg7ge3e.png)

After `Pin` the extension, its default icon will appear on the toolbar:

![Pinned extension](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wm3lb70d5duzbs3rrsae.png)

That's all. We installed the minimal working Chrome extension. However, it looks like a simple "I" symbol on a gray background and nothing happens when you click on it. Let's add other missing parts to the `manifest.json` to change this.

## Add extension icons

The `icons` parameter in the `manifest.json` file has a format of Javascript object, which defines locations of icons of various sizes. Extension should have icons of different sizes: 16x16 px, 32x32 px, 48x48 px and 128x128 px. Icons are ".PNG" images that should be placed anywhere in the extension folder. Image files can have any names. I have created 4 icons of appropriate sizes in 16.png, 32.png, 48.png, and 128.png files and put them into the `icons` folder inside the extension root folder. Then, `manifest.json` should be pointed to these locations using the `icons` parameter in a way, as shown below:

```json
{
    "name": "Image Grabber",
    "description": "Extract all images from current web page",
    "version": "1.0",
    "manifest_version": 3,
    "icons": {
        "16":"icons/16.png",
        "32":"icons/32.png",
        "48":"icons/48.png",
        "128":"icons/128.png"
    },
    "action": {},
    "permissions": [],
    "background":{}
}
```
Paths to icon files are specified as relative paths.

After this is done, press the `Reload` button on the Image Grabber extension panel on the `chrome://extensions` tab to apply changed `manifest.json`. As a result, you should see that the icon of the extension on the toolbar changed, as displayed below:

![Extension with icon](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dm9fotnz4ldxf6d5nt6f.png)

Now it looks better, but if you press this icon, nothing happens. Let's add actions to this extension.

## Create the extension interface

An extension should do something, it should run some actions to have a sense. The extension allows to run actions in two ways:

* In the background, when extension starts
* From an interface of the extension, when a user interacts with it using buttons or other UI controls

The extension can use both options at the same time.

To run actions in the background, you have to create a JS script and specify its location in the `background` parameter of `manifest.json`. This script can define listeners for a wide range of browser events, for example: when the extension is installed, when a user opens/closes a tab in a browser when the user adds/removes a bookmark, and many others. Then this script will run in the background all the time and react to each of these events by running Javascript code from event handling functions.

For this extension, I will not use this feature, so the `background` parameter of `manifest.json` will be empty. It's included only to make the `manifest.json` file to be useful as a starting template for a Chrome extension of any kind, but in the Image Grabber extension, the only action is "Grab images" and it will run only from a user interface when the user explicitly press the "GRAB NOW" button.

To run actions from the interface, we need to define an interface. Interfaces for Chrome extensions are HTML pages, which can be combined with CSS stylesheets to style these pages, and Javascript files, which define actions to run when the user interacts with elements of that interface. The main interface is an interface, displayed when the user clicks on the extension icon on the toolbar and it should be defined in the `action` parameter of the `manifest.json` file. Depending on how the interface is defined, it can be opened as a new tab in the browser or displayed as a popup window below the extension button, when the user presses it.

The Image Grabber extension uses the second option. It displays a popup with a header and the "GRAB NOW" button. So, let's define this in the `manifest.json`:

```json
{
    "name": "Image Grabber",
    "description": "Extract all images from current web page",
    "version": "1.0",
    "manifest_version": 3,
    "icons": {
        "16":"icons/16.png",
        "32":"icons/32.png",
        "48":"icons/48.png",
        "128":"icons/128.png"
    },
    "action": {
        "default_popup":"popup.html"
    },
    "permissions": [],
    "background":{}
}
```
So, as defined here, the main interface is a popup window and the content of this popup window should be in the `popup.html` file. This file is an ordinary HTML page. So, create the `popup.html` file in the extension folder with the following content:

```html
<!DOCTYPE html>
<html>
    <head>
        <title>Image Grabber</title>
    </head>
    <body>
        <h1>Image Grabber</h1>
        <button id="grabBtn">GRAB NOW</button>
    </body>
</html>
```
This is a simple page with the "Image Grabber" header and the "GRAB NOW" button which has a "grabBtn" id. 

Go to `chrome://extensions` to `reload` the Image Grabber extension. Now you can press the extension icon to see the popup window with the interface:

![Extension basic UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01d5xsh4x9dj4afhopo9.png)

It works but looks not enough perfect. Let's style it using CSS. Create the following `popup.css` file in the extension folder:

```css
body {
    text-align:center;
    width:200px;
}

button {
    width:100%;
    color:white;
    background:linear-gradient(#01a9e1, #5bc4bc);
    border-width:0px;
    border-radius:20px;
    padding:5px;
    font-weight: bold;
    cursor:pointer;
}
```
This CSS defines that the `body` should have a width of 200px. This way the size of the popup window should be defined for a Chrome extension. If not defined, then the extension will use a minimum size required to display the content.

Then, add this `popup.css` stylesheet to the header of the `popup.html` page:

```html
<!DOCTYPE html>
<html>
    <head>
        <title>Image Grabber</title>
        <link rel="stylesheet" type="text/css" href="popup.css"/>
    </head>
    <body>
        <h1>Image Grabber</h1>
        <button id="grabBtn">GRAB NOW</button>
    </body>
</html>
```
So, when all this is in place, you can click on the extension icon again to see the styled popup window:

![Extension styled UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kamedmr10g5tdmqg1gmb.png)

As you could notice, you do not need to `reload` extension every time when modify HTML or any other file. You have to reload the extension only when change the `manifest.json`.

Now, to make our UI complete, let's add a Javascript code to react on the "GRAB NOW" button click event. Here is one important note, Chrome does not allow to have any inline Javascript in HTML pages of extensions. All Javascript code should be defined only in separate `.js` files. That is why create a `popup.js` file in the extensions folder with the following placeholder code:

```javascript
const grabBtn = document.getElementById("grabBtn");
grabBtn.addEventListener("click",() => {    
    alert("CLICKED");
})
```
and include this script file to the `popup.html` page:

```html
<!DOCTYPE html>
<html>
    <head>
        <title>Image Grabber</title>
        <link rel="stylesheet" type="text/css" href="popup.css"/>
    </head>
    <body>
        <h1>Image Grabber</h1>
        <button id="grabBtn">GRAB NOW</button>
        <script src="popup.js"></script>
    </body>
</html>
```
This code adds the `onClick` event listener to a button with `grabBtn` ID. Now, if you open the extension popup and click the "GRAB NOW" button, it should display an alert box with "CLICKED" text.

Finally, we have a complete layout of an extension with a styled interface and event handling script for it.

![Extension layout](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xdolewx2fj25wwsfxp5s.png)

At the current stage, this is an extension, that can be used as a base template to start building a wide range of Chrome extensions, based on a popup user interface.
 
Now let's implement a "business logic" of this concrete extension - the onClick handler for the "GRAB NOW" button to get a list of image URLs from the current browser page and copy it to a clipboard.

## Implement the "GRAB NOW" function

Using Javascript in extension you can do everything that you can do using Javascript on a website: open other HTML pages from current one, make requests to a remote servers, upload data from extension to the remote locations and whatever else. But in addition to this, if this script executed in a chrome extension, you can use Chrome browser APIs to communicate with the browser objects: to read from them and to change them. Most of Google Chrome APIs available through `chrome` namespace. In particular, for Image Grabber extension we will use the following APIs:

* `chrome.tabs` - Chrome Tabs API. It will be used to access an active tab of the Chrome browser.
* `chrome.scripting` - Chrome Scripting API. It will be used to inject and execute JavaScript code on a web page, that opened in the active browser tab.

### Obtain required permissions

By default, for security reasons, Chrome does not permit access to all available APIs. The extension should declare, which permissions it requires in the `permissions` parameter of the `manifest.json`. There are many permissions that exist, all they described in the official documentation here: https://developer.chrome.com/docs/extensions/mv3/declare_permissions/. For Image Grabber we need two permissions with the following names:

* `activeTab` - to obtain access to the active tab of a browser
* `scripting` - to obtain access to the Chrome Scripting API to inject and execute JavaScript scripts in different places of the Chrome browser.

To obtain those permissions, need to add their names to the `permissions` array parameter of the `manifest.json`:

```json
{
    "name": "Image Grabber",
    "description": "Extract all images from current web page",
    "version": "1.0",
    "manifest_version": 3,
    "icons": {
        "16":"icons/16.png",
        "32":"icons/32.png",
        "48":"icons/48.png",
        "128":"icons/128.png"
    },
    "action": {
        "default_popup":"popup.html",
    },
    "permissions": ["scripting", "activeTab"],
    "background":{}
}
```
and `reload` the extension on `chrome://extensions` panel. 

This is a final `manifest.json` for this project. Now, it has all required parts: icons, link to the main popup interface, and the permissions, that this interface requires.

### Get information about the active browser tab

To query information about browser tabs, we will use the `chrome.tabs.query` function, which has the following signature:

```javascript
chrome.tabs.query(queryObject,callback)
```

* The `queryObject` is a Javascript object with parameters that define search criteria for browser tabs, which we need to get. 
* The `callback` - is a function, that is called after the query is complete. This function is executed with a single parameter `tabs`, which is an array of found tabs, that meet specified search criteria. Each element of the `tabs` array is a`Tab` object. The `Tab` object describes the found tab and contains a unique ID of the tab, its title, and other information.

Here I will not completely describe `queryObject` format and the returned `Tab` object. You can find this information in a `chrome.tabs` API reference here: https://developer.chrome.com/docs/extensions/reference/tabs/. 

For the purpose of the `Image Grabber` extension, we need to query the tab which is active. The query to search this kind of tab is `{active: true}`.

Let's write a code to get information about the active tab to the "GRAB NOW" button onClick handler:

```javascript
const grabBtn = document.getElementById("grabBtn");
grabBtn.addEventListener("click",() => {    
    chrome.tabs.query({active: true}, (tabs) => {
        const tab = tabs[0];
        if (tab) {
            alert(tab.id)
        } else {
            alert("There are no active tabs")
        }
    })
})
```

This code executes a query to get all tabs that are `active`. After the query is finished, it calls a callback with an array of found tabs in the `tabs` argument. Only one tab can be active, so we can assume that this is the first and only item of the `tabs` array. If the active tab exists, we show an ID of that tab in an alert box (we will replace this alert with reasonable code in the next section). However, if there are no active tabs, we alert the user about that.

Now, if you open the extension and press the "GRAB NOW" button, it should show an alert window with a numeric ID of the active tab.

In the next section, we will use this ID to manipulate the content of a web page, displayed on that tab.

### Grab images from the current page

The extension can communicate with open pages of the Chrome browser using Chrome Scripting JavaScript API, located in the `chrome.scripting` namespace. In particular, we will use this API to inject a script to a web page of the current tab, execute this script and return the result back to the extension. When running, it has access to all content of a web page, to which this script is injected.

The only function of `chrome.scripting` API which is used for this extension is `executeScript`. It has the following signature:

```javascript
chrome.scripting.executeScript(injectSpec,callback)
```

**injectSpec**

This is an object of [ScriptInjection](https://developer.chrome.com/docs/extensions/reference/scripting/#type-ScriptInjection) type. It defines where and how to inject the script. `target` parameter of this object is used to specify "where" to inject the script - the ID of the browser tab to which the script should be injected. Then other parameters of this object define "how" to inject the script. The script can be injected as:

* file or files - in this case, need to specify an array of Javascript files to inject. The files should exist in the extension folder.
* function - in this case, need to specify a function to inject. The function should exist in the same (`popup.js`) file.

The script, which we need to inject will be used to get all images of a target page and return their URLs. This is a small script, so we will inject it as a function, located in the same `popup.js` file. So, `injectSpec` for this case will look like this:

```json
{
    target:{ tabId: tab.id, allFrames: true },
    func: grabImages,
}, 
```

Here we use the id of the `tab` object, that we received in the previous step as a target to inject script to. Also, there is a `allFrames` option set, which tells that the injected script should be executed in each embedded frame of the target page if that page has embedded frames. As a script, we will inject a `grabImages` function which will be defined later.

**callback**

The injected function will do actions on a target web page and on all embedded frames of this page (each frame is a separate page as well) and will return the result. After this happens, the extension will execute the **callback** function with returned results as an argument. An argument of the function is an array of objects of [InjectionResult](https://developer.chrome.com/docs/extensions/reference/scripting/#type-InjectionResult) type for each frame. Each object contains the "result" property, which is an actual result, that the `grabImages` function returns.

Now, let's join all parts together:

```javascript
const grabBtn = document.getElementById("grabBtn");
grabBtn.addEventListener("click",() => {    
    chrome.tabs.query({active: true}, function(tabs) {
        var tab = tabs[0];
        if (tab) {
            chrome.scripting.executeScript(
                {
                    target:{tabId: tab.id, allFrames: true},
                    func:grabImages
                },
                onResult
            )
        } else {
            alert("There are no active tabs")
        }
    })
})

function grabImages() {
    // TODO - Query all images on a target web page
    // and return an array of their URLs
}

function onResult(frames) {
    // TODO - Combine returned arrays of image URLs,
    // join them to a single string, delimited by 
    // carriage return symbol and copy to a clipboard
}
```
Then, this is how the `grabImages` function is implemented:

```javascript
/**
 * Executed on a remote browser page to grab all images
 * and return their URLs
 * 
 *  @return Array of image URLs
 */
function grabImages() {
    const images = document.querySelectorAll("img");
    return Array.from(images).map(image=>image.src);    
}
```

This function will run on a target web page, so, the `document`, specified inside it is a document DOM node of a target web page. This function queries a list of all `img` nodes from a document, then, converts this list to an array and returns an array of URLs (image.src) of these images. This is a very raw and simple function, so as homework you can customize it: apply different filters to this list, cleanup URLS, by removing "query" strings from them, and so on, to make a resulting list look perfect.

After this function is executed in each frame of the target web page, result arrays will be combined and sent to the `onResult` callback function, which could look like this:

```javascript
/**
 * Executed after all grabImages() calls finished on 
 * remote page
 * Combines results and copy a list of image URLs 
 * to clipboard
 * 
 * @param {[]InjectionResult} frames Array 
 * of grabImage() function execution results
 */
function onResult(frames) {
    // If script execution failed on the remote end 
    // and could not return results
    if (!frames || !frames.length) { 
        alert("Could not retrieve images from specified page");
        return;
    }
    // Combine arrays of the image URLs from 
    // each frame to a single array
    const imageUrls = frames.map(frame=>frame.result)
                            .reduce((r1,r2)=>r1.concat(r2));
    // Copy to clipboard a string of image URLs, delimited by 
    // carriage return symbol  
    window.navigator.clipboard
          .writeText(imageUrls.join("\n"))
          .then(()=>{
             // close the extension popup after data 
             // is copied to the clipboard
             window.close();
          });
}
```

Not all tabs that opened in the browser are tabs with web pages inside. For example, a tab with a list of extensions, or a tab with browser settings are not tabs with web pages. If you try to run a script with the `document` object on these tabs it will fail and return nothing. That is why at the beginning of the `onResult` function we check the result and continue only if it exists. Then we combine arrays of image URLs returned for each frame to a single array by using map/reduce combination and then, use [window.navigator.clipboard](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard) API to copy joined to string array to a clipboard. `writeText` function is asynchronous, so we have to wait until it finishes by resolving a promise, that it returns. And when it is resolved, we close the popup window of the extension.

I have explained only a single function of Chrome scripting API and only in the context of the Image Grabber extension. You can see the full documentation for Chrome Scripting API to clarify all missing parts: https://developer.chrome.com/docs/extensions/reference/scripting/ .

### Code cleanup

The last thing that I would do with the code, that handles the "GRAB NOW" onClick event, is to extract a code that does `chrome.scripting` to a separate function:

```javascript
const grabBtn = document.getElementById("grabBtn");
grabBtn.addEventListener("click",() => {    
    // Get active browser tab
    chrome.tabs.query({active: true}, function(tabs) {
        var tab = tabs[0];
        if (tab) {
            execScript(tab);
        } else {
            alert("There are no active tabs")
        }
    })
})

/**
 * Function executes a grabImages() function on a web page,
 * opened on specified tab
 * @param tab - A tab to execute script on
 */
function execScript(tab) {
    // Execute a function on a page of the current browser tab
    // and process the result of execution
    chrome.scripting.executeScript(
        {
            target:{tabId: tab.id, allFrames: true},
            func:grabImages
        },
        onResult
    )
}
```

And the final content of `popup.js` is following:

```javascript
const grabBtn = document.getElementById("grabBtn");
grabBtn.addEventListener("click",() => {    
    // Get active browser tab
    chrome.tabs.query({active: true}, function(tabs) {
        var tab = tabs[0];
        if (tab) {
            execScript(tab);
        } else {
            alert("There are no active tabs")
        }
    })
})

/**
 * Execute a grabImages() function on a web page,
 * opened on specified tab and on all frames of this page
 * @param tab - A tab to execute script on
 */
function execScript(tab) {
    // Execute a function on a page of the current browser tab
    // and process the result of execution
    chrome.scripting.executeScript(
        {
            target:{tabId: tab.id, allFrames: true},
            func:grabImages
        },
        onResult
    )
}

/**
 * Executed on a remote browser page to grab all images
 * and return their URLs
 * 
 *  @return Array of image URLs
 */
function grabImages() {
    const images = document.querySelectorAll("img");
    return Array.from(images).map(image=>image.src);    
}

/**
 * Executed after all grabImages() calls finished on 
 * remote page
 * Combines results and copy a list of image URLs 
 * to clipboard
 * 
 * @param {[]InjectionResult} frames Array 
 * of grabImage() function execution results
 */
function onResult(frames) {
    // If script execution failed on remote end 
    // and could not return results
    if (!frames || !frames.length) { 
        alert("Could not retrieve images from specified page");
        return;
    }
    // Combine arrays of image URLs from 
    // each frame to a single array
    const imageUrls = frames.map(frame=>frame.result)
                            .reduce((r1,r2)=>r1.concat(r2));
    // Copy to clipboard a string of image URLs, delimited by 
    // carriage return symbol  
    window.navigator.clipboard
          .writeText(imageUrls.join("\n"))
          .then(()=>{
             // close the extension popup after data 
             // is copied to the clipboard
             window.close();
          });
}
```

## Conclusion

After this is done, you can open any browser web page with images, click on `Image Grabber` extension to open its popup interface and then click the "GRAB NOW" button. Then, paste the clipboard content to any text editor. It should paste a list of absolute URLs of all images from that web page. 

You can clone and use the full source code of this extension from my GitHub repository: https://github.com/AndreyGermanov/image_grabber. However, I would recommend creating this extension from scratch while reading this article.

This is only the first part of the tutorial, related to this extension. In a second part, I will use this list of image URLs to build an additional interface for this extension, that will allow downloading all or selected images from this list as a single ZIP archive. This is definitely more useful than just having a list of URLs in the clipboard. Also, I will show how to package the completed extension and upload it to the Chrome Web Store which will make it available for anyone.

Read Part 2 here: https://dev.to/andreygermanov/create-a-google-chrome-extension-part-2-image-grabber-47h7.

Feel free to connect and follow me on social networks where I publish announcements about my articles, similar to this one and other software development news:

LinkedIn: https://www.linkedin.com/in/andrey-germanov-dev/
Facebook: https://web.facebook.com/AndreyGermanovDev
Twitter: https://twitter.com/GermanovDev

My online services website: https://germanov.dev

Happy coding guys!