DEV Community

Cover image for Building Chrome Extensions with Plasmo
Alexander Gekov
Alexander Gekov

Posted on

Building Chrome Extensions with Plasmo

Introduction

At TalentSight, we've built a web platform that integrates deeply with LinkedIn. However, we noticed our recruiters were spending most of their time directly on LinkedIn rather than our platform. This led us to an important realization: we needed to bring our tools to where our users actually work.

The solution? A Chrome extension that embeds custom buttons and actions directly into LinkedIn, allowing recruiters to use TalentSight's features without ever leaving their workflow. In this tutorial, I'll walk you through how we built this using the Plasmo framework, and show you how to create your own Chrome extension.

Talsight Chrome Extension

What is Plasmo?

Plasmo is a browser extension SDK that dramatically simplifies extension development. Instead of wrestling with Chrome's complex manifest files and messaging APIs, Plasmo provides:

  • React component support out of the box
  • Built-in handling of pop-ups and messaging APIs
  • Automatic management of browser APIs
  • Hot module replacement for faster development
  • TypeScript support by default

Think of it as the Next.js of browser extensions. It handles the boilerplate so you can focus on building features.

Plasmo logo

Getting Started with Plasmo

Initial Setup

To create a new Plasmo project, run the following command with your preferred package manager:

npm create plasmo
# or
pnpm create plasmo
# or
yarn create plasmo
Enter fullscreen mode Exit fullscreen mode

This scaffolds a complete project structure with everything you need to get started.

Terminal screenshot showing Plasmo project scaffolding

Project Structure

After scaffolding, you'll see several key files:

  • popup.tsx - The React component that displays when users click your extension icon
  • assets/ - Where you store icons and images for your extension
  • package.json - Contains dependencies and web manifest fields needed for publishing

The beauty of Plasmo is that it automatically generates the manifest.json file from your package.json, eliminating manual configuration headaches.

Folder Structure

Development Workflow

One of Plasmo's best features is live reloading. Start the development server with:

npm run dev
Enter fullscreen mode Exit fullscreen mode

This enables hot module replacement, meaning you can make changes to your code and see them instantly without rebuilding or reloading the extension manually. This is a massive time-saver during development.

Building for Production

When you're ready to package your extension for production:

npm run build
Enter fullscreen mode Exit fullscreen mode

This creates a production-ready build in the build/ folder, optimized for your target browser and environment.

Loading Your Extension in Chrome

Before you can test your extension, you need to load it into Chrome:

  1. Navigate to chrome://extensions in your browser
  2. Enable Developer mode using the toggle in the top right
  3. Click the Load unpacked button that appears
  4. Navigate to your project's build/chrome-mv3-dev folder (or build/chrome-mv3-prod for production builds)
  5. Select the folder to load your extension

Your extension icon should now appear in Chrome's toolbar.

Chrome extensions page showing how to load unpacked extension

Extension Pages in Plasmo

Plasmo supports several types of extension pages, each serving a specific purpose:

Popup Page (popup.tsx)

The popup appears when users click your extension icon. This is typically your extension's main interface.

function IndexPopup() {
  return (
    <div className="popup-container">
      <h1>Welcome to My Extension</h1>
      <button>Take Action</button>
    </div>
  )
}

export default IndexPopup
Enter fullscreen mode Exit fullscreen mode

PopUp

Options Page (options.tsx)

The options page provides a settings interface where users can configure your extension. Chrome automatically links to this from the extension management page.

function OptionsPage() {
  return (
    <div>
      <h1>Extension Settings</h1>
      {/* Settings form goes here */}
    </div>
  )
}

export default OptionsPage
Enter fullscreen mode Exit fullscreen mode

New Tab Page (newtab.tsx)

Optionally override Chrome's new tab page with your own custom interface.

Side Panel (sidepanel.tsx)

Create a persistent side panel interface that can stay open while users browse.

Content Scripts: Injecting UI into Web Pages

This is where the real power lies for extensions like ours at TalentSight. Content scripts allow you to inject React components and custom UI elements directly into existing web pages.

Create a content script by adding a file in the contents/ directory:

// contents/linkedin-button.tsx
import type { PlasmoCSConfig } from "plasmo"

export const config: PlasmoCSConfig = {
  matches: ["https://www.linkedin.com/*"],
  css: ["content.css"]
}

const LinkedInButton = () => {
  const handleClick = () => {
    // Your custom logic here
    console.log("Button clicked!")
  }

  return (
    <button onClick={handleClick} className="custom-button">
      Save to Talent Site
    </button>
  )
}

export default LinkedInButton
Enter fullscreen mode Exit fullscreen mode

Content scripts let you:

  • Target specific websites using URL patterns
  • Inject custom buttons and UI elements
  • Attach to existing page elements
  • Execute custom behavior based on page content

This is exactly how we embed TalentSight functionality directly into LinkedIn's interface.

Messaging API: Communicating with Your Backend

Most extensions need to communicate with a backend API to store data, fetch information, or trigger actions. Plasmo's messaging API makes this straightforward.

Setting Up Messaging

First, install the messaging dependency:

npm install @plasmohq/messaging
Enter fullscreen mode Exit fullscreen mode

Creating a Message Handler

Create a message handler in the background script:

// background/messages/api-call.ts
import type { PlasmoMessaging } from "@plasmohq/messaging"

const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
  const { body } = req

  try {
    // Make your API call
    const response = await fetch("https://api.yourbackend.com/endpoint", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body)
    })

    const data = await response.json()

    res.send({
      success: true,
      data: data
    })
  } catch (error) {
    res.send({
      success: false,
      error: error.message
    })
  }
}

export default handler
Enter fullscreen mode Exit fullscreen mode

Sending Messages from Extension Pages

Now you can call this handler from any extension page or content script:

import { sendToBackground } from "@plasmohq/messaging"

const saveToBackend = async (userData) => {
  const response = await sendToBackground({
    name: "api-call", // Must match the handler filename
    body: {
      user: userData
    }
  })

  if (response.success) {
    console.log("Data saved:", response.data)
  } else {
    console.error("Error:", response.error)
  }
}
Enter fullscreen mode Exit fullscreen mode

The standard messaging flow is perfect for one-time requests between extension components. Plasmo also supports relay and ports for more advanced use cases like streaming data or maintaining persistent connections.

Plasmo Messaging

Integrating Authentication with Clerk

If your extension connects to a platform where users are already authenticated, you don't want to force them to log in again. Clerk provides seamless authentication integration for Chrome extensions.

For a complete guide on integrating Clerk with your Plasmo extension, including syncing authentication state between your web platform and extension, see the official Clerk documentation for Chrome extensions.

The integration allows you to:

  • Automatically authenticate users who are logged into your web platform
  • Sync authentication state across browser tabs
  • Handle token refresh seamlessly
  • Provide a smooth user experience without redundant login flows

Publishing Your Extension

Once your extension is ready, you can publish it to the Chrome Web Store:

  1. Create a developer account at the Chrome Web Store Developer Dashboard
  2. Build your production bundle with npm run build
  3. Zip the contents of your build folder
  4. Upload to the Chrome Web Store with required metadata, screenshots, and privacy policy
  5. Submit for review (it can take a while, but after the first submission, next ones will be faster)

Make sure all required manifest fields are properly configured in your package.json before publishing.

Conclusion

Before using Plasmo, our extension was pure HTML,CSS and JavaScript and it was a horrible experience, it didn't match our brand guidelines and was hard to maintain.

Plasmo has transformed how we build browser extensions at TalentSight. What used to require managing complex manifest configurations and wrestling with Chrome's messaging APIs now feels as natural as building a React application.

The key advantages we've experienced:

  • Faster development with hot module replacement
  • Type-safe messaging between components
  • React components work seamlessly
  • Significantly less boilerplate code
  • Easy integration with modern tools like Clerk

Whether you're building a productivity tool, a LinkedIn integration like ours, or any other browser extension, Plasmo provides the modern developer experience you'd expect from today's frameworks.

Ready to get started? Create your first Plasmo extension and bring your web application directly to where your users work.

Additional Resources

Top comments (0)