Budibase/budibase · error · HTTPError
A row action with the same name already exists.
Error message
A row action with the same name already exists.
What it means
Thrown by ensureUniqueAndThrow (called from create) in packages/server/src/sdk/workspace/rowActions/crud.ts when creating a row action whose name (lowercased and trimmed) matches an existing row action name on the same table, unless it belongs to the row action being updated. Row action names must be unique per table because they are referenced by name in generated CRUD views/permissions.
Source
Thrown at packages/server/src/sdk/workspace/rowActions/crud.ts:37
import { generateRowActionsID } from "../../../db/utils"
import automations from "../automations"
async function ensureUniqueAndThrow(
doc: TableRowActions,
name: string,
existingRowActionId?: string
) {
const names = await getNames(Object.values(doc.actions))
name = name.toLowerCase().trim()
if (
Object.entries(names).find(
([automationId, automationName]) =>
automationName.toLowerCase().trim() === name &&
automationId !== existingRowActionId
)
) {
throw new HTTPError("A row action with the same name already exists.", 409)
}
}
export async function create(tableId: string, rowAction: { name: string }) {
const action = { name: rowAction.name.trim() }
const db = context.getWorkspaceDB()
const rowActionsId = generateRowActionsID(tableId)
let doc = await db.tryGet<TableRowActions>(rowActionsId)
if (!doc) {
doc = { _id: rowActionsId, actions: {} }
}
await ensureUniqueAndThrow(doc, action.name)
const workspaceId = context.getWorkspaceId()
if (!workspaceId) {
throw new Error("Could not get the current workspace ID")View on GitHub (pinned to a81a902e9a)
Solutions
- Pick a different name for the row action that no other action on the table uses (case-insensitively).
- Before creating, fetch existing row actions for the table and check the trimmed lowercase name for collisions.
- If you meant to modify an existing action, call the update API instead of create.
Example fix
// before
await rowActions.create(tableId, { name: "Approve" })
// after
const existing = await rowActions.getAllForTable(tableId)
const clash = Object.values(existing?.actions ?? {}).some(a => a.name.toLowerCase().trim() === "approve")
if (!clash) await rowActions.create(tableId, { name: "Approve" }) Defensive patterns
Strategy: validation
Validate before calling
const actionsDoc = await rowActions.getAllForTable(tableId)
const normalized = name.toLowerCase().trim()
if (Object.values(actionsDoc?.actions ?? {}).some(a => a.name.toLowerCase().trim() === normalized)) {
throw new Error(`Row action name "${name}" already in use on this table`)
} Try / catch
try {
await rowActions.create(tableId, { name })
} catch (e) {
if (e instanceof HTTPError && e.status === 409) {
// show 'name already exists' validation message to user
} else throw e
} Prevention
- Enforce name uniqueness in the UI form (case-insensitive, trimmed) before submitting.
- Reuse the update API for changing existing actions instead of create.
- Bulk-creation scripts should dedupe names before issuing create calls.
When it happens
Trigger: Calling create(tableId, { name }) where any existing automation/row-action name on that table equals name.toLowerCase().trim(). The comparison is case-insensitive and whitespace-trimmed, so 'Run Script' collides with 'run script '.
Common situations: Users adding a second action with the same label on one table; scripts bulk-creating actions without a uniqueness check; renaming conflicts where the create path is used instead of update.
Related errors
- Custom REST template cannot be deleted while it is in use
- Project revision does not match.
- Unable to bulk remove documents: ${res.error}
- Failed to generate a unique name for the row action
- Unable to remove top level directory - some skeleton files a
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/13ad431498b54d22.
Report an issue: GitHub.