How to Leverage the Shared Package for Common Code and Types in Chrome Extensions

The @extension/shared workspace provides a centralized library of utilities, TypeScript definitions, React hooks, and higher-order components that you can import into any Chrome extension entry point using the @extension/shared path alias.

The chrome-extension-boilerplate-react-vite repository uses a monorepo architecture where the packages/shared directory contains code reused across popups, content scripts, side panels, and background workers. By consolidating common logic into this dedicated package, you eliminate duplication and ensure consistent behavior throughout your extension.

Configuring the @extension/shared Path Alias

The boilerplate establishes a project-wide alias that maps @extension/shared to the packages/shared/src directory. This configuration exists in the root tsconfig.json and is inherited by every package.

In packages/shared/tsconfig.json, the compiler resolves the alias as follows:

{
  "compilerOptions": {
    "paths": {
      "@extension/shared": ["../shared/src"]
    }
  }
}

Because both TypeScript and Vite recognize this mapping, you can import symbols using the absolute alias rather than brittle relative paths like ../../../../packages/shared. The build system tree-shakes unused exports, ensuring each entry point bundles only the code it actually references.

Core Utilities and Constants

The shared package exports helper functions and constants from packages/shared/lib/utils/ and packages/shared/const.ts.

Colorful Logger and Constants
lib/utils/colorful-logger.ts provides a styled logging utility, while const.ts defines the COLORS palette and related ColorType. These ensure consistent visual feedback across development and production builds.

Shadow DOM Initialization
For content scripts that inject UI into web pages, lib/utils/init-app-with-shadow.ts exports initAppWithShadow. This function creates an isolated Shadow DOM root, protecting your React components from the host page's CSS and JavaScript.

Global Type Definitions

Type safety across extension components relies on centralized type definitions in packages/shared/lib/utils/types.ts.

ManifestType and ColorType
These interfaces enforce valid Chrome Extension Manifest V3 structures and restrict color prop values to the predefined palette:

import type { ManifestType, ColorType } from '@extension/shared';

const manifest: ManifestType = {
  manifest_version: 3,
  name: 'My Extension',
  version: '1.0.0',
};

type BadgeProps = {
  severity: ColorType; // 'success' | 'info' | 'error' | 'warning'
};

By importing these types into your popup, options page, and content scripts, you prevent type drift when the manifest schema changes.

React Hooks for Extension State

The useStorage hook in packages/shared/lib/hooks/use-storage.tsx bridges React 18's concurrent features with the extension's storage API.

Synchronizing with Chrome Storage
This hook accepts any storage object implementing BaseStorageType<T> from @extension/storage and returns a reactive value:

import { useStorage } from '@extension/shared';
import { exampleThemeStorage } from '@extension/storage';

export default function ThemeDisplay() {
  const theme = useStorage(exampleThemeStorage);
  
  return <div>Current theme: {theme}</div>;
}

The hook handles subscription cleanup and state synchronization automatically, eliminating boilerplate in individual components.

Higher-Order Components for Error Handling

Consistent error boundaries and loading states are critical for extension UI reliability. The shared package provides two HOCs in packages/shared/lib/hoc/.

withSuspense
Wraps components to provide a default fallback UI during lazy loading, implemented in with-suspense.tsx.

withErrorBoundary
Catches JavaScript errors in child components and displays a recovery UI, defined in with-error-boundary.tsx.

Compose them to add production-ready error handling:

import { withErrorBoundary, withSuspense } from '@extension/shared';
import MyComponent from './MyComponent';

export default withErrorBoundary(
  withSuspense(MyComponent, <div>Loading...</div>),
  <div>Error loading component</div>
);

Practical Implementation Examples

Accessing Storage in Content Scripts

Combine the shared hook with the storage package to persist user preferences:

// pages/content-ui/src/App.tsx
import { useStorage } from '@extension/shared';
import { exampleThemeStorage } from '@extension/storage';

export default function App() {
  const theme = useStorage(exampleThemeStorage);
  return <div className={theme}>Content UI</div>;
}

Mounting Isolated UI with Shadow DOM

When injecting React into arbitrary web pages, use initAppWithShadow to avoid CSS conflicts:

import { initAppWithShadow } from '@extension/shared';
import App from './App';

initAppWithShadow(App, {
  style: `body { margin: 0; font-family: system-ui; }`,
});

This function, defined in packages/shared/lib/utils/init-app-with-shadow.ts, creates a hidden container, attaches a Shadow Root, injects the optional stylesheet, and renders your component inside the isolated DOM.

Reusing Types Across Packages

Import shared types into your storage implementations or page components to maintain consistency:

// packages/storage/lib/impl/example-theme-storage.ts
import type { ManifestType } from '@extension/shared';

// Ensure storage keys match manifest permissions
const manifest: ManifestType = chrome.runtime.getManifest() as ManifestType;

Real-World Usage Patterns

The boilerplate demonstrates shared package consumption across multiple entry points:

All re-exports flow through packages/shared/index.mts, which aggregates exports from lib/hooks/index.js, lib/hoc/index.js, lib/utils/index.js, and const.js.

Summary

  • Import using @extension/shared to access utilities without relative path traversal.
  • Use useStorage to synchronize React components with Chrome storage areas using React 18 concurrent features.
  • Wrap components with withSuspense and withErrorBoundary to standardize loading and error states across all extension pages.
  • Mount content script UI via initAppWithShadow to isolate your CSS from the host website.
  • Reference ManifestType and ColorType from lib/utils/types.ts to enforce consistent typing for manifest data and theme values.

Frequently Asked Questions

How do I add a new utility function to the shared package?

Create the function in the appropriate subdirectory of packages/shared/lib/ (e.g., utils/, hooks/), then export it from the corresponding index.js file. Finally, ensure packages/shared/index.mts re-exports the symbol so consumers can import it via @extension/shared.

Can I import @extension/shared into the background service worker?

Yes. The path alias works in any TypeScript file within the monorepo, including pages/background/src/. However, avoid importing React-specific exports like useStorage or withSuspense into the service worker context, as they depend on the React runtime which is unavailable in background scripts. Import only utility functions and types.

Does using the shared package increase the bundle size of every entry point?

No. The Vite build configuration tree-shakes unused exports during the compilation of each entry point (popup, content script, etc.). Only the specific functions, types, and components you import are included in the final bundle, resulting in zero runtime cost for unused shared code.

Where should I define shared constants like API endpoints or feature flags?

Place shared constants in packages/shared/const.ts or create a new file in packages/shared/lib/utils/ and export it through the package index. This ensures all extension components reference the same values, preventing configuration drift between the popup and content scripts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →