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
- Ensure queryOptions is a plain object (e.g. { operation: 'GET', params: {...} }) before calling the plugin.
- Add a caller-side type check and default to {} if absent.
- 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
- Default queryOptions to {} in the caller when absent.
- Type-check at the boundary before invoking convertQueryOptions.
- Ensure JSON deserialisation yields an object, not a primitive.
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
- MISSING_OAUTH_CREDENTIALS
- Unknown specType/entity: ${specType}
- Select an operation
- Missing required query options
- Invalid operation, expected 'get', 'post', 'put', 'patch', o
AI-assisted analysis of ToolJet/ToolJet@20602a8e10 (2026-08-13).
Data as JSON: /api/errors/a89dfdbf58dc306d.
Report an issue: GitHub.