8
.
05
.
2024
30
.
01
.
2023
Tutorial
Frontend
Backend

Writing Chrome Extensions Is (probably) Easier Than You Think

Antoni Smoliński
Frontend Developer

Are you interested in writing your own Chrome extension, but think it’s too hard? If so, then I’ve been in your place, and I can tell you now that there's nothing to worry about. I will share my story and the approach that I took while creating my first Chrome extension.

I had a problem that I needed to solve: I’ve been doing my home budget bookkeeping in a Google spreadsheet. At the end of each month, I would sit down and write up all my earnings and expenses, divided by categories that I had come up with. Over the years, this process has become tiring and time-consuming, so I’ve come up with a plan to create a Chrome extension that would scrape data from my banking account and fill all the recurring spendings with the correct category and description.

I was worried that writing such an extension would require me to learn many new concepts. Thankfully, all you need is just a basic understanding of web development technologies such as HTML, CSS, and JavaScript.

The first thing that I needed to create was a manifest file. This file contains all the information that the browser needs to properly set up your extension. It is similar in some ways to an Android app manifest. It needs to be named manifest.json and be in the root directory of your code. A really basic manifest file from my project looks like this:

{
    "name": "Expenses Scraper Extension",
    "description": "Scrap mBank data to CSV",
    "version": "0.0.1",
    "manifest_version": 3,
    "permissions": ["storage", "activeTab", "scripting"],
    "action": {
        "default_popup": "popup.html"
    }
}

The name, description, and version attributes are self-explanatory. At the moment, the latest version of the Chrome manifest is 3, so I’ve used that. The three permissions that I’ve needed are storage (for storing my rule set in Chrome’s memory), activeTab (for targeting the open chrome tab), and scripting (for executing a JS script inside the targeted tab).

The action attribute describes what happens when you click on your extension icon in Chrome’s topbar. In my case, it just renders the "popup.html" file.

There are many more features available through the manifest file, like service worker configuration, an extension options menu, or icons. You can also narrow down an extension’s action to a specific site or domain.

If you have your manifest ready, you can load it into Chrome. To do that, go to chrome://extensions/, switch to Developer Mode, and click Load Unzipped.

Aside from the manifest file, writing a Chrome extension is really similar to writing any web page or application. You use the same techniques for HTML, JS, and CSS as you would in web development. You can use libraries that you like, and the popup.html window that gets opened when you click the icon behaves the same way as any page opened in a new tab.

However, Chrome extensions have some additional powers: they can interact with any other open tab and inject code into it. You do this by using chrome.tabs and chrome.scripting APIs. In my project, I only needed access to my bank account page, so I could scrape the data that I was looking for. To do this, first I used a tab reference:

let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

This line of code lets you access the currently open tab.

Next, I had to execute my scrapping code on this tab:

return chrome.scripting.executeScript({
    target: { tabId: tab.id },
    func: scrapData,
    args: [className]
}).then((res) => {
    return res[0].result;
});

This code runs the scrapData function in the open tab. One important thing is that the ScrapData function gets executed inside this tab’s context, so assigning any values through this won’t work. Also, if you want to use any variables "outside" of the tab, you need to declare them in the "args" attribute. In my case, I wanted to pass the class name of the element that will be checked on the bank page. I think that this class can change when my bank updates its app, so I want to be able to also change it in my extension’s UI.

The other place where writing extensions differs a little from writing web pages is data persistence. When writing Chrome extensions, you should use the chrome.storage API. Here you have access to storage.local and storage.session objects, which behave the same way as localStorage and sessionStorage in web development. One additional feature is the storage.sync object. It lets you sync the data to any Chrome browser that the user is logged into. If the user has syncing disabled, it behaves like storage.local.

You can access and save your data like this:

async function loadRules() {
    return chrome.storage.sync.get("ruleSet");
}

function saveRules(ruleSet) {
    chrome.storage.sync.set({ ruleSet });
}

One thing worth noting is that the chrome.sync.get function is async, so it needs to be handled accordingly.

Overall, the process of writing my Chrome extension was more fun and easier than I anticipated. The finished product allows me to add and remove rules that describe what category and description should be assigned to each expenditure based on its value and name in my banking history.Then, based on those rules, I can generate CSV data that is fully compatible with my spreadsheet. This tool does not look flashy, but it gets the job done.

chrome extension

If you would like to go through the code, it is available here. And if you’re an mBank customer and would find my script helpful, feel free to pull the code and use it yourself.

This post is not a tutorial. Chrome extensions can do much more than what I described here. If you want a more in-depth look at the features that I touched upon here, Google has well-written documentation.

So if you've been wanting to try your hand at creating your own Chrome extension, I hope I've convinced you to do it.

Antoni Smoliński
Frontend Developer

Check my Twitter

Check my Linkedin

Did you like it? 

Sign up To VIsuality newsletter

READ ALSO

LLM Embeddings in Ruby - Paweł Strzałkowski

LLM Embeddings in Ruby

17
.
03
.
2024
Paweł Strzałkowski
Ruby
LLM
Embeddings
ChatGPT
Ollama
Handling Errors in Concurrent Ruby, Michał Łęcicki

Handling Errors in Concurrent Ruby

14
.
11
.
2023
Michał Łęcicki
Ruby
Ruby on Rails
Tutorial
Recap of Friendly.rb 2024 conference

Insights and Inspiration from Friendly.rb: A Ruby Conference Recap

02
.
10
.
2024
Kaja Witek
Conferences
Ruby on Rails

Covering indexes - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Ruby on Rails
Postgresql
Backend
Ula Sołogub - SQL Injection in Ruby on Rails

The Deadly Sins in RoR security - SQL Injection

14
.
11
.
2023
Urszula Sołogub
Backend
Ruby on Rails
Software
Michal - Highlights from Ruby Unconf 2024

Highlights from Ruby Unconf 2024

14
.
11
.
2023
Michał Łęcicki
Conferences
Visuality
Cezary Kłos - Optimizing Cloud Infrastructure by $40 000 Annually

Optimizing Cloud Infrastructure by $40 000 Annually

14
.
11
.
2023
Cezary Kłos
Backend
Ruby on Rails

Smooth Concurrent Updates with Hotwire Stimulus

14
.
11
.
2023
Michał Łęcicki
Hotwire
Ruby on Rails
Software
Tutorial

Freelancers vs Software house

02
.
10
.
2024
Michał Krochecki
Visuality
Business

Table partitioning in Rails, part 2 - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Backend
Postgresql
Ruby on Rails

N+1 in Ruby on Rails

14
.
11
.
2023
Katarzyna Melon-Markowska
Ruby on Rails
Ruby
Backend

Turbo Streams and current user

29
.
11
.
2023
Mateusz Bilski
Hotwire
Ruby on Rails
Backend
Frontend

Showing progress of background jobs with Turbo

14
.
11
.
2023
Michał Łęcicki
Ruby on Rails
Ruby
Hotwire
Frontend
Backend

Table partitioning in Rails, part 1 - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Backend
Ruby on Rails

Table partitioning types - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Backend

Indexing partitioned table - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Backend
Postgresql
SQL Views in Ruby on Rails

SQL views in Ruby on Rails

14
.
11
.
2023
Jan Grela
Backend
Ruby
Ruby on Rails
Postgresql
Design your bathroom in React

Design your bathroom in React

14
.
11
.
2023
Bartosz Bazański
Frontend
React
Lazy Attributes in Ruby - Krzysztof Wawer

Lazy attributes in Ruby

14
.
11
.
2023
Krzysztof Wawer
Ruby
Software

Exporting CSV files using COPY - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Ruby
Ruby on Rails
Michał Łęcicki - From Celluloid to Concurrent Ruby

From Celluloid to Concurrent Ruby: Practical Examples Of Multithreading Calls

14
.
11
.
2023
Michał Łęcicki
Backend
Ruby
Ruby on Rails
Software

Super Slide Me - Game Written in React

14
.
11
.
2023
Antoni Smoliński
Frontend
React
Jarek Kowalewski - ILIKE vs LIKE/LOWER - Postgres Stories

ILIKE vs LIKE/LOWER - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Ruby
Ruby on Rails
Postgresql