How to Build and Use Plugins in Vite
  Creating custom Vite plugins lets you extend your build process beyond the default configuration—whether you need to transform custom file types, inject build-time logic, or add development server middleware. If you’ve hit the limits of standard Vite configuration, building your own plugin is the natural next step.
This guide covers the essential concepts: how Vite’s Plugin API works, the key lifecycle hooks you’ll use, and practical examples to get you started. You’ll learn to create, test, and publish plugins that solve real-world problems in your development workflow.
Key Takeaways
- Vite plugins extend both development and production build processes through lifecycle hooks
 - Most Rollup plugins work in Vite, but dev server features require Vite-specific hooks
 - Start with simple transformations before moving to advanced patterns like virtual modules
 - Follow naming conventions and documentation standards when publishing plugins
 
Understanding Vite Plugins and Their Relationship to Rollup
Vite plugins are JavaScript objects that hook into different stages of the development and build process. They’re built on Rollup’s plugin system, with additional hooks specific to Vite’s development server.
Vite uses two different build tools under the hood: esbuild powers the lightning-fast development server with native ES modules, while Rollup handles production bundling for optimal output. This dual architecture means your plugins can target specific environments using the apply option:
function myPlugin(): Plugin {
  return {
    name: 'my-plugin',
    apply: 'serve', // Only runs during development
    // ... hooks
  }
}
Most Rollup plugins work in Vite without modification. However, if you need dev server functionality—like adding middleware or modifying HTML during development—you’ll need Vite-specific hooks.
Basic Plugin Structure and Configuration
Every Vite plugin must have a name property and at least one hook function:
import type { Plugin } from 'vite'
function myPlugin(options?: MyPluginOptions): Plugin {
  return {
    name: 'vite-plugin-example',
    enforce: 'pre', // Optional: control plugin ordering
    apply: 'build', // Optional: 'serve' | 'build' | undefined
    
    // Hook functions go here
    transform(code, id) {
      if (id.endsWith('.custom')) {
        return transformCustomFile(code)
      }
    }
  }
}
The Plugin API provides TypeScript definitions for all hooks and options. Import the Plugin type from ‘vite’ for full type safety and autocompletion.
Essential Lifecycle Hooks in the Plugin API
Different hooks fire at different stages of the build process. Here are the most commonly used ones:
Configuration Hooks
config: Modify Vite config before it’s resolvedconfigResolved: Access the final resolved config
Transform Hooks
transform: Modify source code of individual modulesload: Custom loading logic for specific file typesresolveId: Custom module resolution
{
  name: 'transform-plugin',
  transform(code, id) {
    if (!id.includes('node_modules') && id.endsWith('.js')) {
      return {
        code: addCustomHeader(code),
        map: null // Source map support
      }
    }
  }
}
Server and Build Hooks
configureServer: Add custom middleware to the dev servertransformIndexHtml: Modify HTML fileswriteBundle: Run after the bundle is written to disk
Each hook has specific return types and execution timing. The transform hook runs for every module, so keep it fast. Use load for expensive operations on specific files.
Discover how at OpenReplay.com.
Building Your First Vite Plugin: Practical Examples
Let’s build three practical plugins that demonstrate common patterns:
Example 1: HTML Modification
function htmlPlugin(): Plugin {
  return {
    name: 'html-modifier',
    transformIndexHtml(html) {
      return html.replace(
        '</head>',
        '<script>console.log("Injected!")</script></head>'
      )
    }
  }
}
Example 2: Dev Server Middleware
function analyticsPlugin(): Plugin {
  return {
    name: 'request-analytics',
    configureServer(server) {
      server.middlewares.use((req, res, next) => {
        console.log(`${req.method} ${req.url}`)
        next()
      })
    }
  }
}
Example 3: Build-Time File Generation
import path from 'path'
import fs from 'fs'
import type { Plugin, ResolvedConfig } from 'vite'
function generateManifest(): Plugin {
  let config: ResolvedConfig
  
  return {
    name: 'generate-manifest',
    configResolved(resolvedConfig) {
      config = resolvedConfig
    },
    writeBundle() {
      const manifestPath = path.join(config.build.outDir, 'manifest.json')
      fs.writeFileSync(manifestPath, JSON.stringify({
        timestamp: Date.now(),
        version: process.env.npm_package_version
      }))
    }
  }
}
For debugging, use console.log in your hooks to trace execution. The this.warn() and this.error() methods provide better error reporting than throwing exceptions.
Advanced Plugin Patterns and Best Practices
Virtual Modules
Create modules that don’t exist on disk:
const virtualModuleId = 'virtual:my-module'
const resolvedVirtualModuleId = '\0' + virtualModuleId
export function virtualPlugin(): Plugin {
  return {
    name: 'virtual-plugin',
    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId
      }
    },
    load(id) {
      if (id === resolvedVirtualModuleId) {
        return `export const msg = "from virtual module"`
      }
    }
  }
}
Performance Optimization
Cache expensive transformations:
const cache = new Map<string, string>()
function cachedTransform(): Plugin {
  return {
    name: 'cached-transform',
    transform(code, id) {
      if (cache.has(id)) return cache.get(id)
      
      const result = expensiveOperation(code)
      cache.set(id, result)
      return result
    }
  }
}
Test your Vite plugins using Vitest with the createServer API to verify hook behavior in isolation.
Publishing and Sharing Your Plugin
Follow these conventions when publishing:
- Name your package 
vite-plugin-[name] - Include 
"vite-plugin"in package.json keywords - Document why it’s Vite-specific (if using Vite-only hooks)
 - Add framework prefixes if framework-specific: 
vite-plugin-vue-[name] 
Create a clear README with installation instructions, configuration examples, and API documentation. Submit your plugin to awesome-vite to reach the community.
Conclusion
Building custom Vite plugins gives you complete control over your build process. Start with simple transformations using hooks like transform or transformIndexHtml, then expand to more complex patterns as needed. The Plugin API is powerful yet approachable—most plugins require just a few hooks to solve specific problems.
For deeper exploration, check the official Vite plugin documentation and study popular plugins in the ecosystem. Your next build optimization or workflow improvement might be just a plugin away.
FAQs
Yes, most Rollup plugins work with Vite without modification. However, plugins that rely on moduleParsed hook or need dev server functionality require Vite-specific adaptations. Check the plugin documentation for Vite compatibility notes.
Use console.log statements in your hooks to trace execution flow. The this.warn() and this.error() methods provide better error reporting. You can also use the DEBUG environment variable with Vite to see detailed logs.
The enforce option controls plugin execution order. Pre plugins run before Vite core plugins, post plugins run after. Use pre for input transformations and post for output modifications. Without enforce, plugins run in the order they appear.
Consider publishing if your plugin solves a problem others might face, even if specialized. Document its specific use case clearly. For truly project-specific plugins, keep them local or in a private registry instead.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.