ToolJet/ToolJet · error · QueryError

INVALID_QUERY_OPTIONS

INVALID_QUERY_OPTIONS

Error message

Expected queryOptions to be an object

What it means

Validation error (code INVALID_QUERY_OPTIONS) thrown by convertQueryOptions() (index.ts:61) when queryOptions is null/undefined or not typeof 'object'. It is a defensive type guard at the entry of query shaping, before any operation/params are read.

Source

Thrown at marketplace/plugins/xero/lib/index.ts:61

    const encodedScope = encodeURIComponent(finalScope);
    const baseUrl =
      `https://login.xero.com/identity/connect/authorize?response_type=code&client_id=${clientId}` +
      `&redirect_uri=${fullUrl}oauth2/authorize`;

    return `${baseUrl}&scope=${encodedScope}&access_type=offline&prompt=consent`;
  }

  private convertQueryOptions(queryOptions: any, customHeaders?: Record<string, string>): any {
    if (!queryOptions || typeof queryOptions !== 'object') {
      const errorMessage = 'Expected queryOptions to be an object';
      const errorDetails = {
        message: errorMessage,
        name: 'InvalidQueryOptionsError',
        code: 'INVALID_QUERY_OPTIONS',
        received: queryOptions,
        expected: 'object',
      };
      throw new QueryError('Invalid configuration', errorMessage, errorDetails);
    }

    const { operation, params = {} } = queryOptions;
    const method = typeof operation === 'string' ? operation.toLowerCase() : 'get';
    const result: ConvertedFormat = {
      method,
      headers: customHeaders || {},
    };

    if (params?.query && Object.keys(params.query).length > 0) {
      const urlParams = new URLSearchParams();
      Object.entries(params.query).forEach(([key, value]) => {
        if (value !== undefined && value !== null) {
          if (Array.isArray(value)) {
            value.forEach((v) => urlParams.append(key, String(v)));
          } else {
            urlParams.append(key, String(value));
          }

View on GitHub (pinned to 20602a8e10)

Solutions

  1. Ensure queryOptions is a plain object (e.g. { operation: 'GET', params: {...} }) before calling the plugin.
  2. Add a caller-side type check and default to {} if absent.
  3. If passing JSON, validate it parses to an object, not a primitive.

Example fix

// before
queryOptions = null;

// after
queryOptions = { operation: 'GET', params: { path: '/Connections' } };
Defensive patterns

Strategy: type-guard

Validate before calling

if (!queryOptions || typeof queryOptions !== 'object' || Array.isArray(queryOptions)) {
  throw new Error('queryOptions must be a plain object');
}

Type guard

function isQueryOptionsObject(q: any): q is Record<string, unknown> {
  return !!q && typeof q === 'object' && !Array.isArray(q);
}

Prevention

When it happens

Trigger: Calling run() (or any path that invokes convertQueryOptions) with queryOptions = null, undefined, a string, a number, or an array; a caller passing a JSON-parsed value that came back non-object.

Common situations: Programmatic caller forgetting to construct queryOptions, a UI binding that sent an empty/null value, deserialisation returning a primitive instead of an object.

Related errors


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