apache/superset · error · ImportFailedError

A dashboard already exists and user doesn't have permissions

Error message

A dashboard already exists and user doesn't have permissions to restore it

What it means

ImportFailedError('A dashboard already exists and user doesn't have permissions to restore it') is raised in the v1 import restore branch when the user has can_write but fails the object-level checks on the soft-deleted dashboard: they cannot access it (can_access_dashboard false) or are neither its editor nor an admin. Restore-with-update touches an existing object, so editorship of that specific row is required.

Source

Thrown at superset/commands/dashboard/importers/v1/utils.py:352

                # without write permission — that would let a dependent import
                # (e.g. a dashboard zip referencing this dashboard) reattach to
                # a deleted dashboard.
                raise ImportFailedError(
                    "Dashboard was deleted and re-import requires can_write "
                    "permission to restore it"
                )
            # ``user`` is None on background / example-loader paths (no Flask
            # request user); combined with ``can_write`` (typically from
            # ``ignore_permissions=True``) the editorship check is intentionally
            # skipped because the caller already established trust.
            if user and (
                not security_manager.can_access_dashboard(existing)
                or (
                    not security_manager.is_editor(existing)
                    and not security_manager.is_admin()
                )
            ):
                raise ImportFailedError(
                    "A dashboard already exists and user doesn't have "
                    "permissions to restore it"
                )
            # Restore in place (clear ``deleted_at``) rather than
            # hard-delete-and-replace: a hard delete would cascade through
            # dashboard_slices junctions and editor / viewer / tag
            # associations, breaking the relationships the import would then
            # need to reconstruct.
            #
            # How the restore lands as an UPDATE: clearing
            # ``existing.deleted_at`` marks the in-session row dirty and the
            # explicit flush emits the ``deleted_at = NULL`` UPDATE before
            # ``Dashboard.import_from_dict`` (below) does its own query-by-uuid
            # lookup. Without the flush we would rely on autoflush ahead of
            # that internal query — correct under default session config but a
            # hidden contract; the explicit flush makes it robust. The lookup
            # then finds the now-live row (the listener filters
            # ``deleted_at IS NULL``) and ``import_from_dict`` applies the

View on GitHub (pinned to f4587218dd)

Solutions

  1. Have an admin perform the import (is_admin() passes the check).
  2. Or transfer ownership of the soft-deleted dashboard to the importing user (update owners via admin API), then retry.
  3. Or purge the soft-deleted row entirely so the import creates a fresh dashboard under the caller's ownership.

Example fix

# before
client.post('/api/v1/dashboard/import/', ...)  # has can_write but not editor of deleted dash

# after
# admin transfers ownership or performs the import
admin_client.put(f'/api/v1/dashboard/{id}', json={'owners': [importer_user_id]})
client.post('/api/v1/dashboard/import/', files=...)
Defensive patterns

Strategy: try-catch

Validate before calling

existing = find_existing_for_import(Dashboard, config['uuid'])
user = get_user()
if existing is not None and existing.deleted_at is not None and user:
    if not security_manager.can_access_dashboard(existing) or (
        not security_manager.is_editor(existing)
        and not security_manager.is_admin()
    ):
        raise PermissionError('restore needs admin or editorship of the deleted dashboard')

Try / catch

try:
    run_import(bundle)
except ImportFailedError as ex:
    if "doesn't have permissions to restore" in str(ex):
        transfer_ownership_then_retry() or run_as_admin()

Prevention

When it happens

Trigger: Importing a v1 bundle whose UUID matches a soft-deleted dashboard, with a user who has generic import permission but no access to / editorship of that particular dashboard (e.g. it was owned by a departed user or a service account).

Common situations: Re-importing team dashboards after the original owner left; ownership left on a service account so no human passes is_editor; multi-tenant instances where the deleted dashboard belongs to another team.

Related errors


AI-assisted analysis of apache/superset@f4587218dd (2026-08-14). Data as JSON: /api/errors/a991483a17333b17. Report an issue: GitHub.