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
- Inspect the raw createPerson response for null payload or errors array.
- Validate email/firstName/lastName are non-empty (and email well-formed) before calling findOrCreatePersonByEmail.
- Confirm input.companyId is a valid existing Company id.
- 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
- Validate email/firstName/lastName non-empty and email well-formed before createPerson.
- Confirm companyId is a valid existing Company id.
- Always select and assert id on createPerson results.
- Confirm Person create permission and metadata sync.
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
- createCompany did not return an id
- createCompany did not return an id
- createPerson did not return an id
- createOpportunity did not return an id
- createOpportunity did not return an id
AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12).
Data as JSON: /api/errors/8e2722c9fccb263c.
Report an issue: GitHub.