evanw/esbuild · error
Invalid path suffix %q returned from plugin (must start with
Error message
Invalid path suffix %q returned from plugin (must start with "?" or "#")
What it means
Returned from esbuild's internal OnResolve dispatch (pkg/api/api_impl.go) when a plugin's OnResolve callback sets a Suffix whose first character is neither '?' nor '#'. esbuild restricts path suffixes to query-string ('?...') or fragment ('#...') prefixes because that is the only form it can attach to a resolved path unambiguously. Any other suffix is rejected as a contract violation by the plugin.
Source
Thrown at pkg/api/api_impl.go:1954
Filter: filter,
Namespace: options.Namespace,
Callback: func(args config.OnResolveArgs) (result config.OnResolveResult) {
response, err := callback(OnResolveArgs{
Path: args.Path,
Importer: args.Importer.Text,
Namespace: args.Importer.Namespace,
ResolveDir: args.ResolveDir,
Kind: importKindToResolveKind(args.Kind),
PluginData: args.PluginData,
With: args.With.DecodeIntoMap(),
})
result.PluginName = response.PluginName
result.AbsWatchFiles = impl.validatePathsArray(response.WatchFiles, "watch file")
result.AbsWatchDirs = impl.validatePathsArray(response.WatchDirs, "watch directory")
// Restrict the suffix to start with "?" or "#" for now to match esbuild's behavior
if err == nil && response.Suffix != "" && response.Suffix[0] != '?' && response.Suffix[0] != '#' {
err = fmt.Errorf("Invalid path suffix %q returned from plugin (must start with \"?\" or \"#\")", response.Suffix)
}
if err != nil {
result.ThrownError = err
return
}
result.Path = logger.Path{
Text: response.Path,
Namespace: response.Namespace,
IgnoredSuffix: response.Suffix,
}
result.External = response.External
result.IsSideEffectFree = response.SideEffects == SideEffectsFalse
result.PluginData = response.PluginData
// Convert log messages
result.Msgs = convertErrorsAndWarningsToInternal(response.Errors, response.Warnings)View on GitHub (pinned to 6ff1d8b0d8)
Solutions
- Ensure your plugin's OnResolve only ever returns a Suffix beginning with '?' (query) or '#' (fragment).
- If you need to attach extra data, encode it as a query string: Suffix: '?foo=bar'.
- Strip any leading non-?/# characters from the suffix before returning it.
- Add a unit test in your plugin asserting the suffix starts with '?' or '#'.
Example fix
// before
onResolve({ filter: /.*/ }, args => ({
path: args.path,
suffix: '.cache' // invalid
}));
// after
onResolve({ filter: /.*/ }, args => ({
path: args.path,
suffix: '?cache=1' // valid query suffix
})); Defensive patterns
Strategy: validation
Validate before calling
function validSuffix(s) { return s === '' || s[0] === '?' || s[0] === '#'; }
// in plugin:
const suffix = computeSuffix();
if (!validSuffix(suffix)) throw new Error('suffix must start with ? or #');
onResolveResult({ suffix }); Type guard
function isValidSuffix(s) { return typeof s === 'string' && (s.length === 0 || s[0] === '?' || s[0] === '#'); } Prevention
- Plugin authors: only return suffixes starting with '?' or '#'.
- Add unit tests asserting suffix format.
- Encode extra data as a query string, never as an arbitrary string.
- Validate suffix before returning from OnResolve.
When it happens
Trigger: A plugin's OnResolve result returns response.Suffix that is non-empty and does not start with '?' or '#'. The check at api_impl.go:1953 fires: err = fmt.Errorf("Invalid path suffix %q ..."). This is purely a plugin-author error, not a user-config error.
Common situations: A plugin author accidentally sets Suffix to something like '.js' or '?v=1#extra' starting with the wrong char; passing a full query incorrectly; copying the path (including its suffix) into the Suffix field instead of just the query/fragment portion; a plugin built against an older esbuild that had looser suffix handling.
Related errors
- onResolve() call is missing a filter
- Expected onResolve() callback in plugin ${quote(name)} to re
- The service was stopped
- Invalid serve path: %s
- Invalid fallback path: %s
AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03).
Data as JSON: /data/errors/1cab14cb54d00948.json.
Report an issue: GitHub.