vitejs/vite · error · Error

Cannot send non-custom events from the client to the server.

Error message

Cannot send non-custom events from the client to the server.

What it means

Thrown by the server module runner transport's client-side send() when the payload type is not 'custom'. The server-side ModuleRunner transport only permits custom events (user/plugin-defined) from the running module back to the dev server; built-in HMR payloads like 'update'/'connected' are server-to-client only.

Source

Thrown at packages/vite/src/node/ssr/runtime/serverModuleRunner.ts:93

    }
    if (typeof options.sourcemapInterceptor === 'object') {
      return { ...prepareStackTrace, ...options.sourcemapInterceptor }
    }
    return options.sourcemapInterceptor
  }
  if (typeof process !== 'undefined' && 'setSourceMapsEnabled' in process) {
    return 'node'
  }
  return prepareStackTrace
}

export const createServerModuleRunnerTransport = (options: {
  channel: NormalizedServerHotChannel
}): ModuleRunnerTransport => {
  const hmrClient: HotChannelClient = {
    send: (payload: HotPayload) => {
      if (payload.type !== 'custom') {
        throw new Error(
          'Cannot send non-custom events from the client to the server.',
        )
      }
      options.channel.send(payload)
    },
  }

  let handler: ((data: HotPayload) => void) | undefined

  return {
    connect({ onMessage }) {
      options.channel.api!.outsideEmitter.on('send', onMessage)
      options.channel.api!.innerEmitter.emit(
        'vite:client:connect',
        undefined,
        hmrClient,
      )
      onMessage({ type: 'connected' })

View on GitHub (pinned to b4d66fee14)

Solutions

  1. Use import.meta.hot.send('my-custom-event', data) — only custom event names are allowed over this transport.
  2. Do not attempt to emit 'update'/'connected'/'prune' payloads from the runner; those are managed by Vite itself.
  3. If this fires from framework code, route the message through a custom event and have the server side listen via server.hot.on(event).

Example fix

// before
hot.send({ type: 'update', updates: [...] }) // throws
// after
hot.send('my-framework-event', { payload })
Defensive patterns

Strategy: validation

Validate before calling

// only emit custom events from the runner
hot.send('my-custom-event', data)

Type guard

function isCustomPayload(p: HotPayload): boolean {
  return p.type === 'custom'
}

Prevention

When it happens

Trigger: Code in an SSR module runner calling something that emits a non-custom HotPayload (e.g. import.meta.hot.send with a reserved type, or an internal HMR API misused). The guard is `if (payload.type !== 'custom')` at serverModuleRunner.ts:92.

Common situations: A plugin or framework trying to push HMR update/connected payloads from the runner to the server. Misusing import.meta.hot.send (which is meant for custom events) with a built-in payload type. Internal Vite bug where a non-custom payload is routed through this transport.

Related errors


AI-assisted analysis of vitejs/vite@b4d66fee14 (2026-08-11). Data as JSON: /api/errors/1b46a0d06411dfce. Report an issue: GitHub.