>_DevLog

Writing Vite Plugins: A Developer Guide

2026-03-05

Vite's plugin API is built on top of Rollup's, with Vite-specific hooks added on top. This guide walks through building a plugin from scratch.

Plugin Basics

A Vite plugin is a plain object with a name and one or more hook functions:

import type { Plugin } from 'vite'

export function myPlugin(): Plugin {
  return {
    name: 'my-plugin',
    transform(code, id) {
      if (!id.endsWith('.ts')) return
      // transform source code
      return code.replace('__VERSION__', '1.0.0')
    },
  }
}

Common Hooks

Hook When it runs
config Modify the Vite config
configResolved Read the final resolved config
transform Transform a module's source
resolveId Resolve a module id to a path
load Load a virtual module
buildStart Before the build begins
generateBundle After bundles are generated

Virtual Modules

Virtual modules are files that don't exist on disk but can be imported:

const virtualId = 'virtual:config'
const resolvedId = '\0' + virtualId

export function configPlugin(): Plugin {
  return {
    name: 'virtual-config',
    resolveId(id) {
      if (id === virtualId) return resolvedId
    },
    load(id) {
      if (id === resolvedId) {
        return `export const config = ${JSON.stringify({ env: 'production' })}`
      }
    },
  }
}

Dev-Only vs Build-Only

Use apply to restrict a plugin to a specific phase:

{ apply: 'serve' }  // dev server only
{ apply: 'build' }  // production build only

Vite's plugin system is surprisingly approachable — most useful plugins are under 100 lines of code.