twentyhq/twenty · error · Error

createPerson did not return an id

Error message

createPerson did not return an id

What it means

findOrCreatePersonByEmail in the shared service guards createPerson result. After an email dedup lookup it creates a Person and asserts createPerson.id. An undefined id means the server returned a null/id-less person payload rather than throwing.

Source

Thrown at packages/twenty-apps/internal/twenty-partners/src/modules/shared/services/find-or-create-company-and-person.service.ts:64

  firstName: string;
  lastName: string;
  companyId: string;
};

export async function findOrCreatePersonByEmail(
  client: CoreApiClient,
  input: FindOrCreatePersonByEmailInput,
): Promise<string> {
  const email = input.email.trim();
  const firstName = input.firstName.trim();
  const lastName = input.lastName.trim();

  const existing = await findPersonIdByPrimaryEmail(client, email);
  if (existing !== undefined) return existing;

  const result = await createPerson(client, { email, firstName, lastName, companyId: input.companyId });
  const id = result.createPerson?.id;
  if (id === undefined) throw new Error('createPerson did not return an id');
  return id;
}

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. Inspect the raw createPerson response for null payload or errors array.
  2. Validate email/firstName/lastName are non-empty (and email well-formed) before calling findOrCreatePersonByEmail.
  3. Confirm input.companyId is a valid existing Company id.
  4. Verify the caller's role has Person create permission and Person is synced.

Example fix

// before
const result = await createPerson(client, { email, firstName, lastName, companyId: input.companyId });
const id = result.createPerson?.id;
if (id === undefined) throw new Error('createPerson did not return an id');

// after — guard inputs and surface result
if (!email || !firstName || !lastName) throw new Error('email/firstName/lastName required');
const result = await createPerson(client, { email, firstName, lastName, companyId: input.companyId });
const id = result.createPerson?.id;
if (id === undefined) {
  throw new Error(`createPerson did not return an id (email=${email}, result=${JSON.stringify(result)})`);
}
Defensive patterns

Strategy: type-guard

Validate before calling

import { isNonEmptyString } from 'twenty-shared';

export async function findOrCreatePersonByEmailSafe(client: CoreApiClient, input: FindOrCreatePersonByEmailInput) {
  if (!isNonEmptyString(input.email) || !isNonEmptyString(input.firstName) || !isNonEmptyString(input.lastName)) {
    throw new Error('email, firstName, lastName are required');
  }
  if (!input.companyId) throw new Error('companyId is required');
  return findOrCreatePersonByEmail(client, input);
}

Type guard

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

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

Prevention

When it happens

Trigger: createPerson returns { createPerson: null } or no id. Causes: caller lacks Person create permission; email/firstName/lastName invalid or empty; Person metadata out of sync; companyId relation invalid; concurrent create beating the dedup lookup.

Common situations: Call site passing an empty email or name; role lacks Person create; manifest changed Person fields; race creating the same contact twice.

Related errors


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