twentyhq/twenty · error · Error

createOpportunity did not return an id

Error message

createOpportunity did not return an id

What it means

submitClientBrief calls createOpportunity and asserts createOpportunity.id before notifying on the new opportunity. Same guard shape as the TFT importer: the server returned a null/empty createOpportunity instead of throwing. The service's outer try/catch converts it to { ok: false, reason }.

Source

Thrown at packages/twenty-apps/internal/twenty-partners/src/modules/opportunity/intake/services/submit-client-brief.service.ts:70

    const opportunityData: CoreSchema.OpportunityCreateInput = {
      name,
      need: input.need,
      isListed: false,
      stage: 'NEW',
      companyId,
      pointOfContactId,
    };
    if (requirements !== null) {
      opportunityData.requirements = requirements;
    }
    if (referringPartner !== null) {
      opportunityData.referredByPartnerId = referringPartner.id;
    }

    const result = await createOpportunity(client, opportunityData);
    const opportunityId = result.createOpportunity?.id;
    if (opportunityId === undefined) {
      throw new Error('createOpportunity did not return an id');
    }

    await notifyClientBrief({ opportunityId, input, referringPartner });

    return { ok: true, opportunityId };
  } catch (err) {
    return { ok: false, reason: err instanceof Error ? err.message : String(err) };
  }
}

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. Check the returned SubmitClientBriefResult.reason — it echoes this message.
  2. Inspect the raw createOpportunity response (null payload vs. errors array).
  3. Validate opportunityData required fields and relation ids (companyId, referredByPartnerId) before the call.
  4. Confirm the caller's role has Opportunity create permission and the object is synced.

Example fix

// before
const result = await createOpportunity(client, opportunityData);
const opportunityId = result.createOpportunity?.id;
if (opportunityId === undefined) {
  throw new Error('createOpportunity did not return an id');
}

// after — richer reason for the caller
const result = await createOpportunity(client, opportunityData);
const opportunityId = result.createOpportunity?.id;
if (opportunityId === undefined) {
  throw new Error(
    `createOpportunity did not return an id (referringPartner=${referringPartner?.id ?? 'none'}, result=${JSON.stringify(result)})`,
  );
}
Defensive patterns

Strategy: try-catch

Validate before calling

function assertClientBriefOpportunityData(data: Record<string, unknown>) {
  if (!data.stage) throw new Error('opportunityData.stage is required');
  if (requirements !== null) data.requirements = requirements; // ensure non-null
}
assertClientBriefOpportunityData(opportunityData);

Type guard

const hasCreatedOpportunityId = (r: unknown): r is { createOpportunity: { id: string } } =>
  typeof r === 'object' && r !== null &&
  typeof (r as any).createOpportunity?.id === 'string';

if (!hasCreatedOpportunityId(result)) {
  throw new Error(`createOpportunity returned no id: ${JSON.stringify(result)}`);
}

Try / catch

// submitClientBrief already wraps this in try/catch returning { ok, reason }.
// Callers branch on result.ok:
const res = await submitClientBrief(input);
if (!res.ok) {
  logger.warn('client brief submit failed', { reason: res.reason });
  return;
}

Prevention

When it happens

Trigger: createOpportunity returns null or an id-less payload. Triggered by missing required fields in opportunityData, an invalid referringPartnerId/referredByPartnerId relation, server-side validation failure, or insufficient Opportunity create permission.

Common situations: Submitting a client brief with incomplete requirements or an unknown referring partner; role lacks Opportunity create; metadata drift; amount/stage outside allowed value list.

Related errors


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