remix-run/remix · error · DataTableValidationError
Invalid beforeWrite callback result for table "' + tableName
Error message
Invalid beforeWrite callback result for table "' + tableName + '"
What it means
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.
Source
Thrown at packages/data-table/src/lib/database/write-lifecycle.ts:293
): Record<string, unknown> {
let tableName = getTableName(table)
let normalizedInput = normalizeWriteObject(table, values, operation)
let beforeWrite = getTableBeforeWrite(table)
if (beforeWrite) {
let beforeWriteResult = beforeWrite({
operation,
tableName,
value: normalizedInput as Partial<TableRow<table>>,
})
assertSynchronousCallbackResult(tableName, operation, 'beforeWrite', beforeWriteResult)
if (hasIssues(beforeWriteResult)) {
throwValidationIssues(tableName, beforeWriteResult.issues, operation, 'beforeWrite')
}
if (!hasValue(beforeWriteResult)) {
throw new DataTableValidationError(
'Invalid beforeWrite callback result for table "' + tableName + '"',
[{ message: 'Expected beforeWrite to return { value } or { issues }' }],
{
metadata: {
table: tableName,
operation,
source: 'beforeWrite',
},
},
)
}
normalizedInput = normalizeWriteObject(table, beforeWriteResult.value, operation, 'beforeWrite')
}
let validator = getTableValidator(table)
if (!validator) {View on GitHub (pinned to 9696913134)
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
Example fix
// before
beforeWrite: (values) => {
values.email = values.email.toLowerCase()
}
// after
beforeWrite: (values) => {
return { value: { ...values, email: values.email.toLowerCase() } }
} Defensive patterns
Strategy: type-guard
Type guard
function isValidBeforeWriteResult(result: unknown): boolean {
return (
typeof result === 'object' &&
result !== null &&
('value' in result || 'issues' in result)
)
} Try / catch
try {
await table.create({ values })
} catch (error) {
if (error instanceof DataTableValidationError && /beforeWrite callback result/.test(error.message)) {
// hook bug: ensure every path returns { value } or { issues }
} else throw error
} Prevention
- 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
When it happens
Trigger: beforeWrite callbacks that mutate and return undefined, return the row unwrapped, or return an arbitrary truthy value.
Common situations: 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.
Related errors
- Invalid afterRead callback result for table "' + tableName +
- Invalid beforeDelete callback result for table "' + context.
- Invalid validator result for table "' + tableName + '"
- upsert requires at least one value
- insertMany() requires at least one explicit value across the
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/02fdf9401d15b90b.
Report an issue: GitHub.