# What Is the make-manifest-plugin in the Vite Configuration?

> Discover the make-manifest-plugin in Vite. Learn how it dynamically generates manifest.json, injects hot-reload scripts, and monitors manifest.js for Chrome extension builds. Optimize your workflow today.

- Repository: [JongHak Seo/chrome-extension-boilerplate-react-vite](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite)
- Tags: tutorial
- Published: 2026-03-05

---

**The `make-manifest-plugin` is a custom Vite plugin that dynamically generates the Chrome extension's [`manifest.json`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.json) file at build time, injects a development-only hot-reload script, and watches the source [`manifest.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.js) for changes.**

In the `jonghakseo/chrome-extension-boilerplate-react-vite` repository, the `make-manifest-plugin` serves as the critical bridge between your editable JavaScript manifest definition and the static JSON file required by Chrome browsers. This plugin handles cache-busting, file watching, and environment-specific script injection to streamline the extension development workflow.

## How make-manifest-plugin Works

The plugin operates through five distinct stages during the Vite build lifecycle, each implemented in [`chrome-extension/utils/plugins/make-manifest-plugin.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/chrome-extension/utils/plugins/make-manifest-plugin.ts).

### Watching the Source Manifest File

During the `buildStart()` hook, the plugin registers the source [`manifest.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.js) file with Vite's watcher. This ensures that any modification to the manifest definition triggers an automatic rebuild.

In `chrome-extension/vite.config.mts`, the plugin is instantiated with an `outDir` parameter:

```ts
makeManifestPlugin({ outDir })

```

The plugin implementation adds the file to the watch list via `this.addWatchFile(manifestFile)` (lines 75-76 in [`make-manifest-plugin.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/make-manifest-plugin.ts)).

### Cache-Busting Manifest Imports

To prevent stale Node.js module caches during development, the plugin imports the manifest definition using a timestamp query string. The `getManifestWithCacheBurst()` function (lines 27-38) dynamically constructs the import path:

```ts
const manifest = await import(`${manifestFile}?${Date.now()}`);

```

This guarantees that every build incorporates the latest manifest changes without requiring a manual Vite server restart.

### Injecting the Development Refresh Script

When the `IS_DEV` environment variable is true, the plugin automatically injects a [`refresh.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/refresh.js) content script into the manifest's `content_scripts` array. The `addRefreshContentScript()` function (lines 41-47) appends this entry, while `makeManifest()` (lines 57-60) orchestrates the injection.

This script enables hot-module reloading (HMR) by establishing a WebSocket connection to the Vite dev server, allowing instant updates to content scripts and background pages without manual extension reloading.

### Writing the Final manifest.json

After processing the manifest object, the plugin serializes it to JSON using `ManifestParser.convertManifestToString()`. This utility (provided by `@extension/dev-utils`) handles browser-specific quirks, particularly for Firefox compatibility.

The final file is written synchronously to the output directory:

```ts
writeFileSync(manifestPath, ManifestParser.convertManifestToString(manifest, color));

```

### Copying the HMR Helper in Development

In development mode, the plugin copies the HMR helper script from [`packages/hmr/dist/lib/injections/refresh.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/hmr/dist/lib/injections/refresh.js) into the extension's output folder. Lines 63-67 wrap this script in an IIFE that tags it with a unique HMR ID, ensuring the refresh logic executes correctly within the Chrome extension context.

## Configuring make-manifest-plugin in Your Vite Setup

The plugin integrates directly into the Vite configuration at `chrome-extension/vite.config.mts`. Import the plugin and conditionally include development plugins as needed:

```ts
import { defineConfig } from 'vite';
import makeManifestPlugin from './utils/plugins/make-manifest-plugin.js';
import { IS_DEV } from '@extension/env';

const outDir = /* path to dist folder */;

export default defineConfig({
  plugins: [
    // …other plugins
    makeManifestPlugin({ outDir }),               // ← generates manifest.json
    IS_DEV && watchRebuildPlugin({ reload: true }) // optional HMR
  ],
  // …rest of the config
});

```

To customize the output location, pass a custom `outDir` parameter:

```ts
makeManifestPlugin({ outDir: resolve(__dirname, '../custom-dist') })

```

## Development vs. Production Output

The plugin produces different [`manifest.json`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.json) outputs depending on the build environment.

**Source Definition ([`manifest.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.js)):**

```js
export default {
  manifest_version: 3,
  name: "My Extension",
  version: "1.0.0",
  // …other fields
};

```

**Development Build (`npm run dev`):**

The plugin injects the [`refresh.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/refresh.js) content script:

```json
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "content_scripts": [
    {
      "matches": ["http://*/*", "https://*/*", "<all_urls>"],
      "js": ["refresh.js"]
    }
  ]
}

```

**Production Build (`npm run build`):**

The HMR script is omitted when `IS_DEV` is false:

```json
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0"
}

```

## Summary

- **The `make-manifest-plugin` generates the static [`manifest.json`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.json) required by Chrome from your dynamic [`manifest.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.js) source file.**
- **It watches the source file for changes using `this.addWatchFile()` in the `buildStart()` hook, triggering automatic rebuilds.**
- **Cache-busting via timestamp query strings ensures fresh manifest imports during development.**
- **In development mode, it injects [`refresh.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/refresh.js) from [`packages/hmr/dist/lib/injections/refresh.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/hmr/dist/lib/injections/refresh.js) and copies the HMR helper to enable live reloading.**
- **The plugin uses `ManifestParser.convertManifestToString()` to handle browser-specific serialization quirks before writing to disk.**

## Frequently Asked Questions

### What file does make-manifest-plugin watch for changes?

The plugin watches the source [`manifest.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.js) file located in the `chrome-extension` directory. In the `buildStart()` hook (lines 75-76 of [`make-manifest-plugin.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/make-manifest-plugin.ts)), it calls `this.addWatchFile(manifestFile)` to register the file with Vite's watcher, ensuring any modification triggers a rebuild.

### How does make-manifest-plugin handle hot module reloading?

During development, when `IS_DEV` is true, the plugin calls `addRefreshContentScript()` (lines 41-47) to inject a [`refresh.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/refresh.js) entry into the manifest's `content_scripts` array. It also copies the actual HMR helper script from [`packages/hmr/dist/lib/injections/refresh.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/hmr/dist/lib/injections/refresh.js) into the output directory, wrapped in an IIFE with a unique HMR ID.

### Where is the final manifest.json written?

The plugin writes the final [`manifest.json`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.json) to the directory specified by the `outDir` parameter passed to `makeManifestPlugin()`. The write operation occurs via `writeFileSync()` at line 61 of [`make-manifest-plugin.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/make-manifest-plugin.ts), using the path constructed by joining `outDir` with the filename [`manifest.json`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.json).

### Can I customize the output directory for manifest.json?

Yes, you can customize the output directory by passing a custom `outDir` option when instantiating the plugin in `chrome-extension/vite.config.mts`. For example: `makeManifestPlugin({ outDir: resolve(__dirname, '../custom-dist') })`. The plugin will emit both [`manifest.json`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.json) and the development [`refresh.js`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/refresh.js) (when applicable) to this location.