How to build a Chrome Extension on Flutter and use the Chrome API?

Joffrey Jougon
7 min readFeb 7, 2023

--

Are you a Flutter fan looking to dive into the world of Chrome extensions? Look no further! In this article, we’ll take you on a journey to build a basic Chrome extension using the power of Flutter.

We’ll be using Flutter to create the starter counter app on Google Chrome. But wait, it gets even better. Each time you press the button, a pop-up will appear on the screen of the user’s web page. See yourself in the screenshot below.

And how are we going to do that? We will explore how we can communicate with the DOM of the user using the Chrome API directly from Flutter.

That’s right, Flutter and Chrome, coming together like peanut butter and jelly. So sit back, relax, and get ready to have some fun because we’re about to create a Chrome extension like you’ve never seen before!

How does a Chrome Extension work?

Before getting started, let me explain to you quickly and in a very basic way how a Chrome Extension is working.

A Chrome extension consists of several parts that work together to provide a seamless user experience. These parts include a background script, content script, popup, and the manifest file.

The background script runs continuously in the background and can perform tasks such as setting up listeners or making API calls. It’s important to note that the background script has limited access to the DOM, so it cannot directly interact with the user’s web page but can send messages to the content script.

The content script is a JavaScript file that runs in the context of the user’s web page and can directly interact with the DOM. This allows it to manipulate the web page, add new elements, or remove existing ones.

The popup is a small HTML file that is displayed when the user clicks on the extension’s icon in the browser’s toolbar. It’s the interface that allows the user to interact with the extension. This is what we are going to build using Flutter.

The manifest file, named ‘manifest.json’, acts as a configuration file for the Chrome extension. It contains information about the extension such as its name, description, version, and the scripts that it needs to run. It also specifies the permissions the extension requires and the browser actions it should perform.

How are we going to use these parts?

In our case, we’ll be sending a message from the pop-up to the background script and sending this information to the content script. Then, the content script will receive this message and perform the desired action. This communication between the different components of the Chrome extension is made possible by the Chrome API only available on Javascript.

And with that, we now have a basic understanding of how a Chrome extension works. Let’s get started!

1) Creation of the Flutter App

First, we’ll create a Flutter app by taping the following command in your terminal :

flutter create flutter_chrome_app

Next, we’ll be installing the ‘js’ package from pubspec.yaml. This handy little package will allow us to make calls to the Chrome API using JavaScript, making our lives a lot easier.

dependencies:
js: ^0.6.4
flutter:
sdk: flutter

Then create a file inside your lib folder called “chrome_api.dart” and put the following code :

@JS('chrome')
library main; // library name can be whatever you want

import 'package:js/js.dart';

@JS('runtime.sendMessage')
external sendMessage(ParameterSendMessage parameterSendMessage);

@JS()
@anonymous
class ParameterSendMessage {
external String get type;
external String get data;

external factory ParameterSendMessage({String type, String data});
}

Here we are using the message parsing method of the Chrome API. You can find more information about it here. The way we format the code here and the reason why we do it is also explained in the documentation of the JS package for Flutter.

Once, you’re done, don’t forget to modify the onPressed attribute of your floating action button as follows :

floatingActionButton: FloatingActionButton(
onPressed: () {
_incrementCounter();
sendMessage(ParameterSendMessage(
type: "counter", data: _counter.toString()));
},
...
));

What’s happening here is that each time the floating button is triggered, the Flutter app is going to update its UI with the counter number, but it will also send a message as a String to the background script.

2) Create the folder for the Chrome Extension

Now that we are done with our Flutter app, let’s generate the folder we are going to use for the Chrome Extension.

First, modify your file “index.html” as follow:

  • adding the line for the dimension of the app inside the HTML at the top
<html style="height: 600px; width: 350px">
  • changing the script of your file by keeping only this line
<script src="main.dart.js" type="application/javascript"></script>

This is what your HTML file should look like :

<!DOCTYPE html>
<html style="height: 600px; width: 350px">
<head>

<base href="$FLUTTER_BASE_HREF">

<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">

<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="flutter_chrome_app">
<link rel="apple-touch-icon" href="icons/Icon-192.png">

<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>

<title>flutter_chrome_app</title>
<link rel="manifest" href="manifest.json">

</head>
<body>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>

Then change your “manifest.json” as follows :

{
"name": "flutter_chrome_app",
"description": "A new Flutter project.",
"version": "1.0.0",
"content_security_policy": {
"extension_pages": "script-src 'self' ; object-src 'self'"
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"contentScript.js"
]
}
],
"action": {
"default_popup": "index.html",
"default_icon": "icons/Icon-192.png"
},
"manifest_version": 3
}

And finally, use this command to create the folder :

flutter build web --web-renderer html --csp

3) Add the background and the content script

Now, in your Flutter app, you should have a “build” folder with a “web” folder inside. Don’t hesitate to rename it “chrome-extension” to avoid mistakes.

Now, let’s create these two files inside the folder :

background.js

The following code enables you to send messages between a background script and a content script.

The chrome.runtime.onMessage.addListener listens for incoming messages with the type "counter" (this is what we send from the Flutter app).

When a message with this type is received, it calls the sendMessage function.
This sendMessage function sends a message to the content script with a type "notifications" and the “message” parameter to the active tab in Google Chrome.

function sendMessage(message) {
chrome.tabs.query({ active: !0, currentWindow: !0 }, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, { "type": "notifications", "data": message });
});
}

chrome.runtime.onMessage.addListener(async function (message, sender, sendResponse) {
if (message.type === "counter") {
sendMessage(message.data);
}
});

contentScript.js

The code creates a pop-up message using JavaScript and the Chrome runtime API.

When the browser receives a message of type “notifications”, the create_popup function is called with the message data as an argument.

This function creates a div element with a message item and a close button to the div and appends the div to the body of the document. The close button has a click event listener that removes the popup element when clicked. The popup will also automatically disappear after 3 seconds if it hasn’t been closed by the user.

function create_popup(message) {

var popup = document.createElement("div");
popup.id = "popup";
popup.style.cssText = "position:fixed;bottom:20px;right:20px;width:200px;height:90px;background-color:rgba(35,68,128);z-index:9999;text-align:center;font-size:13px;padding:10px;border-radius: 10px;box-shadow: 0px 0px 5px #888888; display: flex; align-items: center; justify-content: center; flex-direction: column;";
var messageItem = document.createElement("message-item");
messageItem.innerHTML = message;
messageItem.style.cssText = "margin: 0; padding-right:10px; color: white";

var closeBtn = document.createElement("div");
closeBtn.innerHTML = "<p>✖︎</p>";
closeBtn.style.cssText = "position:absolute;top:0;right:10px;cursor:pointer;";

popup.appendChild(messageItem);
popup.appendChild(closeBtn);
document.body.appendChild(popup);
closeBtn.addEventListener("click", function () {
popup.remove();
});
setTimeout(function () {
popup.remove();
}, 3000);

}

chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
if (message.type == "notifications") {
create_popup(message.data);
}
});

That’s it!

This is what your folder should look like :

Here is a Github with all the code we created.

4) Final step: install the app on Google Chrome

We can finally install the app :

1. Open Google Chrome and navigate to the “Extensions” page by typing “chrome://extensions” in the URL bar and pressing “Enter”.

2. Turn on the “Developer mode” toggle switch in the upper-right corner of the page.

3. Click the “Load unpacked” button and select the folder that contains the extension you want to install.

4. After selecting the folder, Chrome will install the extension and you should see it listed on the “Extensions” page.

You should now be able to use the Chrome Extension! Don’t forget to reload the page on which you are trying to make it work, be careful it won’t work on the Extensions page.

Conclusion

In this article, we learned how to build a basic Chrome extension using Flutter and the Chrome API.
We covered the different parts of a Chrome extension, including the background script, content script, popup, and manifest file.

We explained how to communicate between the different components of the extension using the Chrome sendMessage function, and how to manipulate the DOM of the user’s web page.

If you have any questions or remarks, feel free to leave a comment. And keep an eye out for my next tutorial on how to authenticate on Firebase using Flutter in a Chrome extension. Happy coding!

Here is a Github with all the code we created.

--

--

Joffrey Jougon

I'm a junior developer with a passion for learning new technologies. Currently, I'm focusing my efforts on mastering Flutter and Node JS. 👨🏾‍💻