twentyhq/twenty · error · Error
createPerson did not return an id
Error message
createPerson did not return an id
What it means
findOrCreatePersonId in import-opportunity-from-tft guards the createPerson mutation result the same way as the company guard. After selecting createPerson.id, an undefined id means the server returned a null person payload rather than throwing. It exists because a Person insert can be silently rejected (e.g. email uniqueness handled server-side, or field validation) while the transport still reports success.
Source
Thrown at packages/twenty-apps/internal/twenty-partners/src/modules/opportunity/intake/services/import-opportunity-from-tft.service.ts:75
const lastName = isNonEmptyString(pointOfContact?.lastName)
? pointOfContact.lastName.trim()
: '';
if (email === undefined && firstName === '' && lastName === '') return undefined;
if (email !== undefined) {
const existing = await findPersonIdByPrimaryEmail(client, email);
if (existing !== undefined) return existing;
}
const personData: CoreSchema.PersonCreateInput = { name: { firstName, lastName } };
if (email !== undefined) personData.emails = { primaryEmail: email };
if (companyId !== undefined) personData.companyId = companyId;
const result = await client.mutation({
createPerson: { __args: { data: personData }, id: true },
});
const id = result.createPerson?.id;
if (id === undefined) throw new Error('createPerson did not return an id');
return id;
}
// Manual one-way copy of one Opportunity from the TFT workspace into partners; idempotent on
// tftOpportunityId (name as a fallback for manual calls).
export async function importOpportunityFromTft(
input: ImportOpportunityFromTftInput,
): Promise<ImportOpportunityFromTftResult> {
try {
const client = new CoreApiClient();
const name = input.name.trim();
const tftOpportunityId = isNonEmptyString(input.tftOpportunityId)
? input.tftOpportunityId.trim()
: undefined;
const dedupeFilter: CoreSchema.OpportunityFilterInput =
tftOpportunityId !== undefined
? { tftOpportunityId: { eq: tftOpportunityId } }View on GitHub (pinned to 1f5dd2bbd2)
Solutions
- Log the full result object to see whether createPerson is null or whether an errors array accompanied it.
- Confirm the caller has Person create permission and that the Person object/fields are synced in metadata.
- Validate firstName/lastName/email are non-empty and the email is well-formed before calling createPerson.
- Reproduce createPerson in the GraphQL playground against the same workspace to read the server's rejection message.
Example fix
// before
const result = await client.mutation({
createPerson: { __args: { data: personData }, id: true },
});
const id = result.createPerson?.id;
if (id === undefined) throw new Error('createPerson did not return an id');
// after — guard input + richer error
const result = await client.mutation({
createPerson: { __args: { data: personData }, id: true },
});
const id = result.createPerson?.id;
if (id === undefined) {
throw new Error(
`createPerson did not return an id for email=${email} (result=${JSON.stringify(result)})`,
);
} Defensive patterns
Strategy: type-guard
Validate before calling
import { isNonEmptyString } from 'twenty-shared';
function assertPersonInput(input: { name?: unknown; emails?: unknown }) {
// name is required server-side; email strongly recommended for dedup
if (!input.name || typeof input.name !== 'object') {
throw new Error('createPerson requires a name object');
}
} 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)}`);
} Try / catch
try {
const id = await findOrCreatePersonId(client, input.pointOfContact, companyId);
// use id
} catch (err) {
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
} Prevention
- Validate email/firstName/lastName are non-empty and email is well-formed before createPerson.
- Always select and assert id on createPerson results.
- Log the raw response when the id is missing to distinguish null payload from swallowed errors.
- Confirm Person create permission and metadata sync.
When it happens
Trigger: createPerson mutation returns { createPerson: null } or an object lacking id. Happens when the Person object metadata is stale, the caller lacks Person create permission, the supplied email/name fails server validation, or a create-trigger returns null.
Common situations: Importing a TFT contact whose email is malformed or already mapped; running the import under a role without Person create rights; workspace not re-synced after a Person field change; partial GraphQL response with errors swallowed by the SDK.
Related errors
- createCompany did not return an id
- createOpportunity did not return an id
- createOpportunity did not return an id
- createCompany did not return an id
- createPartner did not return an id
AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12).
Data as JSON: /api/errors/0cf71b8afbaf690b.
Report an issue: GitHub.