{"record":{"id":"e9ed0b3905be1144","repo":"transloadit/uppy","slug":"s3endpoint-must-be-a-string","errorCode":null,"errorMessage":"s3Endpoint must be a string","messagePattern":"s3Endpoint must be a string","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/@uppy/aws-s3/src/index.ts","lineNumber":191,"sourceCode":"    this.#setResumableUploadsCapability(true)\n    this.#queue.clear()\n  }\n\n  // --------------------------------------------------------------------------\n  // S3 Client Initialization\n  // --------------------------------------------------------------------------\n\n  #initS3Client(): void {\n    if ('companionEndpoint' in this.opts) {\n      if (typeof this.opts.companionEndpoint !== 'string') {\n        throw new TypeError('companionEndpoint must be a string')\n      }\n      this.#s3Client = new S3Companion({\n        companionEndpoint: this.opts.companionEndpoint,\n      })\n    } else if ('getCredentials' in this.opts) {\n      if (typeof this.opts.s3Endpoint !== 'string') {\n        throw new TypeError('s3Endpoint must be a string')\n      }\n      if (typeof this.opts.getCredentials !== 'function') {\n        throw new TypeError('getCredentials must be a function')\n      }\n      if (this.opts.region != null && typeof this.opts.region !== 'string') {\n        throw new TypeError('region must be a string')\n      }\n\n      // Mode: Temporary credentials (client-side signing)\n      this.#s3Client = new S3mini({\n        endpoint: this.opts.s3Endpoint,\n        getCredentials: this.opts.getCredentials,\n        region: this.opts.region,\n      })\n    } else if ('signRequest' in this.opts) {\n      if (typeof this.opts.signRequest !== 'function') {\n        throw new TypeError('signRequest must be a function')\n      }","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/aws-s3/src/index.ts#L173-L209","documentation":"Each Companion provider class exposes an oauthProvider string identifying the grant provider (e.g. 'google', 'dropbox'). If the provider class for the requested provider has a null/empty oauthProvider, Companion cannot build the grant authorize URL and returns 400.","triggerScenarios":"Using a custom provider class that forgets to set the oauthProvider static property; or a provider whose grant config does not map to an OAuth provider (some providers like local-disk or transloadit-style integrations do not use OAuth and should not go through /connect).","commonSituations":"Writing a custom provider and missing the static oauthProvider field; attempting to 'connect' to a non-OAuth provider via the OAuth connect endpoint.","solutions":["If using a custom provider, set `static oauthProvider = 'mygrantprovider'` (matching the grant config key)","Do not call /connect/:providerName for providers that are not OAuth-based","Verify the provider class version matches the Companion version (API changes across upgrades)"],"exampleFix":"// before\nclass MyProvider extends Provider { /* missing oauthProvider */ }\n// after\nclass MyProvider extends Provider {\n  static oauthProvider = 'myprovider'\n  // ...\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"class MyProvider extends Provider {\n  static oauthProvider = 'myprovider'\n}\nconst hasOAuthProvider = (C: typeof Provider): C is typeof Provider & { oauthProvider: string } =>\n  typeof (C as any).oauthProvider === 'string' && (C as any).oauthProvider.length > 0","tryCatchPattern":null,"preventionTips":["Unit-test custom provider classes for required statics (id, oauthProvider)","Only route OAuth-based providers through /connect","Pin Companion versions and read migration notes for provider API changes"],"tags":["companion","oauth","custom-provider","http-400"],"backgroundTag":"provider-misconfiguration","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}