{"record":{"id":"02fdf9401d15b90b","repo":"remix-run/remix","slug":"invalid-beforewrite-callback-result-for-table","errorCode":null,"errorMessage":"Invalid beforeWrite callback result for table \"' + tableName + '\"","messagePattern":"Invalid beforeWrite callback result for table \"' \\+ tableName \\+ '\"","errorType":"validation","errorClass":"DataTableValidationError","httpStatus":null,"severity":"error","filePath":"packages/data-table/src/lib/database/write-lifecycle.ts","lineNumber":293,"sourceCode":"): Record<string, unknown> {\n  let tableName = getTableName(table)\n  let normalizedInput = normalizeWriteObject(table, values, operation)\n  let beforeWrite = getTableBeforeWrite(table)\n\n  if (beforeWrite) {\n    let beforeWriteResult = beforeWrite({\n      operation,\n      tableName,\n      value: normalizedInput as Partial<TableRow<table>>,\n    })\n    assertSynchronousCallbackResult(tableName, operation, 'beforeWrite', beforeWriteResult)\n\n    if (hasIssues(beforeWriteResult)) {\n      throwValidationIssues(tableName, beforeWriteResult.issues, operation, 'beforeWrite')\n    }\n\n    if (!hasValue(beforeWriteResult)) {\n      throw new DataTableValidationError(\n        'Invalid beforeWrite callback result for table \"' + tableName + '\"',\n        [{ message: 'Expected beforeWrite to return { value } or { issues }' }],\n        {\n          metadata: {\n            table: tableName,\n            operation,\n            source: 'beforeWrite',\n          },\n        },\n      )\n    }\n\n    normalizedInput = normalizeWriteObject(table, beforeWriteResult.value, operation, 'beforeWrite')\n  }\n\n  let validator = getTableValidator(table)\n\n  if (!validator) {","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/data-table/src/lib/database/write-lifecycle.ts#L275-L311","documentation":"A beforeWrite hook must return either a result with { value } (the possibly-transformed row) or one with { issues }. If it returns a value with neither field, a DataTableValidationError is thrown during validateWriteValues, because the library cannot tell whether the write should proceed or fail.","triggerScenarios":"beforeWrite callbacks that mutate and return undefined, return the row unwrapped, or return an arbitrary truthy value.","commonSituations":"Transforming input in beforeWrite (hashing passwords, normalizing email) and forgetting to return the new object; copy-pasting afterRead-style hooks where in-place mutation was okay; early-return code paths.","solutions":["Return { value: newValues } from beforeWrite, including every path","Return { issues: [...] } to reject the write with validation issues","Double-check early returns and conditional branches inside the hook"],"exampleFix":"// before\nbeforeWrite: (values) => {\n  values.email = values.email.toLowerCase()\n}\n\n// after\nbeforeWrite: (values) => {\n  return { value: { ...values, email: values.email.toLowerCase() } }\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isValidBeforeWriteResult(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 && /beforeWrite callback result/.test(error.message)) {\n    // hook bug: ensure every path returns { value } or { issues }\n  } else throw error\n}","preventionTips":["Return { value: transformedValues } from beforeWrite on all code paths","Do async work before the write and keep beforeWrite synchronous and pure","Type-check hook return values in their signatures"],"tags":["data-table","before-write","lifecycle-hooks","validation"],"backgroundTag":"invalid-hook-return-shape","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}