{"record":{"id":"251cb568d9ed723b","repo":"transloadit/uppy","slug":"companionendpoint-must-be-a-string","errorCode":null,"errorMessage":"companionEndpoint must be a string","messagePattern":"companionEndpoint must be a string","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/@uppy/aws-s3/src/index.ts","lineNumber":184,"sourceCode":"        ...capabilities,\n        resumableUploads: value,\n      },\n    })\n  }\n\n  #handleCancelAll = (): void => {\n    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,","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/aws-s3/src/index.ts#L166-L202","documentation":"In the connect flow (encodeStateAndRedirect), before redirecting the user to the provider's consent screen, Companion verifies that both providerClass and req.companion.buildURL exist. A missing providerClass means the provider is not enabled/known; a missing buildURL means URL construction config is absent. Either results in 400.","triggerScenarios":"Hitting /connect/:providerName when the provider lacks Companion configuration (no key/secret so it was dropped from the providers list), or when server URL options are missing so buildURL was never set on req.companion.","commonSituations":"Frontend requests a provider not listed in companionOptions.providers; providerOptions missing key/secret for the requested provider; incomplete server.host configuration on self-hosted Companion.","solutions":["Add the provider to companionOptions.providers and provide its key/secret in providerOptions","Ensure server.host/companionUrl is configured so buildURL exists","Restart Companion after config changes and verify the provider appears in startup logs"],"exampleFix":"// before\ncompanion({ providers: ['dropbox'] })\n// after\ncompanion({\n  providers: ['dropbox'],\n  providerOptions: { dropbox: { key: '...', secret: '...' } },\n  server: { host: 'https://companion.example.com' },\n})","handlingStrategy":"validation","validationCode":"if (!providersConfig.includes(provider) || !providerOptions[provider]?.key) {\n  throw new Error(`provider ${provider} not fully configured`)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Sync enabled provider lists between frontend and Companion","Provide key/secret for every enabled provider","Add startup validation that rejects incomplete provider configs"],"tags":["companion","oauth","provider-not-registered","http-400"],"backgroundTag":"provider-not-configured","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}