{"record":{"id":"f47e465004879871","repo":"Budibase/budibase","slug":"err-message-err-rethrown-as-httperror-f47e46","errorCode":null,"errorMessage":"err.message || err (rethrown as HTTPError)","messagePattern":"err\\.message \\|\\| err \\(rethrown as HTTPError\\)","errorType":"http","errorClass":"HTTPError","httpStatus":500,"severity":"error","filePath":"packages/server/src/sdk/workspace/tables/internal/index.ts","lineNumber":63,"sourceCode":"  const isImport = !!rows\n\n  if (!tableToSave.views) {\n    tableToSave.views = {}\n  }\n\n  try {\n    const { table } = await save(tableToSave, {\n      userId,\n      rowsToImport: rows,\n      isImport,\n    })\n\n    return table\n  } catch (err: any) {\n    if (err instanceof Error) {\n      throw new HTTPError(err.message, 400)\n    } else {\n      throw new HTTPError(err.message || err, err.status || 500)\n    }\n  }\n}\n\nexport async function save(\n  table: Table,\n  opts?: {\n    userId?: string\n    tableId?: string\n    rowsToImport?: Row[]\n    renaming?: RenameColumn\n    isImport?: boolean\n  }\n) {\n  const db = context.getWorkspaceDB()\n\n  // if the table obj had an _id then it will have been retrieved\n  let oldTable: Table | undefined","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/tables/internal/index.ts#L45-L81","documentation":"This is the catch-all in the internal table create() wrapper: when save() throws something that is not an Error instance (e.g. a plain string or a bare object from CouchDB/pouch), it is rethrown as an HTTPError using err.message || err with err.status or a 500 fallback. It exists so non-Error throwables from lower layers still surface as a proper HTTP response.","triggerScenarios":"Calling sdk.tables.create (POST /tables) when an inner save() call throws a non-Error value, such as a PouchDB/CouchDB error object that lacks Error.prototype or a thrown string, instead of the standard Error instances thrown by validation.","commonSituations":"CouchDB/PouchDB layer throwing non-standard error payloads; older plugin or integration code throwing strings; proxied DB errors surfacing as plain objects.","solutions":["Inspect the HTTPError status (400 vs 500) and message to identify the underlying save() failure","Log the original err payload server-side before it is coerced, since the string form loses structure","Ensure inner layers throw Error or HTTPError instances so the 400 branch is taken with the real message","Upgrade backend-core/pouch layers if known bugs throw raw objects"],"exampleFix":"// before (inner code)\nthrow 'Table validation failed'\n// after\nthrow new Error('Table validation failed')","handlingStrategy":"try-catch","validationCode":"// ensure inner layers throw Errors\nfunction assertError(e: unknown): e is Error { return e instanceof Error }","typeGuard":"function isError(e: unknown): e is Error {\n  return e instanceof Error\n}","tryCatchPattern":"try {\n  await sdk.tables.create(payload)\n} catch (e) {\n  if (isError(e)) {\n    // e.message is the real validation message, status 400\n  } else {\n    // coerced non-Error throw; log raw value\n  }\n}","preventionTips":["Throw Error/HTTPError instances in custom code, never raw strings","Log original error payloads before they are coerced into HTTPError","Check HTTPError status to distinguish validation (400) from server (500) issues"],"tags":["http","error-handling","tables"],"backgroundTag":"non-error-thrown","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}