{"record":{"id":"1553f0dd5d123940","repo":"TryGhost/Ghost","slug":"errors-invalidusername","errorCode":null,"errorMessage":"errors.invalidUsername","messagePattern":"errors\\.invalidUsername","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/admin/src/settings/app/utils/social-urls/platform-validator.ts","lineNumber":280,"sourceCode":"        if (!validator.isURL(url)) {\n            throw new Error(errors.invalidUrl);\n        }\n        return url;\n    };\n\n    const validate = (input: string) => {\n        if (!input) {\n            return '';\n        }\n        const {region, pathType, rawUsername, atPrefixConsumed} = extractParts(input.trim());\n        const username = formatUsername(rawUsername, atPrefixConsumed);\n        checkUsername(pathType, username);\n        return buildUrl(pathType, username, region);\n    };\n\n    const handleToUrl = (handle: string) => {\n        if (!handle) {\n            throw new Error(errors.invalidUsername);\n        }\n        const {pathType, rawUsername, atPrefixConsumed} = parseHandle(handle.trim());\n        const username = formatUsername(rawUsername, atPrefixConsumed);\n        checkUsername(pathType, username);\n        return buildUrl(pathType, username);\n    };\n\n    const urlToHandle = (url: string) => {\n        if (!url || !isUrlInput(url.trim())) {\n            return null;\n        }\n        try {\n            const {pathType, rawUsername, atPrefixConsumed} = extractParts(url.trim());\n            const username = formatUsername(rawUsername, atPrefixConsumed);\n            checkUsername(pathType, username);\n            // the regional subdomain (uk.linkedin.com) is intentionally dropped:\n            // stored handles are region-less\n            return `${pathType.storagePrefix}${username}`;","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/admin/src/settings/app/utils/social-urls/platform-validator.ts#L262-L298","documentation":"This is the generic handleToUrl guard inside createPlatformValidator. It throws errors.invalidUsername (the message configured per-platform) when handleToUrl is called with an empty, null, or whitespace-only handle. handleToUrl is meant to convert a stored handle to a canonical URL and has no sensible output for an empty handle, so it fails fast rather than returning a malformed URL.","triggerScenarios":"Calling any platform's handleToUrl export (e.g. blueskyHandleToUrl, threadsHandleToUrl, twitterHandleToUrl, linkedinHandleToUrl) with an empty string, undefined-coerced-to-string, or a handle that trims to empty. The check is `if (!handle)` which catches '', 0, null, undefined, NaN, and false.","commonSituations":"A settings form that loads a blank social profile field from the database and calls handleToUrl to render the canonical URL without first checking the field is populated. A migration or default-seeding script that passes undefined for a new user with no social profiles set.","solutions":["Guard the call site: check the handle is a non-empty string before calling handleToUrl.","Use validate instead of handleToUrl when the input may be empty — validate returns '' for empty input rather than throwing.","If rendering, treat empty handles as 'no profile set' and skip the URL generation entirely.","Coerce with a default or early-return in the caller: if (!handle) return null;"],"exampleFix":"// before (throws when handle is empty)\nconst url = blueskyHandleToUrl(member.bluesky_handle);\n// after\nconst url = member.bluesky_handle ? blueskyHandleToUrl(member.bluesky_handle) : null;","handlingStrategy":"validation","validationCode":"function safeHandleToUrl(handleToUrl: (h: string) => string, handle: string | null | undefined): string | null {\n  if (!handle || !handle.trim()) {\n    return null;\n  }\n  return handleToUrl(handle);\n}","typeGuard":"function isNonEmptyHandle(value: unknown): value is string {\n  return typeof value === 'string' && value.trim().length > 0;\n}","tryCatchPattern":"try {\n  const url = blueskyHandleToUrl(handle);\n} catch (e) {\n  if (e instanceof Error && /not a valid/.test(e.message)) {\n    // handle was empty or invalid — treat as no profile\n    return null;\n  }\n  throw e;\n}","preventionTips":["Always check the handle is a non-empty string before calling handleToUrl.","Prefer validate() for user-facing input — it returns '' for empty rather than throwing.","Use urlToHandle (returns null on bad input) when you only need to extract a handle from a URL."],"tags":["validation","social-urls","null-guard","platform-validator"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}