{"record":{"id":"f957147ee236a0b1","repo":"facebook/docusaurus","slug":"extension-ext-contains-invalid-uri-characters","errorCode":null,"errorMessage":"Extension \"${ext}\" contains invalid URI characters.\nIf the redirect extension system is not good enough for your use case, you can create redirects yourself with the \"createRedirects\" plugin option.","messagePattern":"Extension \"(.+?)\" contains invalid URI characters\\.\nIf the redirect extension system is not good enough for your use case, you can create redirects yourself with the \"createRedirects\" plugin option\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts","lineNumber":35,"sourceCode":"\nconst validateExtension = (ext: string) => {\n  if (!ext) {\n    throw new Error(\n      `Extension \"${ext}\" is not allowed.\\n${ExtensionAdditionalMessage}`,\n    );\n  }\n  if (ext.includes('.')) {\n    throw new Error(\n      `Extension \"${ext}\" contains a \".\" (dot) which is not allowed.\\n${ExtensionAdditionalMessage}`,\n    );\n  }\n  if (ext.includes('/')) {\n    throw new Error(\n      `Extension \"${ext}\" contains a \"/\" (slash) which is not allowed.\\n${ExtensionAdditionalMessage}`,\n    );\n  }\n  if (encodeURIComponent(ext) !== ext) {\n    throw new Error(\n      `Extension \"${ext}\" contains invalid URI characters.\\n${ExtensionAdditionalMessage}`,\n    );\n  }\n};\n\nconst addLeadingDot = (extension: string) => `.${extension}`;\n\n/**\n * Create new `/path` that redirects to existing an `/path.html`\n */\nexport function createToExtensionsRedirects(\n  paths: string[],\n  extensions: string[],\n): RedirectItem[] {\n  extensions.forEach(validateExtension);\n\n  const dottedExtensions = extensions.map(addLeadingDot);\n","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/facebook/docusaurus/blob/3f483e80e326cc646b54b83d564b3f0c4881b9a6/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts#L17-L53","documentation":"Thrown by validateExtension() when encodeURIComponent(ext) !== ext, i.e. the extension contains characters that are not URI-safe (spaces, unicode, #, ?, etc.). Redirect extensions become part of URLs, so they must already be in encoded form. This is the catch-all for invalid extensions that pass the empty/dot/slash checks but are still not URL-safe.","triggerScenarios":"Configuring fromExtensions: ['htm lg'] (space), toExtensions: ['htéml'] (accented unicode), or any extension containing #, ?, %, spaces, or other reserved/unsafe URI characters. Reached in the forEach(validateExtension) loop after the empty/dot/slash guards.","commonSituations":"Pasting extensions from a spreadsheet that introduced non-breaking spaces; using localized/unicode tokens; a typo slipping a reserved character into the array.","solutions":["Use only ASCII alphanumeric extension tokens: 'html', 'htm', 'php', 'aspx'.","Pre-encode any unusual token with encodeURIComponent before pushing it, or better, pick a plain ASCII name.","Sanitize generated arrays: ext = encodeURIComponent(ext) and verify it equals the input."],"exampleFix":"// before\nfromExtensions: ['ht ml'],\n// after\nfromExtensions: ['html'],","handlingStrategy":"validation","validationCode":"function assertUriSafe(ext: string): void {\n  if (encodeURIComponent(ext) !== ext) {\n    throw new Error(`Extension \"${ext}\" contains invalid URI characters.`);\n  }\n}","typeGuard":"const isUriSafeExtension = (e: unknown): e is string =>\n  typeof e === 'string' && !!e && !e.includes('.') && !e.includes('/')\n  && encodeURIComponent(e) === e;","tryCatchPattern":null,"preventionTips":["Stick to ASCII alphanumeric tokens ('html', 'htm', 'php').","Run each extension through encodeURIComponent and assert it is unchanged.","Sanitize copy-pasted config arrays for hidden spaces/unicode."],"tags":["plugin-client-redirects","redirects","extensions","uri","validation"],"backgroundTag":null,"analyzedSha":"3f483e80e326cc646b54b83d564b3f0c4881b9a6","analyzedAt":"2026-08-12T13:25:04.382Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}