{"id":"0c8ef6dcd3a50ffa","repo":"evanw/esbuild","slug":"could-not-decode-percent-escaped-data-s","errorCode":null,"errorMessage":"could not decode percent-escaped data: %s","messagePattern":"could not decode percent-escaped data: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/resolver/dataurl.go","lineNumber":73,"sourceCode":"\tdefault:\n\t\treturn MIMETypeUnsupported\n\t}\n}\n\nfunc (parsed DataURL) DecodeData() (string, error) {\n\t// Try to read base64 data\n\tif parsed.isBase64 {\n\t\tbytes, err := base64.StdEncoding.DecodeString(parsed.data)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"could not decode base64 data: %s\", err.Error())\n\t\t}\n\t\treturn string(bytes), nil\n\t}\n\n\t// Try to read percent-escaped data\n\tcontent, err := url.PathUnescape(parsed.data)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"could not decode percent-escaped data: %s\", err.Error())\n\t}\n\treturn content, nil\n}\n","sourceCodeStart":55,"sourceCodeEnd":77,"githubUrl":"https://github.com/evanw/esbuild/blob/6ff1d8b0d8c134e867a397eef39702a223ebef9e/internal/resolver/dataurl.go#L55-L77","documentation":"Returned by DataURL.DecodeData when a non-base64 data URL's payload fails net/url.PathUnescape. Percent-escaped data URLs use %XX sequences; an invalid sequence (e.g. '%ZZ' or a trailing '%') makes PathUnescape error. esbuild hits this while resolving data: imports.","triggerScenarios":"Import a data URL like `data:text/plain,100%25` (fine) but break it with `data:text/plain,50%off` (the '%of' is not a valid hex pair). Any data URL without ';base64' whose percent sequences are malformed triggers this.","commonSituations":"URL-encoding a literal '%' as a single '%' instead of '%25'; templating engines that interpolate user text into a data URL without escaping; truncation that leaves a dangling '%'.","solutions":["Properly percent-encode the payload: every literal '%' must become '%25'.","If the data is binary or contains many special chars, switch to ';base64' encoding instead.","Generate the data URL with encodeURIComponent (JS) or url.PathEscape (Go) rather than concatenating raw strings.","Lint data URLs in fixtures with a small decoder test before bundling."],"exampleFix":"// before\nimport x from 'data:text/plain,50%off'\n\n// after\nimport x from 'data:text/plain,' + encodeURIComponent('50%off')  // -> '50%25off'","handlingStrategy":"validation","validationCode":"// Check percent-encoding of a non-base64 data URL.\nfunction checkPercentDataUrl(url) {\n  if (!url.startsWith('data:') || url.includes(';base64,')) return true\n  const payload = url.slice(url.indexOf(',') + 1)\n  return !/(?:%[0-9A-Fa-f]{2})|[%]/.test(payload.replace(/%[0-9A-Fa-f]{2}/g, ''))\n}\nif (!checkPercentDataUrl(specifier)) {\n  // re-encode properly:\n  specifier = 'data:' + mime + ',' + encodeURIComponent(raw)\n}","typeGuard":"function isFullyPercentEncoded(payload: string): boolean {\n  // after stripping valid %XX, no stray '%' should remain\n  return !payload.replace(/%[0-9A-Fa-f]{2}/g, '').includes('%')\n}","tryCatchPattern":"try {\n  await esbuild.build({ ... })\n} catch (e) {\n  if (/could not decode percent-escaped data/i.test(e.message)) {\n    console.error('A data URL import has invalid percent-encoding:', e.message)\n  }\n  throw e\n}","preventionTips":["Always encodeURIComponent user data before embedding in a data URL.","Escape literal '%' as '%25'.\n","Consider base64 for binary payloads to sidestep percent-encoding edge cases."],"tags":["resolver","data-url","percent-encoding","import","encoding"],"analyzedSha":"6ff1d8b0d8c134e867a397eef39702a223ebef9e","analyzedAt":"2026-08-03T19:42:38.433Z","schemaVersion":2}