Skip to content

WXT Template

The WXT template provides a modern browser extension development framework with Vue 3, TypeScript, and Vite, perfect for building cross-browser extensions with excellent developer experience.

Technical Stack

  • WXT - Browser extension development framework
  • Vue 3 - Progressive JavaScript framework
  • TypeScript - Type-safe development
  • Vite - Fast build tool and dev server
  • Vue Router - Client-side routing
  • Pinia - State management library
  • Vue i18n - Internationalization
  • Tailwind CSS - Utility-first CSS framework

Quick Start

1. Create Project

bash
# Initialize project
vup init my-extension-project
cd my-extension-project

# Add WXT template
vup add my-extension

2. Install Dependencies

bash
# Install dependencies
pnpm install

3. Start Development

bash
# Start development server
cd apps/my-extension
pnpm dev

The extension will be available in the browser with hot reload support.

Project Structure

apps/my-extension/
├── src/
│   ├── assets/           # Static assets
│   │   ├── vue.svg       # Logo asset
│   │   └── images/
│   │       └── hero-logo.svg
│   ├── entrypoints/      # Extension entry points
│   │   ├── background.ts # Background script
│   │   ├── content.ts    # Content script
│   │   ├── newtab/       # New tab page
│   │   │   ├── index.html
│   │   │   ├── App.vue
│   │   │   └── main.ts
│   │   ├── options/      # Options page
│   │   │   ├── index.html
│   │   │   ├── App.vue
│   │   │   └── main.ts
│   │   └── popup/        # Popup page
│   │       ├── index.html
│   │       ├── App.vue
│   │       └── main.ts
│   └── manifest.ts       # Extension manifest
├── public/               # Public assets
│   ├── icon/             # Extension icons
│   │   ├── 16.png
│   │   ├── 32.png
│   │   ├── 48.png
│   │   ├── 96.png
│   │   └── 128.png
│   └── wxt.svg           # Favicon/logo
├── package.json          # Dependencies and scripts
├── tsconfig.json         # TypeScript configuration
└── wxt.config.ts         # WXT configuration

Core Features

Multiple Entry Points

Background Script

typescript
// src/entrypoints/background.ts

export default defineBackground(() => {
  console.log('Background script loaded');

  // Listen for extension installation
  chrome.runtime.onInstalled.addListener((details) => {
    console.log('Extension installed:', details);
  });

  // Listen for messages from content scripts
  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    console.log('Message received:', message);
    sendResponse({ status: 'success' });
  });
});

Content Script

typescript
// src/entrypoints/content.ts

export default defineContentScript({
  matches: ['<all_urls>'],
  main() {
    console.log('Content script loaded');

    // Inject content into the page
    const div = document.createElement('div');
    div.textContent = 'Hello from WXT!';
    div.style.cssText = `
      position: fixed;
      top: 10px;
      right: 10px;
      background: #007aff;
      color: white;
      padding: 10px;
      border-radius: 5px;
      z-index: 10000;
    `;
    document.body.appendChild(div);
  },
});
vue
<!-- src/entrypoints/popup/App.vue -->
<template>
  <div class="popup-container">
    <h1>{{ title }}</h1>
    <p>{{ description }}</p>
    <button @click="handleClick" class="btn">
      {{ buttonText }}
    </button>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const title = ref('WXT Extension');
const description = ref('A modern browser extension');
const buttonText = ref('Click Me');

const handleClick = () => {
  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    if (tabs[0]?.id) {
      chrome.tabs.sendMessage(tabs[0].id, { action: 'hello' });
    }
  });
};
</script>

<style scoped>
.popup-container {
  @apply w-80 p-4 bg-white;
}

.btn {
  @apply w-full px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600;
}
</style>

Options Page

vue
<!-- src/entrypoints/options/App.vue -->
<template>
  <div class="options-container">
    <h1>Extension Settings</h1>
    <form @submit.prevent="saveSettings">
      <div class="form-group">
        <label for="theme">Theme:</label>
        <select v-model="settings.theme" id="theme">
          <option value="light">Light</option>
          <option value="dark">Dark</option>
        </select>
      </div>
      <div class="form-group">
        <label for="notifications">Enable Notifications:</label>
        <input
          v-model="settings.notifications"
          type="checkbox"
          id="notifications"
        />
      </div>
      <button type="submit" class="btn">Save Settings</button>
    </form>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';

interface Settings {
  theme: string;
  notifications: boolean;
}

const settings = ref<Settings>({
  theme: 'light',
  notifications: true,
});

onMounted(() => {
  // Load settings from storage
  chrome.storage.sync.get(['theme', 'notifications'], (result) => {
    settings.value = {
      theme: result.theme || 'light',
      notifications: result.notifications !== false,
    };
  });
});

const saveSettings = () => {
  chrome.storage.sync.set(settings.value, () => {
    console.log('Settings saved');
  });
};
</script>

<style scoped>
.options-container {
  @apply max-w-2xl mx-auto p-6;
}

.form-group {
  @apply mb-4;
}

.form-group label {
  @apply block mb-2 font-medium;
}

.form-group select,
.form-group input[type='checkbox'] {
  @apply w-full p-2 border rounded;
}

.btn {
  @apply px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600;
}
</style>

New Tab Page

vue
<!-- src/entrypoints/newtab/App.vue -->
<template>
  <div class="newtab-container">
    <div class="hero">
      <h1>{{ title }}</h1>
      <p>{{ description }}</p>
    </div>
    <div class="features">
      <div v-for="feature in features" :key="feature.title" class="feature">
        <h3>{{ feature.title }}</h3>
        <p>{{ feature.description }}</p>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const title = ref('WXT Extension');
const description = ref('A modern browser extension with Vue 3');

const features = ref([
  {
    title: 'Modern Framework',
    description: 'Built with Vue 3 and TypeScript',
  },
  {
    title: 'Fast Development',
    description: 'Hot reload and instant feedback',
  },
  {
    title: 'Cross-browser',
    description: 'Works on Chrome, Firefox, and Edge',
  },
]);
</script>

<style scoped>
.newtab-container {
  @apply min-h-screen bg-gray-100 p-8;
}

.hero {
  @apply text-center mb-12;
}

.hero h1 {
  @apply text-4xl font-bold text-gray-900 mb-4;
}

.hero p {
  @apply text-xl text-gray-600;
}

.features {
  @apply grid grid-cols-1 md:grid-cols-3 gap-8;
}

.feature {
  @apply bg-white p-6 rounded-lg shadow-md;
}

.feature h3 {
  @apply text-xl font-semibold text-gray-900 mb-2;
}

.feature p {
  @apply text-gray-600;
}
</style>

Extension Manifest

typescript
// src/manifest.ts
import { defineManifest } from '@wxt/sdk';

export default defineManifest({
  manifest_version: 3,
  name: 'WXT Extension',
  version: '1.0.0',
  description: 'A modern browser extension built with WXT',
  permissions: ['storage', 'tabs', 'activeTab'],
  host_permissions: ['<all_urls>'],
  action: {
    default_popup: '/popup.html',
    default_title: 'WXT Extension',
  },
  background: {
    service_worker: '/background.js',
  },
  content_scripts: [
    {
      matches: ['<all_urls>'],
      js: ['/content.js'],
    },
  ],
  web_accessible_resources: [
    {
      resources: ['icon/*.png'],
      matches: ['<all_urls>'],
    },
  ],
  icons: {
    16: 'icon/16.png',
    32: 'icon/32.png',
    48: 'icon/48.png',
    96: 'icon/96.png',
    128: 'icon/128.png',
  },
});

Development Tools

WXT Configuration

typescript
// wxt.config.ts
import { defineConfig } from 'wxt';

export default defineConfig({
  manifest: {
    name: 'WXT Extension',
    version: '1.0.0',
    description: 'A modern browser extension built with WXT',
  },
  modules: ['vue'],
  vue: {
    // Vue-specific configuration
  },
  runner: {
    // Development server configuration
    startUrls: ['https://example.com'],
  },
  build: {
    // Build configuration
    outDir: '.output',
  },
});

TypeScript Configuration

json
// tsconfig.json
{
  "extends": ["./.wxt/tsconfig.json"]
}

Available Scripts

json
{
  "scripts": {
    "dev": "wxt",
    "dev:firefox": "wxt -b firefox",
    "build": "wxt build",
    "build:firefox": "wxt build -b firefox",
    "zip": "wxt zip",
    "zip:firefox": "wxt zip -b firefox",
    "postinstall": "wxt prepare",
    "lint": "eslint src/ --ext .vue,.ts,.js",
    "lint:fix": "eslint src/ --ext .vue,.ts,.js --fix",
    "format": "prettier --write \"src/**/*.{js,ts,vue,json,css,scss}\"",
    "format:check": "prettier --check \"src/**/*.{js,ts,vue,json,css,scss}\""
  }
}

Build and Deployment

Development Build

bash
cd apps/my-extension

# Build for development
pnpm build

# The built files will be in the .output directory

Production Build

bash
cd apps/my-extension

# Build for specific browser
pnpm build
pnpm build:firefox

# Create ZIP package
pnpm zip

Browser Installation

  1. Chrome/Edge: Load unpacked extension from .output directory
  2. Firefox: Load temporary add-on from .output directory
  3. Production: Upload ZIP file to respective web stores