{"record":{"id":"68eb524589c8570c","repo":"TryGhost/Ghost","slug":"the-url-must-be-in-a-format-like-https-bsky-app","errorCode":null,"errorMessage":"The URL must be in a format like https://bsky.app/profile/yourUsername","messagePattern":"The URL must be in a format like https://bsky\\.app/profile/yourUsername","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"apps/admin/src/settings/app/utils/social-urls/bluesky.ts","lineNumber":26,"sourceCode":"    /^[a-zA-Z0-9._]{1,15}$/,\n    // domain handle: requires a dot, max 191 chars\n    // (the lookahead does the length check because + is unbounded)\n    /^(?=.{1,191}$)[a-zA-Z0-9._-]+\\.[a-zA-Z0-9._-]+$/\n];\n\nconst bluesky = createPlatformValidator({\n    domains: ['bsky.app'],\n    www: false,\n    pathTypes: [\n        // bsky.app/profile/@username is a common paste (users type Bluesky\n        // handles with a leading @ out of habit); the @ is decorative here,\n        // not a marker for a competing path type, so it's still stripped\n        {urlPrefix: 'profile/', storagePrefix: '', tolerateLeadingAt: true, rule: {patterns: BLUESKY_USERNAME_PATTERNS}}\n    ],\n    // DIDs are case-insensitive identifiers, canonically lowercase\n    transformUsername: username => (/^did:plc:/i.test(username) ? username.toLowerCase() : username),\n    errors: {\n        invalidUrl: 'The URL must be in a format like https://bsky.app/profile/yourUsername',\n        invalidUsername: 'Your Username is not a valid Bluesky Username'\n    }\n});\n\nexport const validateBlueskyUrl = bluesky.validate;\nexport const blueskyHandleToUrl = bluesky.handleToUrl;\nexport const blueskyUrlToHandle = bluesky.urlToHandle;\n","sourceCodeStart":8,"sourceCodeEnd":34,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/admin/src/settings/app/utils/social-urls/bluesky.ts#L8-L34","documentation":"Thrown by the platform-validator engine for the Bluesky definition when input is URL-shaped but can't be resolved to a valid bsky.app profile URL. Message declared in bluesky.ts ('The URL must be in a format like https://bsky.app/profile/yourUsername'); thrown in platform-validator.ts at: URL regex non-match (line 227 — wrong domain), no path-type match (the 'profile/' prefix) or empty remainder (line 233 — e.g. 'https://bsky.app/' with no profile path, or 'https://bsky.app/profile/' with empty username), or built URL failing validator.isURL (line 263). The path type uses tolerateLeadingAt so a decorative leading @ on the username is stripped; DIDs are lower-cased via transformUsername.","triggerScenarios":"Input is URL-shaped but: the domain isn't bsky.app; the path doesn't start with 'profile/' (e.g. 'https://bsky.app/'); the remainder after 'profile/' is empty; or the built canonical URL fails validator.isURL. Bare handles that fail BLUESKY_USERNAME_PATTERNS route to invalidUsername. A common paste like 'https://bsky.app/profile/@username' is accepted (leading @ tolerated and stripped).","commonSituations":"User pasted a Bluesky post/feed URL instead of a profile URL; user entered 'https://bsky.app' with no path; user pasted a URL for a different platform; URL with characters validator.isURL rejects.","solutions":["Use a profile URL like https://bsky.app/profile/yourUsername (or .bsky.social handle, or did:plc:... DID). A leading @ after profile/ is tolerated.","Confirm the domain is bsky.app and the path starts with profile/.","Ensure the username segment after profile/ isn't empty.","For non-throwing checks, use blueskyUrlToHandle(url) which returns null on invalid input."],"exampleFix":"// before — throws on non-profile or empty-path URL\nconst normalized = validateBlueskyUrl(value);\n\n// after — non-throwing pre-check, then validate\nimport {blueskyUrlToHandle, validateBlueskyUrl} from './bluesky';\nconst trimmed = value.trim();\ntry {\n    if (trimmed && trimmed.includes('://') && blueskyUrlToHandle(trimmed) === null) {\n        setFieldError('bluesky', 'Use https://bsky.app/profile/yourUsername');\n    } else {\n        setNormalized(validateBlueskyUrl(trimmed));\n    }\n} catch (e) {\n    setFieldError('bluesky', e instanceof Error ? e.message : 'Invalid Bluesky URL');\n}","handlingStrategy":"try-catch","validationCode":"// Non-throwing pre-check using the URL→handle extractor\nimport {blueskyUrlToHandle} from './bluesky';\nfunction looksLikeBlueskyUrl(input: string): boolean {\n    if (!input || !input.includes('://')) return true;\n    return blueskyUrlToHandle(input) !== null;\n}","typeGuard":"null","tryCatchPattern":"try {\n    const normalized = validateBlueskyUrl(value.trim());\n} catch (e) {\n    setFieldError('bluesky', e instanceof Error ? e.message : 'Invalid Bluesky URL');\n}","preventionTips":["Use https://bsky.app/profile/yourUsername (.bsky.social handle or did:plc:... DID). A leading @ after profile/ is tolerated.","Ensure the path starts with profile/ and the username after it isn't empty.","Use blueskyUrlToHandle for non-throwing URL checks.","DIDs are case-insensitively lower-cased automatically."],"tags":["bluesky","social-urls","validation"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}