{"record":{"id":"6091bf7d77ba4a52","repo":"remix-run/remix","slug":"invalid-validator-result-for-table-tablename","errorCode":null,"errorMessage":"Invalid validator result for table \"' + tableName + '\"","messagePattern":"Invalid validator result for table \"' \\+ tableName \\+ '\"","errorType":"validation","errorClass":"DataTableValidationError","httpStatus":null,"severity":"error","filePath":"packages/data-table/src/lib/database/write-lifecycle.ts","lineNumber":327,"sourceCode":"  let validator = getTableValidator(table)\n\n  if (!validator) {\n    return normalizedInput\n  }\n\n  let validationResult = validator({\n    operation,\n    tableName,\n    value: normalizedInput as Partial<TableRow<table>>,\n  })\n  assertSynchronousCallbackResult(tableName, operation, 'validate', validationResult)\n\n  if (hasIssues(validationResult)) {\n    throwValidationIssues(tableName, validationResult.issues, operation, 'validate')\n  }\n\n  if (!hasValue(validationResult)) {\n    throw new DataTableValidationError(\n      'Invalid validator result for table \"' + tableName + '\"',\n      [{ message: 'Expected validator to return { value } or { issues }' }],\n      {\n        metadata: {\n          table: tableName,\n          operation,\n          source: 'validate',\n        },\n      },\n    )\n  }\n\n  return normalizeWriteObject(table, validationResult.value, operation, 'validate')\n}\n\nfunction hasIssues(value: unknown): value is { issues: ReadonlyArray<ValidationIssue> } {\n  return typeof value === 'object' && value !== null && 'issues' in value\n}","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/data-table/src/lib/database/write-lifecycle.ts#L309-L345","documentation":"The per-table validator must return either { value } (validated, possibly-coerced values) or { issues } (validation failures). Any other return shape triggers a DataTableValidationError during validateWriteValues. This distinguishes 'validator forgot to return' from an actually-passing validation.","triggerScenarios":"A validator function that returns undefined, returns the plain values object, or returns a Zod-style result without adapting it to the { value } / { issues } contract.","commonSituations":"Using a schema library inside the validator and returning its raw result; refactoring validators from async/safeParse patterns; forgetting that in-place mutation is not enough.","solutions":["Return { value: validatedValues } on success","Return { issues: [{ message, path }] } on failure instead of throwing yourself","If wrapping a schema library, map its result: parse.ok ? { value: parse.data } : { issues: parse.error.issues }"],"exampleFix":"// before\nvalidator: (values) => {\n  mySchema.parse(values) // throws or returns undefined\n}\n\n// after\nvalidator: (values) => {\n  let parsed = mySchema.safeParse(values)\n  return parsed.success\n    ? { value: parsed.data }\n    : { issues: parsed.error.issues }\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isValidValidatorResult(result: unknown): boolean {\n  return (\n    typeof result === 'object' &&\n    result !== null &&\n    ('value' in result || 'issues' in result)\n  )\n}","tryCatchPattern":"try {\n  await table.create({ values })\n} catch (error) {\n  if (error instanceof DataTableValidationError) {\n    // error.issues contains per-field problems to show the user\n    return json({ errors: error.issues }, { status: 400 })\n  }\n  throw error\n}","preventionTips":["Adapt schema-library results to the { value } / { issues } contract explicitly","Unit-test validators with both valid and invalid inputs","Never let a validator fall through without returning"],"tags":["data-table","validator","lifecycle-hooks","validation"],"backgroundTag":"invalid-validator-return-shape","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}