ToolJet/ToolJet · error · Error

HubSpot OAuth "scope" config is missing

Error message

HubSpot OAuth "scope" config is missing

What it means

HubSpot plugin's authUrl() throws this plain Error when getOAuthCredentials() returns falsy scopes. Unlike clientId/clientSecret (which can come from env), scopes are config-only — they must be present in source_options. This is the third guard in the authUrl pre-flight sequence and runs after clientId and clientSecret checks pass.

Source

Thrown at marketplace/plugins/hubspot/lib/index.ts:122

  authUrl(source_options: SourceOptions): string {
    const { clientId, clientSecret, scopes, redirectUri } = this.getOAuthCredentials(source_options);
    const oauth_type = source_options.oauth_type.value;

    if (!clientId) {
      throw new Error(
        `HubSpot OAuth "clientId" ${oauth_type === 'tooljet_app' ? 'environment variable' : 'config'} is missing`
      );
    }

    if (!clientSecret) {
      throw new Error(
        `HubSpot OAuth "clientSecret" ${oauth_type === 'tooljet_app' ? 'environment variable' : 'config'} is missing`
      );
    }

    if (!scopes) {
      throw new Error(`HubSpot OAuth "scope" config is missing`);
    }

    const baseUrl = 'https://app.hubspot.com/oauth/authorize';

    const params = new URLSearchParams({
      response_type: 'code',
      client_id: clientId,
      redirect_uri: redirectUri,
      scope: scopes,
    });

    const authUrl = `${baseUrl}?${params.toString()}`;
    return authUrl;
  }

  async accessDetailsFrom(authCode: string, source_options: any, resetSecureData = false): Promise<object> {
    if (resetSecureData) {
      return [

View on GitHub (pinned to 20602a8e10)

Solutions

  1. Open the datasource config and enter at least one HubSpot OAuth scope, e.g. 'contacts' (space-separated for multiple: 'contacts companies deals').
  2. Confirm the value matches the shape getOAuthCredentials expects (string truthy). If it expects a string and you supplied an array, serialize it.
  3. Match the scopes against what is registered on the HubSpot app (HubSpot will reject scopes not declared on the app).
  4. Re-trigger authUrl() after saving to confirm the redirect URL builds.

Example fix

// before — datasource config with empty scopes
source_options = {
  oauth_type: { value: 'custom' },
  client_id: '...',
  client_secret: '...',
  scopes: '' // or undefined
};

// after
source_options = {
  oauth_type: { value: 'custom' },
  client_id: '...',
  client_secret: '...',
  scopes: 'contacts companies deals'
};
Defensive patterns

Strategy: validation

Validate before calling

function ensureHubSpotScopes(source_options) {
  const scopes = source_options?.scopes?.value ?? source_options?.scopes;
  if (!scopes || (typeof scopes === 'string' && scopes.trim() === '')) {
    throw new Error('HubSpot OAuth scopes are required (e.g. "contacts companies")');
  }
  return scopes;
}
ensureHubSpotScopes(source_options);

Type guard

function hasHubSpotScopes(source_options): boolean {
  const s = source_options?.scopes?.value ?? source_options?.scopes;
  return typeof s === 'string' && s.trim().length > 0;
}

Try / catch

try {
  const url = plugin.authUrl(source_options);
} catch (e) {
  if (/scope.*missing/.test(e.message)) {
    return { action: 'configure_scopes' };
  }
  throw e;
}

Prevention

When it happens

Trigger: Datasource config has clientId and clientSecret filled but the scopes field is empty or undefined. Happens when the form field is optional in the UI but the OAuth flow requires at least one scope (HubSpot requires explicit scopes; empty scope is rejected).

Common situations: New datasource setup where the operator skipped the scopes input; migration that preserved credentials but dropped scopes; scopes were set as an array but getOAuthCredentials expected a space- or comma-delimited string (or vice versa) and returned falsy.

Related errors


AI-assisted analysis of ToolJet/ToolJet@20602a8e10 (2026-08-13). Data as JSON: /api/errors/ab64a02dddded95f. Report an issue: GitHub.