{"record":{"id":"9e6a3c01709ac2ee","repo":"transloadit/uppy","slug":"oauth2-error-response-error","errorCode":null,"errorMessage":"OAuth2 error: ${response.error}","messagePattern":"OAuth2 error: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@uppy/core/src/companion-client/googlePicker.ts","lineNumber":198,"sourceCode":"        // Authorization scopes required by the API; multiple scopes can be included, separated by spaces.\n        scope: scopes.join(' '),\n        callback: resolve,\n        error_callback: reject,\n      })\n\n      if (accessToken === null) {\n        // Prompt the user to select a Google Account and ask for consent to share their data\n        // when establishing a new session.\n        tokenClient.requestAccessToken({ prompt: 'consent' })\n      } else {\n        // Skip display of account chooser and consent dialog for an existing session.\n        tokenClient.requestAccessToken({ prompt: '' })\n      }\n    },\n  )\n\n  if (response.error) {\n    throw new Error(`OAuth2 error: ${response.error}`)\n  }\n  return response.access_token\n}\n\nasync function doLogout(accessToken: string): Promise<void> {\n  await new Promise<void>((resolve) =>\n    google.accounts.oauth2.revoke(accessToken, resolve),\n  )\n}\n\nexport class InvalidTokenError extends Error {\n  constructor() {\n    super('Invalid or expired token')\n    this.name = 'InvalidTokenError'\n  }\n}\n\nasync function handleDocObjectRecursively({","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/core/src/companion-client/googlePicker.ts#L180-L216","documentation":"Thrown by the Google Picker's internal authorize() when the OAuth2 token flow completes but the response carries an error field (e.g. 'access_denied', 'popup_closed_by_user') instead of an access_token. It means Google refused or aborted issuance of the access token needed for the Picker API.","triggerScenarios":"Calling uppy.googlePicker.showPicker() / renderButton() where the google Picker client's requestAccessToken callback receives response.error — user denies consent, closes the popup, or the OAuth client lacks the required scopes (e.g. missing Picker or Photos Picker scope).","commonSituations":"OAuth client ID misconfigured (unauthorized origin, missing scope 'https://www.googleapis.com/auth/photospicker'); user cancels the consent popup; Picker API not enabled in the Google Cloud project; app in testing mode with the test user not added.","solutions":["Ensure the OAuth client's authorized JavaScript origins include your page origin","Enable the Google Picker API (and Photos Picker API if used) and add required scopes in the plugin's pick options / client config","Handle user cancellation gracefully — catch the error and keep the picker button in a signed-out state","Add your test users if the OAuth consent screen is in testing mode"],"exampleFix":"// before\nrenderButton(uppy, { clientId: 'xxx' }) // origin not whitelisted -> OAuth2 error: access_denied\n\n// after\nrenderButton(uppy, {\n  clientId: 'xxx',\n  // in Google Cloud Console: add https://app.example.com to Authorized JavaScript origins\n  // enable 'Google Picker API' + scopes: photospicker, drive.readonly\n})","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"function isOAuth2Error(e: unknown): boolean {\n  return e instanceof Error && e.message.startsWith('OAuth2 error:')\n}","tryCatchPattern":"try {\n  await uppy.googlePicker.showPicker()\n} catch (err) {\n  if (isOAuth2Error(err)) {\n    if (err.message.includes('access_denied')) return // user cancelled — ignore\n    uppy.info('Google sign-in failed. Check app configuration.', 'error', 5000)\n  }\n}","preventionTips":["Whitelist all page origins in the OAuth client","Enable Picker/Photos Picker APIs and add scopes before shipping","Treat popup_closed/access_denied as non-errors"],"tags":["google-picker","oauth2","consent","scopes"],"backgroundTag":"oauth-access-denied","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}