transloadit/uppy · error · Error

OAuth2 error: ${response.error}

Error message

OAuth2 error: ${response.error}

What it means

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.

Source

Thrown at packages/@uppy/core/src/companion-client/googlePicker.ts:198

        // Authorization scopes required by the API; multiple scopes can be included, separated by spaces.
        scope: scopes.join(' '),
        callback: resolve,
        error_callback: reject,
      })

      if (accessToken === null) {
        // Prompt the user to select a Google Account and ask for consent to share their data
        // when establishing a new session.
        tokenClient.requestAccessToken({ prompt: 'consent' })
      } else {
        // Skip display of account chooser and consent dialog for an existing session.
        tokenClient.requestAccessToken({ prompt: '' })
      }
    },
  )

  if (response.error) {
    throw new Error(`OAuth2 error: ${response.error}`)
  }
  return response.access_token
}

async function doLogout(accessToken: string): Promise<void> {
  await new Promise<void>((resolve) =>
    google.accounts.oauth2.revoke(accessToken, resolve),
  )
}

export class InvalidTokenError extends Error {
  constructor() {
    super('Invalid or expired token')
    this.name = 'InvalidTokenError'
  }
}

async function handleDocObjectRecursively({

View on GitHub (pinned to 5d4dedd02a)

Solutions

  1. Ensure the OAuth client's authorized JavaScript origins include your page origin
  2. Enable the Google Picker API (and Photos Picker API if used) and add required scopes in the plugin's pick options / client config
  3. Handle user cancellation gracefully — catch the error and keep the picker button in a signed-out state
  4. Add your test users if the OAuth consent screen is in testing mode

Example fix

// before
renderButton(uppy, { clientId: 'xxx' }) // origin not whitelisted -> OAuth2 error: access_denied

// after
renderButton(uppy, {
  clientId: 'xxx',
  // in Google Cloud Console: add https://app.example.com to Authorized JavaScript origins
  // enable 'Google Picker API' + scopes: photospicker, drive.readonly
})
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

function isOAuth2Error(e: unknown): boolean {
  return e instanceof Error && e.message.startsWith('OAuth2 error:')
}

Try / catch

try {
  await uppy.googlePicker.showPicker()
} catch (err) {
  if (isOAuth2Error(err)) {
    if (err.message.includes('access_denied')) return // user cancelled — ignore
    uppy.info('Google sign-in failed. Check app configuration.', 'error', 5000)
  }
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of transloadit/uppy@5d4dedd02a (2026-08-28). Data as JSON: /api/errors/9e6a3c01709ac2ee. Report an issue: GitHub.