remix-run/remix · error · DataTableValidationError
Invalid beforeDelete callback result for table "' + context.
Error message
Invalid beforeDelete callback result for table "' + context.tableName + '"
What it means
A beforeDelete hook may return nothing (undefined) to allow the delete, or { issues } to abort it with validation problems. Any other non-issue return value is rejected with a DataTableValidationError, because there is no meaningful 'value' transformation before a delete. The throw happens after the issues check, i.e. when the result is an unrecognized shape.
Source
Thrown at packages/data-table/src/lib/database/write-lifecycle.ts:205
): void {
let callback = getTableBeforeDelete(table)
if (!callback) {
return
}
let callbackResult = callback(context)
assertSynchronousCallbackResult(context.tableName, 'delete', 'beforeDelete', callbackResult)
if (callbackResult === undefined) {
return
}
if (hasIssues(callbackResult)) {
throwValidationIssues(context.tableName, callbackResult.issues, 'delete', 'beforeDelete')
}
throw new DataTableValidationError(
'Invalid beforeDelete callback result for table "' + context.tableName + '"',
[{ message: 'Expected beforeDelete to return nothing or { issues }' }],
{
metadata: {
table: context.tableName,
operation: 'delete',
source: 'beforeDelete',
},
},
)
}
export function runAfterWriteHook<table extends AnyTable>(
table: table,
context: TableAfterWriteContext<TableRow<table>>,
): void {
let callback = getTableAfterWrite(table)
View on GitHub (pinned to 9696913134)
Solutions
- Return nothing (undefined) from beforeDelete when the delete should proceed
- Return { issues: [{ message: 'reason' }] } to block the delete
- Audit beforeDelete callbacks for stray return statements
Example fix
// before
beforeDelete: (row) => {
return row.deletable // boolean: invalid
}
// after
beforeDelete: (row) => {
if (!row.deletable) {
return { issues: [{ message: 'Row cannot be deleted' }] }
}
// return nothing to allow the delete
} Defensive patterns
Strategy: type-guard
Type guard
function isValidBeforeDeleteResult(result: unknown): boolean {
return result === undefined || (typeof result === 'object' && result !== null && 'issues' in result)
} Try / catch
try {
await table.delete({ where })
} catch (error) {
if (error instanceof DataTableValidationError && /beforeDelete callback result/.test(error.message)) {
// hook bug: audit beforeDelete return values
} else throw error
} Prevention
- Return nothing from beforeDelete on success; use { issues } to veto
- Never return booleans from beforeDelete
- Review hooks after porting middleware from other frameworks
When it happens
Trigger: A beforeDelete callback that returns true, a string, a promise-like value that is not an issue result, or any object without issues.
Common situations: Writing beforeDelete as if it could veto with a boolean (return true to proceed); porting middleware from another framework with different conventions; returning a value out of habit from beforeWrite-style hooks.
Related errors
- Invalid afterRead callback result for table "' + tableName +
- Invalid beforeWrite callback result for table "' + tableName
- 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/bdc977c9e0fc069f.
Report an issue: GitHub.