TryGhost/Ghost · error · Error
errors.invalidUsername
Error message
errors.invalidUsername
What it means
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.
Source
Thrown at apps/admin/src/settings/app/utils/social-urls/platform-validator.ts:280
if (!validator.isURL(url)) {
throw new Error(errors.invalidUrl);
}
return url;
};
const validate = (input: string) => {
if (!input) {
return '';
}
const {region, pathType, rawUsername, atPrefixConsumed} = extractParts(input.trim());
const username = formatUsername(rawUsername, atPrefixConsumed);
checkUsername(pathType, username);
return buildUrl(pathType, username, region);
};
const handleToUrl = (handle: string) => {
if (!handle) {
throw new Error(errors.invalidUsername);
}
const {pathType, rawUsername, atPrefixConsumed} = parseHandle(handle.trim());
const username = formatUsername(rawUsername, atPrefixConsumed);
checkUsername(pathType, username);
return buildUrl(pathType, username);
};
const urlToHandle = (url: string) => {
if (!url || !isUrlInput(url.trim())) {
return null;
}
try {
const {pathType, rawUsername, atPrefixConsumed} = extractParts(url.trim());
const username = formatUsername(rawUsername, atPrefixConsumed);
checkUsername(pathType, username);
// the regional subdomain (uk.linkedin.com) is intentionally dropped:
// stored handles are region-less
return `${pathType.storagePrefix}${username}`;View on GitHub (pinned to 47d8b0e2ad)
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;
Example fix
// before (throws when handle is empty) const url = blueskyHandleToUrl(member.bluesky_handle); // after const url = member.bluesky_handle ? blueskyHandleToUrl(member.bluesky_handle) : null;
Defensive patterns
Strategy: validation
Validate before calling
function safeHandleToUrl(handleToUrl: (h: string) => string, handle: string | null | undefined): string | null {
if (!handle || !handle.trim()) {
return null;
}
return handleToUrl(handle);
} Type guard
function isNonEmptyHandle(value: unknown): value is string {
return typeof value === 'string' && value.trim().length > 0;
} Try / catch
try {
const url = blueskyHandleToUrl(handle);
} catch (e) {
if (e instanceof Error && /not a valid/.test(e.message)) {
// handle was empty or invalid — treat as no profile
return null;
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- The URL must be in a format like @username@instance.tld or h
- Your Username is not a valid Mastodon Username
- The URL must be in a format like https://x.com/yourUsername
- Your Username is not a valid Twitter Username
- The URL must be in a format like https://www.linkedin.com/in
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/1553f0dd5d123940.
Report an issue: GitHub.