ToolJet/ToolJet · error · QueryError

Invalid JSON message

Error message

Invalid JSON message

What it means

QueryError thrown by the legacy grpc plugin when queryOptions.jsonMessage is present but JSON.parse fails. The rpc call needs a JSON object as its request payload, so a syntactically invalid string is rejected before invoking the method. The error carries empty objects for data and metadata, so the original parse message is lost.

Source

Thrown at plugins/packages/grpc/lib/index.ts:54

    if (authType === 'basic') {
      metadata.add('username', sourceOptions.username);
      metadata.add('password', sourceOptions.password);
    }

    if (authType === 'bearer') {
      metadata.add('Authorization', `Bearer ${sourceOptions.bearer_token}`);
    }

    if (authType === 'api_key') {
      metadata.add(sourceOptions.grpc_apikey_key, sourceOptions.grpc_apikey_value);
    }

    let jsonMessage = {};
    if (queryOptions.jsonMessage) {
      try {
        jsonMessage = JSON.parse(queryOptions.jsonMessage);
      } catch (e) {
        throw new QueryError('Invalid JSON message', {}, {});
      }
    }

    const result = await new Promise((resolve, reject) => {
      clientStub[rpc](jsonMessage, metadata, (err: any, response: any) => {
        if (err) {
          reject(err);
        }
        resolve(response);
      });
    }).catch((err) => {
      throw new QueryError(err.message, {}, {});
    });

    return {
      status: 'ok',
      data: result as any,
    };

View on GitHub (pinned to 20602a8e10)

Solutions

  1. Validate the message parses as JSON before calling run (JSON.parse in a try/catch).
  2. Use strict JSON: double quotes, no trailing commas, no comments, no undefined.
  3. If the payload contains variables, render then re-validate before sending.

Example fix

// before
run(srcOpts, { serviceName, rpc, jsonMessage: "{name: 'foo',}" });
// after
run(srcOpts, { serviceName, rpc, jsonMessage: '{"name":"foo"}' });
Defensive patterns

Strategy: validation

Validate before calling

function parseJsonMessage(raw: string): Record<string, unknown> {
  try {
    return JSON.parse(raw);
  } catch {
    throw new Error('jsonMessage is not valid JSON');
  }
}
if (queryOptions.jsonMessage) parseJsonMessage(queryOptions.jsonMessage);
await grpcPlugin.run(sourceOptions, queryOptions, dataSourceId);

Type guard

const isJsonString = (s: string): boolean => {
  try { JSON.parse(s); return true; } catch { return false; }
};

Try / catch

try {
  await grpcPlugin.run(sourceOptions, queryOptions, dataSourceId);
} catch (e) {
  if (e instanceof QueryError && e.message === 'Invalid JSON message') {
    surfaceJsonError(queryOptions.jsonMessage);
  }
  throw e;
}

Prevention

When it happens

Trigger: User enters a request message with a trailing comma, single quotes, or unquoted keys; copy/paste of a JS object literal instead of JSON; an empty-but-non-empty string of whitespace past the truthiness check.

Common situations: Query editor with hand-typed JSON; payload templated with a variable that rendered to invalid JSON; mixing JS syntax (undefined, comments) into the message.

Understand the failure class

Related errors


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