twentyhq/twenty · error · Error

GraphQL errors: ${json.errors.map((e) => e.message).join(';

Error message

GraphQL errors: ${json.errors.map((e) => e.message).join('; ')}

What it means

configure-partner-rls's metadata fetch helper throws when the GraphQL response carries a non-empty errors array. It joins all error messages into one string. This surfaces server-side rejections from the metadata API (auth, query validation, permission) rather than treating them as silent no-ops.

Source

Thrown at packages/twenty-apps/internal/twenty-partners/src/scripts/configure-partner-rls.ts:141

async function metadataFetch<T>(
  metadataUrl: string,
  apiKey: string,
  query: string,
  variables?: Record<string, unknown>,
): Promise<T> {
  const res = await fetch(metadataUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${apiKey}`,
    },
    body: JSON.stringify({ query, variables }),
  });

  const json = (await res.json()) as MetadataResponse<T>;

  if (json.errors && json.errors.length > 0) {
    throw new Error(
      `GraphQL errors: ${json.errors.map((e) => e.message).join('; ')}`,
    );
  }

  return json.data;
}

// Pages through an object's fields until it finds a field with the given name.
// Uses cursor-based pagination to avoid truncation on large objects (company/opportunity
// have >200 fields, so a single 200-cap request may miss partnerUser).
async function findFieldByName(
  metadataUrl: string,
  apiKey: string,
  objectId: string,
  objectName: string,
  fieldName: string,
): Promise<string> {
  let after: string | null = null;

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. Read the joined messages — they name the exact GraphQL error (auth, field-not-found, etc.).
  2. Verify TWENTY_PARTNERS_API_KEY is valid for the workspace and has metadata read access.
  3. Confirm the app is installed and synced in the target workspace before running the script.
  4. Reproduce the failing query in the GraphQL playground with the same key to see full error extensions.

Example fix

// before
if (json.errors && json.errors.length > 0) {
  throw new Error(`GraphQL errors: ${json.errors.map((e) => e.message).join('; ')}`);
}

// after — include error codes/paths if present
if (json.errors && json.errors.length > 0) {
  const detail = json.errors.map((e) => `[${e.extensions?.code ?? '?'}] ${e.message} (path=${JSON.stringify(e.path)})`).join('; ');
  throw new Error(`GraphQL errors: ${detail}`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

import { config } from 'dotenv';
config({ path: process.env.ENV_FILE ?? '.env.local' });

// Pre-check the key is present and the URL reachable before the first metadata call.
const apiKey = process.env.TWENTY_PARTNERS_API_KEY;
const baseUrl = process.env.TWENTY_PARTNERS_API_URL?.replace(/\/$/, '');
if (!apiKey || !baseUrl) {
  throw new Error('TWENTY_PARTNERS_API_URL and TWENTY_PARTNERS_API_KEY must be set');
}

Try / catch

try {
  await configureRls();
} catch (err) {
  // The joined GraphQL messages name the exact server-side error.
  // Common: authorization (bad key), field-not-found (metadata drift).
  console.error('rls:configure failed:', err instanceof Error ? err.message : err);
  process.exit(1);
}

Prevention

When it happens

Trigger: A POST to the metadata endpoint returns JSON with errors: [{ message }, ...]. Causes: TWENTY_PARTNERS_API_KEY invalid/expired (permission/authorization error); the query references a field/object not present in this workspace; metadata API version mismatch; malformed query.

Common situations: Wrong/rotated API key; workspace where the app isn't installed so metadata queries for partner objects fail; metadata API changed across server versions; copy-paste error in the query string.

Related errors


AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12). Data as JSON: /api/errors/839cfa876a45dae4. Report an issue: GitHub.