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

  1. Re-fetch the plugin to get the current _rev and retry the delete
  2. Check the wrapped errMsg to identify whether the failure is in db.remove, events.plugin.deleted, or quotas.removePlugin
  3. Verify the tenant's global database is reachable and healthy (CouchDB health)
  4. 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

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


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/e823b9306b17fe48. Report an issue: GitHub.