evanw/esbuild · error

[%s] %q filter is not a valid Go regular expression: %q

Error message

[%s] %q filter is not a valid Go regular expression: %q

What it means

Returned by config.CompileFilterForPlugin when a plugin's OnResolve/OnLoad filter is a non-empty string that fails Go's regexp.Compile (RE2 syntax). esbuild precompiles filters for performance and caches them, so a syntactically bad regex is caught at plugin-registration time. The offending filter is echoed in the message.

Source

Thrown at internal/config/config.go:763

	// Cache for next time
	filterMutex.Lock()
	defer filterMutex.Unlock()
	if filterCache == nil {
		filterCache = make(map[string]*regexp.Regexp)
	}
	filterCache[filter] = result
	return
}

func CompileFilterForPlugin(pluginName string, kind string, filter string) (*regexp.Regexp, error) {
	if filter == "" {
		return nil, fmt.Errorf("[%s] %q is missing a filter", pluginName, kind)
	}

	result := compileFilter(filter)
	if result == nil {
		return nil, fmt.Errorf("[%s] %q filter is not a valid Go regular expression: %q", pluginName, kind, filter)
	}

	return result, nil
}

func PluginAppliesToPath(path logger.Path, filter *regexp.Regexp, namespace string) bool {
	return (namespace == "" || path.Namespace == namespace) && filter.MatchString(path.Text)
}

////////////////////////////////////////////////////////////////////////////////
// Plugin API

type Plugin struct {
	Name      string
	OnStart   []OnStart
	OnResolve []OnResolve
	OnLoad    []OnLoad
}

View on GitHub (pinned to 6ff1d8b0d8)

Solutions

  1. Rewrite the regex in RE2-compatible syntax (no lookahead, no lookbehind, no backreferences).
  2. Test the pattern with Go's regexp playground or `regexp.Compile` before passing it as a filter.
  3. If you need lookahead semantics, move that logic inside the callback and use a broader filter.
  4. Validate filter strings at plugin-author time with a unit test that compiles them.

Example fix

// before
b.onResolve({ filter: 'foo(?=bar)' }, fn)  // lookahead unsupported by RE2

// after
b.onResolve({ filter: 'foobar' }, fn)  // match literally; or do lookahead inside fn
Defensive patterns

Strategy: validation

Validate before calling

// Pre-compile the filter with Go-compatible RE2 semantics where possible.
// In Node there is no RE2 by default, but you can at least sanity-check balance:
function looksLikeBalancedRegex(s) {
  let depth = 0
  for (const ch of s) {
    if (ch === '(') depth++
    if (ch === ')') depth--
    if (depth < 0) return false
  }
  return depth === 0
}
if (!looksLikeBalancedRegex(filter)) throw new Error('Filter looks unbalanced: ' + filter)

Type guard

function isSafeEsbuildFilter(s: string): boolean {
  return s.length > 0 && !/(\(\?<=)|(\(\?=)|(\(\?!)|(\(\?<!)|\\\d)/.test(s)
}

Try / catch

try {
  await esbuild.build({ plugins })
} catch (e) {
  if (/not a valid Go regular expression/i.test(e.message)) {
    console.error('Plugin filter is not RE2-compatible:', e.message)
    // identify the offending plugin via the bracketed [name] prefix
  }
  throw e
}

Prevention

When it happens

Trigger: Pass a filter containing PCRE-only constructs unsupported by RE2 (e.g. lookahead '(?=...)', backreferences, or an unbalanced parenthesis). Examples: filter: 'foo(bar', filter: '(?<=x)', filter: '[z-a]' (invalid range). Fires for OnResolve and OnLoad registrations in JS or Go plugin setup.

Common situations: Copying a JavaScript RegExp literal into the filter string that uses lookbehind/lookahead (JS supports these, Go RE2 does not); pasting a regex from a Stack Overflow answer that uses backreferences; version differences where a previously-tolerant engine accepted the pattern but RE2 rejects it.

Related errors


AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03). Data as JSON: /data/errors/306d1a9a1d5a4384.json. Report an issue: GitHub.