{"record":{"id":"b70f5172820e270f","repo":"RocketChat/Rocket.Chat","slug":"customoauth-failed-to-extract-avatar-url-b70f51","errorCode":null,"errorMessage":"CustomOAuth: Failed to extract avatar url","messagePattern":"CustomOAuth: Failed to extract avatar url","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/auth-providers/custom-oauth/custom_oauth_server.js","lineNumber":363,"sourceCode":"\t\t\t\treturn this.getName(data);\n\t\t\t}\n\n\t\t\treturn value;\n\t\t} catch (error) {\n\t\t\tthrow new Error('CustomOAuth: Failed to extract custom name', error.message);\n\t\t}\n\t}\n\n\tgetAvatarUrl(data) {\n\t\ttry {\n\t\t\tconst value = fromTemplate(this.avatarField, data);\n\n\t\t\tif (!value) {\n\t\t\t\tlogger.debug({ msg: 'Avatar field not found in data', avatarField: this.avatarField, data });\n\t\t\t}\n\t\t\treturn value;\n\t\t} catch (error) {\n\t\t\tthrow new Error('CustomOAuth: Failed to extract avatar url', error.message);\n\t\t}\n\t}\n\n\tgetName(identity) {\n\t\tconst name =\n\t\t\tidentity.name ||\n\t\t\tidentity.username ||\n\t\t\tidentity.nickname ||\n\t\t\tidentity.CharacterName ||\n\t\t\tidentity.userName ||\n\t\t\tidentity.preferred_username ||\n\t\t\t(identity.user && identity.user.name);\n\t\treturn name;\n\t}\n\n\taddHookToProcessUser() {\n\t\tBeforeUpdateOrCreateUserFromExternalService.push(async (serviceName, serviceData /* , options*/) => {\n\t\t\tif (serviceName !== this.name) {","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/auth-providers/custom-oauth/custom_oauth_server.js#L345-L381","documentation":"Legacy (deprecated, non-Passport) CustomOAuth class twin of the strategy error: getAvatarUrl() throws Error('CustomOAuth: Failed to extract avatar url', <inner message>) only when fromTemplate(this.avatarField, data) throws. A missing avatar value is not an error - it logs 'Avatar field not found in data' and returns undefined - so this indicates a broken avatarField template (invalid regex in a '{{/regex/::path}}' formula, or avatarField undefined with a direct call). It fails the login via normalizeIdentity.","triggerScenarios":"avatarField configured with a formula whose regex fails new RegExp() during getIdentity; avatarField left undefined by configure() and getAvatarUrl invoked directly (getNestedValue throws on undefined.split).","commonSituations":"Older Rocket.Chat deployments still on the deprecated class; admin copies a regex mapping from docs and mistypes it; provider payload rename after upgrade.","solutions":["Set Avatar Field to a plain dot path present in the identity payload, e.g. 'picture' or 'avatar_url'","Compile-check any regex formula with new RegExp() before saving; it needs exactly one capture group","Clear Avatar Field - extraction is optional and login succeeds without an avatar","Check the CustomOAuth debug log for the printed payload to confirm the key name"],"exampleFix":"// before: Avatar Field = '{{/https:(.+)/::image}}' (broken regex)\n// Error: CustomOAuth: Failed to extract avatar url ...\n\n// after: Avatar Field = 'image_url'","handlingStrategy":"validation","validationCode":"// verify the avatarField template against a captured payload\nconst capturedIdentity = JSON.parse(savedIdentityJson); // from CustomOAuth debug logs\nif (avatarFieldSetting) {\n  try {\n    fromTemplate(avatarFieldSetting, capturedIdentity);\n  } catch (e) {\n    throw new Error(`Avatar Field template is broken: ${e.message}`);\n  }\n}","typeGuard":"const isResolvableTemplate = (tpl: string, data: Record<string, unknown>): boolean => {\n  try {\n    return fromTemplate(tpl, data) != null;\n  } catch {\n    return false;\n  }\n};","tryCatchPattern":"try {\n  identity.avatarUrl = customOAuth.getAvatarUrl(identity);\n} catch (error) {\n  logger.warn(`avatar extraction failed: ${error.message}`);\n  identity.avatarUrl = undefined;\n}","preventionTips":["Use a plain dot path for Avatar Field","Compile-check any regex formula before saving","Leave Avatar Field empty if the provider has no avatar claim","Plan migration off the deprecated CustomOAuth class onto the Passport strategy"],"tags":["oauth","custom-oauth","avatar","field-mapping","regex","deprecated"],"backgroundTag":"oauth-identity-field-mapping-failed","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}