Migrate to Plasmo Framework
The Plasmo Browser Extension Framework simplifies building browser extensions with its declarative approach. It eliminates boilerplate code and provides a file-based configuration system that is easy to understand, use and opt out of.
In this guide, we will walk you through transitioning from any extension project to Plasmo and highlight some key changes you need to make.
Install Plasmo CLI
The Plasmo framework's main driver is the Plasmo CLI. It is a Node.js package that contains a compiler, a bundler, a development server, and a packager tailor-made for browser extensions:
pnpm install plasmo
To start the development server, run plasmo dev
. To build the extension, run plasmo build
. To package the extension, run plasmo package
.
manifest.json
Plasmo merges manifest.json
into the package.json
and abstracts away the most basic properties:
Manifest Field | Abstractions |
---|---|
icons | Auto generated with the icon.png in the assets directory |
action , browser_actions | popup.tsx |
options_ui | options.tsx |
content_scripts | contents/*.{ts,tsx} , content.ts , content.tsx |
background | background.ts |
sandbox | sandbox.tsx , Sandbox Pages |
manifest_version | set by the --target build flag, default to 3 |
version | set by the version field in package.json |
name | set by the displayName field in package.json |
description | set by the description field in package.json |
author | set by the author field in package.json |
homepage_url | set by the homepage field in package.json |
Plasmo centralizes common metadata between package.json
and manifest.json
and resolves any static file references (such as action, background, content scripts, and so on) automatically.
This enables you to focus on the metadata that matters, such as name, description, OAuth, declarative_net_request, and so on.
Furthermore, it enables the framework to provide even more powerful features, such as:
- Using environment variables in the manifest
node_modules
resolving for web-accessible resources- Targeting multiple browsers, manifest versions, and environments (dev, prod, staging, etc...)
Popup, Options, New tab Pages
Plasmo removes the need to manually mount your React/Svelte/Vue components.
In a non-Plasmo extension project, to mount a React component, you'd create an index.html
template and associated JavaScript code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Popup</title>
</head>
<body>
<div id="root"></div>
<script src="popup.js"></script>
</body>
</html>
import { createRoot } from "react"
import Popup from "./core/popup"
const root = document.getElementById("root")
createRoot(root).render(<Popup />)
If you wanted to add TypeScript, LESS, or SCSS, you would need to use Webpack, Parcel, or ESBuild, with extra plugins and loader setups.
With the Plasmo framework, it's much simpler. Add a popup.tsx
or options.tsx
file in the source code directory, exporting a default React component.
Plasmo will take care of the rest:
import Popup from "./core/popup"
export default Popup
Plasmo has built-in support for CSS, SCSS, LESS, CSS Modules, and PostCSS plugins.
If you'd like to, for example, use SCSS, you can simply import the SCSS file in your React component:
import Popup from "./core/popup"
import "./popup.scss"
export default Popup
You can mount a React component this way for the options page, new tab page, sandbox pages, and any other custom page.
Custom Pages
In non-Plasmo extension projects, creating custom pages typically requires additional HTML templates and associated JavaScript code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Custom Page</title>
</head>
<body>
<div id="root"></div>
<script src="./custom.js"></script>
</body>
</html>
import { createRoot } from "react"
import CustomPage from "./core/custom-page"
const root = document.getElementById("root")
createRoot(CustomPage).render(<PopupApp />)
With Plasmo, you can use the built-in tab pages feature to create custom pages easily. Add a tabs
folder in you source code directory, and add your custom pages there, using .tsx
files:
import Hello from "./core/hello"
export default Hello
The page will be available at chrome-extension://<extension-id>/tabs/custom.html
.
Content Scripts
In non-Plasmo extension projects, you'd specify your content scripts in the manifest.json
file and write them in JavaScript:
{
"content_scripts": [
{
"matches": ["https://*/*", "http://*/*"],
"all_frames": true,
"css": ["content.css"],
"run_at": "document_start"
}
]
}
console.log("Hello from content script!")
With Plasmo, your can specify your content script and its respective config in a file called content.ts
, or inside a directory called contents
- More on that later!
Here's what the content.ts
file would look like:
import type { PlasmoCSConfig } from "plasmo"
export const config: PlasmoCSConfig = {
matches: ["https://*/*", "http://*/*"],
all_frames: true,
css: ["~content.css"]
}
console.log("Hello from content script!")
No more moving back and forth between your manifest.json
and content.js
files. An added benefit is that the config is fully typed and thus can provide valuable auto-completion from your IDE.
To add multiple content scripts, create a directory called contents
and repeat the above steps. You can name your scripts whatever you'd like in this directory. They will all get added as content scripts with their own configs.
For example, let's say you want to add a content script that changes the background color of the page to green. You can do so by creating a file called emerald-splash.ts
in the contents
directory:
document.body.style.backgroundColor = "green"
Since the content script file didn't export a config, Plasmo will use the default config:
{
"matches": ["<all_urls>"]
}
Content Scripts UI
If you're injecting a React component into a web page, your extension likely has a lot of boilerplate code, creating Shadow DOMs, finding the right element to mount to, MutationObservers, and more.
We've abstracted all of this out so you can focus on building your component and not worry about the rest.
For example, let's say you want to inject a React component of a simple button into a web page. You can do so by creating a file called press-me.tsx
in the contents
directory:
export default function PressMeCSUI() {
return <button>Press me</button>
}
Plasmo will automatically inject this component into the page, and mount it to the root element of the page.
What if you want to inject the component into a specific element? You can do so by exporting a getInlineAnchor function from the file:
import type { PlasmoGetInlineAnchor } from "plasmo"
export const getInlineAnchor: PlasmoGetInlineAnchor = async () =>
document.querySelector("#pricing")
By returning an Element, Plasmo will mount the component adjacent to that element. You can completely customize the mounting behavior, how the component renders, and more.
This feature is called Content Scripts UI. To learn more about its API and how it works, check out the technical documentation on its lifecycle.
Background Service Worker
In non-Plasmo projects, creating a background service worker requires specifying the background
property in the manifest.json
file and writing the service worker code in JavaScript:
{
"background": {
"service_worker": "background.js"
}
}
console.log("Hello from BGSW!")
In Plasmo, you specify the background service worker by creating a background.ts
file:
console.log("Hello from BGSW!")
You can import any modules that target the standard service worker runtime into background.ts
. For example, you can add the bip39
module:
import { generateMnemonic } from "bip39"
console.log(
"Live now; make now always the most precious time. Now will never come again."
)
console.log(generateMnemonic())
Plasmo will automatically bundle the background.ts
file and add it as a background service worker to the manifest.json
file.
Environment Variables
If you're currently using environment variables, you can continue to do so in Plasmo.
The Plasmo framework supports environment variables out of the box with no additional setup required.
Opting Out
The Plasmo framework's abstraction philosophy is to remove the most common configuration and boilerplate code. This enables developers to work under a higher abstraction layer -- their chosen UI library/framework, such as React, Vue, and Svelte. This is the path to creating more powerful and beautiful extensions.
Opting out of Plasmo is as easy as removing the plasmo
dependency from your package.json
file and taking your components out onto your custom setups. This is possible because all the glue code generated by Plasmo is injected at the framework compiler and bundler layer, making your feature code extremely portable.
You can also use Plasmo as much or as little as needed. All chrome APIs are available, and you can use them directly in your feature code. For example, instead of using the messaging API, you can use the chrome.runtime.messaging
API directly. The same goes for content scripts and extension pages.