TryGhost/Ghost · warning

Your Username is not a valid Bluesky Username

Error message

Your Username is not a valid Bluesky Username

What it means

Thrown by checkUsername inside createPlatformValidator when the Bluesky username extracted from a URL or handle fails to match any of the three accepted identifier shapes: a DID (did:plc: + 24 Base32 chars), a short handle (1-15 chars of [a-zA-Z0-9._]), or a domain handle (dot-separated, up to 191 chars). The message is the platform definition's errors.invalidUsername string. It signals that the input parsed as a Bluesky profile URL but the identifier portion is malformed.

Source

Thrown at apps/admin/src/settings/app/utils/social-urls/bluesky.ts:27

    // domain handle: requires a dot, max 191 chars
    // (the lookahead does the length check because + is unbounded)
    /^(?=.{1,191}$)[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$/
];

const bluesky = createPlatformValidator({
    domains: ['bsky.app'],
    www: false,
    pathTypes: [
        // bsky.app/profile/@username is a common paste (users type Bluesky
        // handles with a leading @ out of habit); the @ is decorative here,
        // not a marker for a competing path type, so it's still stripped
        {urlPrefix: 'profile/', storagePrefix: '', tolerateLeadingAt: true, rule: {patterns: BLUESKY_USERNAME_PATTERNS}}
    ],
    // DIDs are case-insensitive identifiers, canonically lowercase
    transformUsername: username => (/^did:plc:/i.test(username) ? username.toLowerCase() : username),
    errors: {
        invalidUrl: 'The URL must be in a format like https://bsky.app/profile/yourUsername',
        invalidUsername: 'Your Username is not a valid Bluesky Username'
    }
});

export const validateBlueskyUrl = bluesky.validate;
export const blueskyHandleToUrl = bluesky.handleToUrl;
export const blueskyUrlToHandle = bluesky.urlToHandle;

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Inspect the extracted username: it must match /^did:plc:[a-z2-7]{24}$/, /^[a-zA-Z0-9._]{1,15}$/, or /^(?=.{1,191}$)[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$/.
  2. If the input is a URL, ensure only the single path segment after profile/ is the identifier — remove trailing /post/... or /lists/... paths before validating, or rely on the pipeline which takes only the first segment.
  3. If storing DIDs, lowercase them; the transformUsername only lowercases did:plc: prefixed values so a mixed-case short handle is accepted but a DID with uppercase after the prefix must already be lowercase or it will be lowercased automatically.
  4. Strip any leading '@' before calling handleToUrl if you bypass validate, though tolerateLeadingAt already handles bsky.app/profile/@user pastes.

Example fix

// before (fails — space in handle)
validateBlueskyUrl('https://bsky.app/profile/my handle')
// after
validateBlueskyUrl('https://bsky.app/profile/myhandle')
Defensive patterns

Strategy: validation

Validate before calling

const BLUESKY_PATTERNS = [
  /^did:plc:[a-z2-7]{24}$/,
  /^[a-zA-Z0-9._]{1,15}$/,
  /^(?=.{1,191}$)[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$/
];
function isValidBlueskyHandle(handle: string): boolean {
  return BLUESKY_PATTERNS.some(p => p.test(handle));
}
// run before validateBlueskyUrl on the extracted segment

Type guard

function isBlueskyHandle(value: unknown): value is string {
  return typeof value === 'string' && [
    /^did:plc:[a-z2-7]{24}$/,
    /^[a-zA-Z0-9._]{1,15}$/,
    /^(?=.{1,191}$)[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$/
  ].some(p => p.test(value));
}

Try / catch

try {
  const url = validateBlueskyUrl(input);
} catch (e) {
  if (e instanceof Error && /not a valid Bluesky Username/.test(e.message)) {
    // show field-level validation error
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling validateBlueskyUrl, blueskyHandleToUrl, or blueskyUrlToHandle with input whose path segment after 'profile/' is not a valid DID, short handle, or domain handle. Examples: 'https://bsky.app/profile/user name' (space), 'https://bsky.app/profile/-bad' (leading dash in a non-domain handle), 'https://bsky.app/profile/' (empty, caught earlier as invalidUrl), 'https://bsky.app/profile/a.b.c.d.e.f.g' where the segment exceeds 15 chars and has no dot so it matches no pattern, or a handle like 'did:plc:XYZ' where XYZ contains invalid Base32 characters.

Common situations: User pastes a Bluesky URL with a trailing query string or fragment that leaks into the username (caught by the ?# check first), copies a profile URL for a suspended/renamed account, or types a handle with characters Bluesky forbids. Developers migrating from a system that stored handles with a leading '@' will usually pass because tolerateLeadingAt strips it, but handles stored with other decorations (slashes, spaces) will fail.

Related errors


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/c0629af7dbe7e80b. Report an issue: GitHub.