Budibase/budibase · error
Failed to delete plugin: ${errMsg}
Error message
Failed to delete plugin: ${errMsg} What it means
deletePlugin performs several steps — removing the plugin doc from the global DB, emitting a plugin.deleted event, and decrementing the plugin quota. Any failure inside the try block (DB conflict/409, missing revision, event service error, quota decrement failure) is caught and rethrown wrapped as 'Failed to delete plugin: <cause>'. It normalizes non-Error throwables to a string message.
Source
Thrown at packages/pro/src/sdk/plugins/index.ts:124
}
}
export async function deletePlugin(pluginId: string) {
const db = tenancy.getGlobalDB()
try {
const plugin: Plugin = await db.get(pluginId)
const bucketPath = objectStore.getPluginS3Dir(plugin.name)
await objectStore.deleteFolder(
objectStore.ObjectStoreBuckets.PLUGINS,
bucketPath
)
await db.remove(pluginId, plugin._rev!)
await events.plugin.deleted(plugin)
await quotas.removePlugin()
} catch (err: any) {
const errMsg = err?.message ? err?.message : err
throw new Error(`Failed to delete plugin: ${errMsg}`)
}
}
export async function checkPluginQuotas() {
const db = tenancy.getGlobalDB()
try {
const allPlugins = await db.allDocs(dbCore.getPluginParams())
const pluginCount = allPlugins.rows.length
console.log(`Syncing plugin count: ${pluginCount}`)
await quotas.updatePluginCount(pluginCount)
} catch (err) {
logging.logAlert("Unable to retrieve plugins for quota check", err)
}
}
View on GitHub (pinned to a81a902e9a)
Solutions
- Re-fetch the plugin to get the current _rev and retry the delete
- Check the wrapped errMsg to identify whether the failure is in db.remove, events.plugin.deleted, or quotas.removePlugin
- Verify the tenant's global database is reachable and healthy (CouchDB health)
- Check for concurrent deletions / replication conflicts on the plugin doc and retry once conflicts settle
Example fix
// before: deleting with a stale plugin object
await deletePlugin(oldPlugin)
// after: refresh the doc, then delete
const db = tenancy.getGlobalDB()
const fresh = await db.get(pluginId)
await deletePlugin({ ...fresh, pluginId }) Defensive patterns
Strategy: try-catch
Validate before calling
const fresh = await db.get(pluginId) // fails fast if already deleted / rev unknown
Try / catch
try {
await deletePlugin(plugin)
} catch (err: any) {
if (String(err.message).startsWith("Failed to delete plugin:")) {
// re-fetch current _rev and retry once
const current = await tenancy.getGlobalDB().get(pluginId)
await deletePlugin({ ...plugin, _rev: current._rev })
} else { throw err }
} Prevention
- Always delete from freshly fetched plugin docs
- Avoid concurrent deletes of the same plugin
- Check CouchDB health before bulk plugin maintenance
When it happens
Trigger: Calling the plugin delete API when the plugin doc's _rev is stale (someone modified the plugin concurrently), the global DB is unreachable, the events/quotas service call fails, or the plugin doc was already deleted.
Common situations: Two admins deleting the same plugin simultaneously; CouchDB replication conflicts on the tenant's global database; network blip to the backing DB during the remove; quota service misconfiguration causing removePlugin() to throw after the doc was already removed.
Related errors
- DB does not exist
- ${err.message}
- CouchDB error: ${err.message}
- Unable to get bulk documents: ${missingIds}
- Unable to remove doc without a valid _id and _rev.
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/e823b9306b17fe48.
Report an issue: GitHub.