Hello everyone 👋
For a while, I've been toying with the idea of building an extension for the ChatGPT website. I wanted to enhance its functionality by allowing multiple chat deletions and adding other useful features. But every time I started the development process, I found myself hitting a wall, particularly around Vite configuration for extension bundling or communication between different extension contexts.
After many attempts, I finally succeeded! And I thought I'd share my experience with you all ❤️. This is the first part in a series of blog posts where I will guide you through my process.
In this initial part, we'll cover the setup process: initializing the project, creating the popup, content script and background worker, and incorporating them into our manifest.json
file.
The second part which shows how to connect different parts of the extension together is available here
Setup
Let's start by setting up a Vite project. Use your favorite package manager to establish a fresh Vite project. Here, I'll be using yarn:
yarn create vite
During the installation, I select React -> Typescript with SWC
.
Once done, I add a few Vite plugins in the root directory of the newly created project:
yarn add -D vite-tsconfig-paths @crxjs/vite-plugin@beta
The vite-tsconfig-paths
plugin supports path aliases and @crxjs/vite-plugin@beta
is a library for bundling the extension.
Next, we need to register these plugins in the vite.config.ts
file:
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import react from "@vitejs/plugin-react-swc";
import { ManifestV3Export, crx } from "@crxjs/vite-plugin";
import manifestJson from "./manifest.json";
const manifest = manifestJson as ManifestV3Export;
// https://vitejs.dev/config/
export default defineConfig({
plugins: [tsconfigPaths(), react(), crx({ manifest })],
});
As you can see, I've imported the manifest file and asserted it to the ManifestV3Export
type. The crx
plugin's typings require this to function correctly 😅. A quick note - I needed to add the manifest.json
file to my tsconfig.node.json
's include
field to avoid a resolution error.
Now let's discuss the manifest.json
file. This file provides an overview of my extension: its name, description, permissions, as well as the locations of entry files for the popup, content script, and background worker.
{
"manifest_version": 3,
"name": "ChatGPT Extension",
"version": "0.0.1",
"description": "Improve your ChatGPT experience with this extension! Bulk exports, bulk deletes, save prompts and more!",
"permissions": [
"activeTab",
"cookies",
"declarativeContent",
"scripting",
"storage",
"tabs"
],
"host_permissions": ["https://chat.openai.com/*"],
"action": {
"default_popup": "index.html"
},
"background": {
"service_worker": "src/background/background.ts",
"type": "module",
"persistent": false
},
"content_scripts": [
{
"matches": ["https://chat.openai.com/*"],
"js": ["src/contentScript/main.tsx"],
"run_at": "document_end"
}
]
}
Here, permissions like
activeTab
, cookies
, and tabs
grant the extension access to various aspects of the browser's functionalities. For example, activeTab
allows the extension to interact with the current tab while cookies
and tabs
allow access to cookie data and tab details respectively.
Next, we organize our application structure. The setup process has provided us with several files. Here's how we arrange them:
1) I create a new popup
directory inside my src
folder, move there main.tsx
and App.tsx
files, and update the index.html
file to point to the new entry file location:
...
<script type="module" src="/src/popup/main.tsx"></script>
...
The popup runs in its own context, independently from the background workers or content scripts. It doesn't have access to the DOM, which is why we'll be using a content script for that purpose.
2) I duplicate the popup
directory, rename it to contentScript
, and within the main.tsx
script, create a new div
element to append it to the document's body
. Then, I render my application into the created div
:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "../index.css";
const root = document.createElement("div");
root.id = "chatgpt-content-script-root";
document.body.appendChild(root);
ReactDOM.createRoot(root).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
This approach ensures: a) there is a root element to render our application into; b) we will not have conflicts with the website's existing DOM elements, ensuring our extension doesn't interfere with the website's functionality or vice versa.
3) Lastly, we create a background script inside the background
folder. For now, let's insert a dummy console.log
just to ensure everything is working correctly.
Now comes the exciting part: testing our setup:
1) yarn build
- this command builds my extension into a dist
directory.
2) Enable developer mode in Google Chrome extensions.
3) Load the extension by clicking the Load unpacked
button and selecting the dist
directory.
4) Open https://chat.openai.com/
- at this point, you should notice the website's user interface has been modified by a Vite counter. When you click the extension button, guess what pops up? 😏 The last thing we need to test is the background script. Right-click anywhere inside the extension popup and select Inspect popup
. Here, you should see your dummy console.log.
Yay! Congratulations to us! 🎉 We've just created our first Google Chrome extension. It currently doesn't do much more than adjusting the website's layout, but it's just the beginning.
In case you face any issues during setup, ensure to double-check your file structure and the content of your manifest.json
file. Most errors at this stage come from misconfigurations in these areas.
Stay tuned for the next part where I'll set up communication between different contexts of our extension.
In the meantime, if you've tried these steps and have feedback, or if you'd like me to explain anything in more detail, please leave a comment below. I'd love to hear from you!
Top comments (4)
I have a question... how to use environment variables in web extension built with React + Vite?
Awesome! thanks for sharing this.
Didn't know where to put the background script and how to configure vite in order to make the extension work.
kudos! 🎉
is there a github repo ?
I inspect my popup but i cant see my log message I type in background.js