{"record":{"id":"1cab14cb54d00948","repo":"evanw/esbuild","slug":"invalid-path-suffix-q-returned-from-plugin-must","errorCode":null,"errorMessage":"Invalid path suffix %q returned from plugin (must start with \"?\" or \"#\")","messagePattern":"Invalid path suffix %q returned from plugin \\(must start with \"\\?\" or \"#\"\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/api/api_impl.go","lineNumber":1979,"sourceCode":"\t\tFilter:    filter,\n\t\tNamespace: options.Namespace,\n\t\tCallback: func(args config.OnResolveArgs) (result config.OnResolveResult) {\n\t\t\tresponse, err := callback(OnResolveArgs{\n\t\t\t\tPath:       args.Path,\n\t\t\t\tImporter:   args.Importer.Text,\n\t\t\t\tNamespace:  args.Importer.Namespace,\n\t\t\t\tResolveDir: args.ResolveDir,\n\t\t\t\tKind:       importKindToResolveKind(args.Kind),\n\t\t\t\tPluginData: args.PluginData,\n\t\t\t\tWith:       args.With.DecodeIntoMap(),\n\t\t\t})\n\t\t\tresult.PluginName = response.PluginName\n\t\t\tresult.AbsWatchFiles = impl.validatePathsArray(response.WatchFiles, \"watch file\")\n\t\t\tresult.AbsWatchDirs = impl.validatePathsArray(response.WatchDirs, \"watch directory\")\n\n\t\t\t// Restrict the suffix to start with \"?\" or \"#\" for now to match esbuild's behavior\n\t\t\tif err == nil && response.Suffix != \"\" && response.Suffix[0] != '?' && response.Suffix[0] != '#' {\n\t\t\t\terr = fmt.Errorf(\"Invalid path suffix %q returned from plugin (must start with \\\"?\\\" or \\\"#\\\")\", response.Suffix)\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tresult.ThrownError = err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresult.Path = logger.Path{\n\t\t\t\tText:          response.Path,\n\t\t\t\tNamespace:     response.Namespace,\n\t\t\t\tIgnoredSuffix: response.Suffix,\n\t\t\t}\n\t\t\tresult.External = response.External\n\t\t\tresult.IsSideEffectFree = response.SideEffects == SideEffectsFalse\n\t\t\tresult.PluginData = response.PluginData\n\n\t\t\t// Convert log messages\n\t\t\tresult.Msgs = convertErrorsAndWarningsToInternal(response.Errors, response.Warnings)","sourceCodeStart":1961,"sourceCodeEnd":1997,"githubUrl":"https://github.com/evanw/esbuild/blob/f6058f8364fe7ab91ca57a83e02577ed74c9cae4/pkg/api/api_impl.go#L1961-L1997","documentation":"When an esbuild plugin's OnResolve callback returns a suffix field, esbuild requires it to start with '?' (query string) or '#' (hash fragment). This restriction exists to match esbuild's internal handling of external path suffixes. Any other character as the first byte triggers this error.","triggerScenarios":"A plugin's onResolve callback returns { path, suffix: 'version=1' } where the suffix does not start with '?' or '#'.","commonSituations":"Plugin authors who misunderstand the suffix field's purpose and return arbitrary strings, or who forget to prefix the suffix with '?' or '#'.","solutions":["Prefix the suffix with '?' for query strings (e.g. '?version=1')","Prefix the suffix with '#' for hash fragments (e.g. '#section')","Omit the suffix field entirely if your plugin does not need it"],"exampleFix":"// before\nonResolve: () => ({ path: resolved, suffix: 'v=2' })\n// after\nonResolve: () => ({ path: resolved, suffix: '?v=2' })","handlingStrategy":"validation","validationCode":"function validateOnResolveResult(result) {\n  if (result.suffix && result.suffix.length > 0) {\n    if (result.suffix[0] !== '?' && result.suffix[0] !== '#') {\n      throw new Error(`Suffix must start with '?' or '#', got: ${result.suffix}`)\n    }\n  }\n  return result\n}\n// In plugin setup:\nonResolve: { filter: '.*' },\ncallback: (args) => {\n  const result = { path: resolvePath(args) }\n  if (args.query) result.suffix = '?' + args.query\n  return validateOnResolveResult(result)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always prefix suffix values with '?' for queries or '#' for hashes","Only set the suffix field when you actually need query/hash suffixes","Add a validation wrapper around onResolve return values during development"],"tags":["esbuild","plugin","onresolve","validation"],"backgroundTag":null,"analyzedSha":"f6058f8364fe7ab91ca57a83e02577ed74c9cae4","analyzedAt":"2026-08-09T18:37:22.223Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}