{"record":{"id":"de020af9759cb483","repo":"Budibase/budibase","slug":"resource-not-found-body-resourceid","errorCode":null,"errorMessage":"Resource not found: ${body.resourceId}","messagePattern":"Resource not found: (.+?)","errorType":"http","errorClass":"HTTPError","httpStatus":404,"severity":"error","filePath":"packages/server/src/api/controllers/workspaceFavourites.ts","lineNumber":94,"sourceCode":"    [WorkspaceResource.DATASOURCE]: sdk.datasources.get,\n    [WorkspaceResource.QUERY]: sdk.queries.find,\n    [WorkspaceResource.VIEW]: sdk.views.get,\n    [WorkspaceResource.AGENT]: sdk.ai.agents.getOrThrow,\n  }\n  const verifyResource: ResourceGetter = check[body.resourceType]\n\n  // Abort if it can't be verified\n  if (!verifyResource)\n    ctx.throw(\n      `Workspace favourite failure. Could not verify resource ${body.resourceId}, ${body.resourceType}`\n    )\n\n  // Attempt to find the resource to ensure that it exists\n  try {\n    const doc = await verifyResource(body.resourceId)\n    // tryGet doesnt throw so in the event that a resource doesn't throw, ensure it.\n    if (!doc) {\n      throw new HTTPError(`Resource not found: ${body.resourceId}`, 404)\n    }\n  } catch (e: any) {\n    ctx.throw(\n      404,\n      `Workspace favourite failure. Could not verify resource: ${body.resourceId}. ${e.message}`\n    )\n  }\n\n  const workspaceFavourite = await sdk.workspace.create(newFavourite)\n  ctx.status = 201\n  ctx.body = {\n    ...workspaceFavourite,\n  } as AddWorkspaceFavouriteResponse\n}\n\nexport async function destroy(\n  ctx: UserCtx<void, DeleteWorkspaceFavouriteResponse>\n) {","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/api/controllers/workspaceFavourites.ts#L76-L112","documentation":"Creating a workspace favourite first verifies the target resource exists via verifyResource. If the lookup returns no document (tryGet semantics) or the resource cannot be verified, a 404 HTTPError with the resource ID is raised, and any verification error is re-thrown as a 404 with a descriptive message.","triggerScenarios":"POSTing a favourite whose body.resourceId references a deleted or non-existent table/row/view/query/datasource, or an ID from a different workspace.","commonSituations":"Stale UI state favouriting an item deleted in another tab; copied resource IDs across apps; resources removed by import/restore operations.","solutions":["Verify the resource still exists (fetch its doc) and use a valid resourceId.","Check the request is made in the workspace context of the app that owns the resource.","Remove the stale favourite request from the client and refresh the resource list.","If the resource was deleted intentionally, delete the favourite instead of creating it."],"exampleFix":"// before\nawait api.post('/api/favourites', { resourceId: deletedRowId })\n// after\nconst table = await api.get(`/api/tables/${tableId}`)\nif (table) await api.post('/api/favourites', { resourceId: tableId })","handlingStrategy":"validation","validationCode":"const doc = await verifyResource(resourceId)\nif (!doc) return res.status(404).json({ error: `Resource ${resourceId} does not exist; cannot favourite` })","typeGuard":"const resourceExists = async (id: string): boolean => Boolean(await verifyResource(id))","tryCatchPattern":"try {\n  await api.post('/api/favourites', { resourceId })\n} catch (e) {\n  if (e.status === 404 && /Could not verify resource/.test(e.message)) {\n    // refresh resource list, drop the stale favourite\n  } else throw e\n}","preventionTips":["Refresh client caches of resource lists after deletes.","Validate resourceId belongs to the current workspace before favouriting.","Clean up favourites when their resources are deleted.","Avoid hand-copying resource IDs between apps."],"tags":["favourites","http-404","not-found","validation"],"backgroundTag":"referenced-resource-missing","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}