apache/superset · error · DashboardSlugConflictError
Dashboard cannot be restored because its slug is now used by
Error message
Dashboard cannot be restored because its slug is now used by another active dashboard. Rename one of the dashboards and retry.
What it means
DashboardSlugConflictError raised by DashboardRestoreCommand.validate (restore.py:63) when the dashboard being restored from the trash has a slug that is now owned by another ACTIVE dashboard. Slug uniqueness among active rows is enforced by the partial index ix_dashboards_active_slug, so restoring would create two active rows with the same slug; the command pre-checks and raises this readable domain error instead of an opaque IntegrityError at flush time. Note the guard checks 'is not None', so even an empty-string slug participates.
Source
Thrown at superset/commands/dashboard/restore.py:63
restore_failed_exc = DashboardRestoreFailedError
def validate(self) -> Dashboard: # type: ignore[override]
"""Extend ``BaseRestoreCommand.validate`` with a slug-conflict pre-check.
Raises ``DashboardSlugConflictError`` when the dashboard has a
``slug`` that has been claimed by another active dashboard while
this one was soft-deleted. Surfacing the conflict as a domain
error here keeps callers from seeing an opaque ``IntegrityError``
at flush time on dialects with the partial index, and a
constraint-violation 500 on dialects without it.
"""
model = super().validate()
# Check ``is not None`` rather than truthiness: an empty-string slug is
# still subject to the partial unique index, so it must be guarded too
# (a falsy "" would otherwise skip the pre-check and fail later with an
# opaque IntegrityError).
if model.slug is not None and self._has_active_slug_twin(model):
raise DashboardSlugConflictError()
return model
@staticmethod
def _has_active_slug_twin(model: Dashboard) -> bool:
"""Return True iff another active dashboard already owns this slug.
Slug uniqueness is enforced only among active rows (via the
partial index ``ix_dashboards_active_slug``). If the slug has
been claimed since this dashboard was soft-deleted, the restore
would create two active rows with the same slug — caught here
so it surfaces as a readable domain error rather than an opaque
``IntegrityError`` at flush time.
Delegates to ``DashboardDAO.validate_update_slug_uniqueness`` so
the active-slug-twin rule has exactly one implementation — the
update path, this explicit restore, and the importer's
restore-with-update all consult the same predicate (which relies
on the ``SoftDeleteMixin`` listener to consider only activeView on GitHub (pinned to f4587218dd)
Solutions
- Rename or clear the slug of the currently active dashboard that owns the slug (PUT /api/v1/dashboard/<other_id> with {"slug": "new-slug"}), then retry the restore.
- Alternatively remove the slug from the soft-deleted dashboard before restoring — but since it is in the trash, the practical route is renaming the active twin.
- If the active twin is itself disposable, hard-delete it, then restore.
- After restoring, verify only one active dashboard carries the slug (the partial index only guards active rows).
Example fix
# before: restore fails with slug conflict
POST /api/v1/dashboard/42/restore
# -> DashboardSlugConflictError
# after: rename the active twin first, then restore
PUT /api/v1/dashboard/7
{"slug": "sales-old"}
POST /api/v1/dashboard/42/restore Defensive patterns
Strategy: validation
Validate before calling
from superset.daos.dashboard import DashboardDAO
from superset.models.dashboard import Dashboard
def slug_is_free_for_restore(model: Dashboard) -> bool:
if model.slug is None:
return True
twin = (
db.session.query(Dashboard.id)
.filter(Dashboard.slug == model.slug, Dashboard.id != model.id, ~Dashboard.deleted_at.isnot(None) is False)
.filter(Dashboard.deleted_at.is_(None))
.first()
)
return twin is None Try / catch
from superset.commands.dashboard.exceptions import DashboardSlugConflictError
try:
DashboardRestoreCommand(model_id).run()
except DashboardSlugConflictError:
# rename the active twin, then retry
... Prevention
- Before restoring from trash, check the slug is unclaimed among active dashboards.
- When re-importing dashboards after a delete, prefer new slugs rather than recycling the old one.
- Remember empty-string slugs conflict too — the guard uses 'is not None', not truthiness.
When it happens
Trigger: POST /api/v1/dashboard/<id>/restore after, while this dashboard was soft-deleted, another dashboard was created or renamed with the same slug (including slug='').
Common situations: Delete a dashboard, recreate it with the same slug (e.g. re-importing a bundle), then try to restore the original from trash; two admins working concurrently; bulk import creating a slug twin.
Related errors
- Dashboard cannot be restored via re-import because its slug
- Chart {existing.slice_name!r} (uuid {config['uuid']}) was de
- Dashboard parameters are invalid.
- Dashboard was deleted and re-import requires can_write permi
- A dashboard already exists and user doesn't have permissions
AI-assisted analysis of apache/superset@f4587218dd (2026-08-14).
Data as JSON: /api/errors/60f2c2a9880e11a9.
Report an issue: GitHub.