Namespacing Chrome Storage for page dependant settings for your Chrome Extension

Published on Mar 3, 2021

Reading time 3 min

As far as I know chrome.storage saves it’s keys globally, so it’s not like localstorage on normal pages that gets only works in the current page. For that purpose I had to figure out a way of achieving this. So I decided using namespaces using template literals. This is a really made up process so there might be inaccuracies, feel free to let me know :)

Creating the extension

Creating the extension from scratch is pretty straightforward, we just have to add a manifest.json file with the following values:

permissions are needed for the sole purpose of this tutorial but could be not added

{
    "name": "[name of the extension]",
    "version": "1.0",
    "manifest_version": 3,
    "permissions": ["storage", "tabs"],
    "action": {
        "default_popup": "popup.html"
    }
}

We have to create a popup.html file and just use it as if it was a normal HTML. We can import scripts add stylsheets, etc.

<div class="container">
    <div class="box "></div>
    <button id="toggle">Toggle</button>
</div>

<script src="popup.js"></script>

In the popup.js file is where we are going to have the logic for namespacing chrome.storage keys.

Firstly, I have to point out that chrome.storage is an async api therefore we will have to use async/await on our main function.

If you are working with multiple variables I recommend using an object, as I did.

;(async function () {
    let settings = {
        active: false
    }
    // Gets the current URL of the tab
    const url = await new Promise((res, rej) => {
        chrome.tabs.query({ active: true, lastFocusedWindow: true }, (tabs) => {
            let url = tabs[0].url
            res(url)
        })
    })

    // Gets chrome storage with the namespace URL
    const synchedSettings = await new Promise((res, rej) => {
        chrome.storage.sync.get(`settings:${url}`, (results) => {
            res(Object.values(results)[0])
        })
    })

    if (synchedSettings) {
        settings = synchedSettings
        handleToggleButton(settings.active)
    }
})()

Here we are using the tab API, that we enabled before in the manifest, to get the URL of the current page and we wait for the Promise to resolve. Then we use the storage api and use a template literal to get the settings only from the current URL. As we have no real way of getting the key from the results, we just resolve the first element in the Object.values() that returns an array of all the keys in the results which in this case is the settings object we want. Then we substitute the default settings object with the one we got from storage. To conclude, it works.

To set up a new element we have to set the addEventListener inside the async function as we’ll need the URL for setting up the namespace.

const toggleButton = document.querySelector("#toggle")
const box = document.querySelector('.box')

// Add class to .box depending of if it's active or not
function handleToggleButton(active) {
    if (active) {
        box.classList.add('active')
    } else {
        box.classList.remove('active')
    }
}

;(async function () {

    // ...

    // When toggle gets pressed changes settings.value value and saves it to storage with namespace
    toggleButton.addEventListener("click", () => {
        settings = {
            active: !settings.active
        }
        chrome.storage.sync.set({ [`settings:${url}`]: settings }, function () {
            handleToggleButton(settings.active)
        })
    })
})()

Add Chrome Extension

You have to go to chrome://extensions and turn on Developer Mode. Press Load unpacked and select the directory where the extension is. In the screenshot I’m using Brave Browser but it makes no difference.

image

Wrap up

I hope you will find this blog post useful and keep it handy for a quick reference. This solution is a little clumsy but I didn’t find any better way and wanted to share it with you. Feel free to send me a DM or to mention me on Twitter if you’ve got any suggestion or fix.

You can look at the whole code in this repository