You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/08/24 16:43:29 UTC

(superset) 02/02: chore(tags): stop auto-generating type:/editor:/favorited_by: tags

This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch chore/remove-implicit-tag-generation
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 009847e6afbb53334de70ced8ab06b49c5acf05f
Author: Claude Code <no...@anthropic.com>
AuthorDate: Mon Aug 24 09:42:31 2026 -0700

    chore(tags): stop auto-generating type:/editor:/favorited_by: tags
    
    villebro noted on #43390 that these system-generated tags appear to be
    unused. Confirmed: every tags list and filter in the frontend explicitly
    excludes non-custom tags (ChartList, DashboardList, SavedQueryList, the
    chart PropertiesModal, the dashboard Header), so nothing ever surfaced
    them to a user. What remained was pure write-side overhead: 13
    SQLAlchemy event listeners across 5 models firing on every chart/
    dashboard/query/dataset save and every favorite/unfavorite, plus a whole
    performance-optimization mixin (CustomTagsOptimizationMixin,
    DASHBOARD_LIST_CUSTOM_TAGS_ONLY) that existed purely to strip the
    resulting noise back out of dashboard-list responses.
    
    This removes the generation:
    - superset/tags/models.py: drop ObjectUpdater's editor:/type: generation
      (after_insert/after_update) and FavStarUpdater's favorited_by:
      generation entirely. Keeps after_delete (tagged_object cleanup applies
      to every tag, custom included, and nothing else removes those rows
      since tagged_object.object_id has no FK - see its column comment).
    - superset/tags/core.py: only registers the delete-cleanup listeners now.
    - superset/common/tags.py + the `sync_tags` CLI command: removed (the
      backfill path for the generation this removes).
    - superset/views/custom_tags_api_mixin.py, DASHBOARD_LIST_CUSTOM_TAGS_ONLY,
      Dashboard.custom_tags, and the schema/API plumbing built around them:
      removed - nothing left to optimize away once implicit tags stop
      accumulating.
    
    Kept for backward compatibility, since MCP's list_tags/get_tag_info tools
    document these tag types and upgraded deployments may already have rows
    of these types: the TagType enum values, the custom_tag API filter,
    and bulk-delete protection for non-custom tags. Docstrings updated to
    say these are legacy/no longer generated rather than actively implicit.
    
    Also fixes a real, currently-broken import in superset/daos/tag.py
    (current_user_can_modify_object doesn't live in
    superset.commands.tag.utils, only in superset.commands.utils) that
    otherwise blocks every test in this area from running at all. Filed and
    fixed separately as #43467; this commit will collapse away on rebase
    once that merges.
    
    Follow-up to #43390.
    
    Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
 UPDATING.md                                        |  24 +
 docs/static/resources/openapi.json                 |   7 -
 superset/app.py                                    |   3 +-
 superset/cli/update.py                             |  17 -
 superset/common/tags.py                            | 496 ---------------------
 superset/config.py                                 |   5 -
 superset/dashboards/api.py                         |  27 +-
 superset/dashboards/schemas.py                     |   7 -
 superset/mcp_service/tag/tool/get_tag_info.py      |   6 +-
 superset/mcp_service/tag/tool/list_tags.py         |   6 +-
 superset/models/dashboard.py                       |  10 -
 superset/tags/core.py                              |  78 +---
 superset/tags/models.py                            | 227 +---------
 superset/views/custom_tags_api_mixin.py            | 118 -----
 tests/integration_tests/dashboards/api_tests.py    | 104 -----
 tests/integration_tests/superset_test_config.py    |   3 -
 tests/integration_tests/tagging_tests.py           | 294 ------------
 tests/integration_tests/tags/dao_tests.py          |  46 +-
 .../integration_tests/tasks/async_queries_tests.py |   3 -
 tests/unit_tests/dashboards/api_test.py            |   1 -
 .../unit_tests/views/test_custom_tags_api_mixin.py | 109 -----
 21 files changed, 120 insertions(+), 1471 deletions(-)

diff --git a/UPDATING.md b/UPDATING.md
index a4adec96cf3..53e488e5204 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -26,6 +26,30 @@ assists people when migrating to a new version.
 
 - `SAMPLES_ROW_LIMIT` is now the default for `/datasource/samples` requests without a valid explicit `per_page`, rather than a hard per-request ceiling; explicit limits are honored up to the existing global row-limit ceiling, matching `/chart/data` SAMPLES requests.
 
+### Superset no longer auto-generates `type:`/`editor:`/`favorited_by:` tags
+
+With `TAGGING_SYSTEM` enabled, Superset used to auto-tag every chart,
+dashboard, saved query, and dataset with implicit tags derived from
+metadata (object type, editors, and who favorited it), and generate a
+`favorited_by:<user id>` tag on every favorite/unfavorite. Nothing in the
+UI ever surfaced these tags to users — every tags list and filter in the
+frontend explicitly excluded them — so the generation added continuous
+write overhead (13 SQLAlchemy event listeners across 5 models) with no
+user-visible benefit. That generation is removed.
+
+Manually-created (custom) tags are unaffected: creating, editing,
+listing, and filtering tags still works exactly as before, including the
+`custom_tag` API filter used to distinguish custom from implicit tags.
+
+Deployments already running with `TAGGING_SYSTEM` enabled keep any
+`type:`/`editor:`/`favorited_by:` tag rows created before upgrading — they
+remain queryable via the API and MCP's `list_tags`/`get_tag_info` tools,
+and are still exempt from bulk tag deletion — but no new ones are created,
+and the `superset sync_tags` CLI command that backfilled them has been
+removed. The `DASHBOARD_LIST_CUSTOM_TAGS_ONLY` config flag and the
+dashboard-list optimization it enabled are also removed, since every tag
+returned is now a custom tag by default.
+
 ### MCP tool results preserve stored string values
 
 Structured MCP tool results no longer add `<UNTRUSTED-CONTENT>` wrappers or
diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json
index f3f29908b55..750f88d2350 100644
--- a/docs/static/resources/openapi.json
+++ b/docs/static/resources/openapi.json
@@ -3257,12 +3257,6 @@
             "description": "Override CSS for the dashboard.",
             "type": "string"
           },
-          "custom_tags": {
-            "items": {
-              "$ref": "#/components/schemas/Tag1"
-            },
-            "type": "array"
-          },
           "dashboard_title": {
             "description": "A title for the dashboard.",
             "type": "string"
@@ -17207,7 +17201,6 @@
                     "charts": [],
                     "created_on_delta_humanized": "string",
                     "css": "string",
-                    "custom_tags": [],
                     "dashboard_title": "string",
                     "id": 1,
                     "is_managed_externally": true,
diff --git a/superset/app.py b/superset/app.py
index fb7ba012b7a..83e1f19b143 100644
--- a/superset/app.py
+++ b/superset/app.py
@@ -194,7 +194,8 @@ class SupersetApp(Flask):
 
             logger.info("Syncing configuration to database...")
 
-            # Register SQLA event listeners for tagging system
+            # Register the tagged_object cleanup listeners for the tagging system
+            # (deletion only; see superset.tags.core.register_sqla_event_listeners)
             if feature_flag_manager.is_feature_enabled("TAGGING_SYSTEM"):
                 from superset.tags.core import register_sqla_event_listeners
 
diff --git a/superset/cli/update.py b/superset/cli/update.py
index 6e928ec8eda..ed5dc13ef69 100755
--- a/superset/cli/update.py
+++ b/superset/cli/update.py
@@ -25,7 +25,6 @@ from apispec import APISpec
 from apispec.ext.marshmallow import MarshmallowPlugin
 from flask import current_app
 from flask.cli import with_appcontext
-from flask_appbuilder import Model
 from flask_appbuilder.api import BaseApi
 from flask_appbuilder.api.manager import resolver
 
@@ -53,22 +52,6 @@ def set_database_uri(database_name: str, uri: str, skip_create: bool) -> None:
     database_utils.get_or_create_db(database_name, uri, not skip_create)
 
 
-@click.command()
-@with_appcontext
-@transaction()
-def sync_tags() -> None:
-    """Rebuilds special tags (owner, type, favorited by)."""
-    # pylint: disable=no-member
-    metadata = Model.metadata
-
-    # pylint: disable=import-outside-toplevel
-    from superset.common.tags import add_favorites, add_owners, add_types
-
-    add_types(metadata)
-    add_owners(metadata)
-    add_favorites(metadata)
-
-
 @click.command()
 @with_appcontext
 def update_api_docs() -> None:
diff --git a/superset/common/tags.py b/superset/common/tags.py
deleted file mode 100644
index a29af03f075..00000000000
--- a/superset/common/tags.py
+++ /dev/null
@@ -1,496 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-import contextlib
-from typing import Any
-
-from sqlalchemy import MetaData
-from sqlalchemy.exc import IntegrityError
-from sqlalchemy.sql import and_, func, join, literal, select
-
-from superset.extensions import db
-from superset.tags.models import ObjectType, TagType
-
-
-def add_types_to_charts(
-    metadata: MetaData, tag: Any, tagged_object: Any, columns: list[str]
-) -> None:
-    slices = metadata.tables["slices"]
-
-    charts = (
-        select(
-            tag.c.id.label("tag_id"),
-            slices.c.id.label("object_id"),
-            literal(ObjectType.chart.name).label("object_type"),
-        )
-        .select_from(
-            join(
-                join(slices, tag, tag.c.name == "type:chart"),
-                tagged_object,
-                and_(
-                    tagged_object.c.tag_id == tag.c.id,
-                    tagged_object.c.object_id == slices.c.id,
-                    tagged_object.c.object_type == "chart",
-                ),
-                isouter=True,
-                full=False,
-            )
-        )
-        .where(tagged_object.c.tag_id.is_(None))
-    )
-    query = tagged_object.insert().from_select(columns, charts)
-    db.session.execute(query)
-
-
-def add_types_to_dashboards(
-    metadata: MetaData, tag: Any, tagged_object: Any, columns: list[str]
-) -> None:
-    dashboard_table = metadata.tables["dashboards"]
-
-    dashboards = (
-        select(
-            tag.c.id.label("tag_id"),
-            dashboard_table.c.id.label("object_id"),
-            literal(ObjectType.dashboard.name).label("object_type"),
-        )
-        .select_from(
-            join(
-                join(dashboard_table, tag, tag.c.name == "type:dashboard"),
-                tagged_object,
-                and_(
-                    tagged_object.c.tag_id == tag.c.id,
-                    tagged_object.c.object_id == dashboard_table.c.id,
-                    tagged_object.c.object_type == "dashboard",
-                ),
-                isouter=True,
-                full=False,
-            )
-        )
-        .where(tagged_object.c.tag_id.is_(None))
-    )
-    query = tagged_object.insert().from_select(columns, dashboards)
-    db.session.execute(query)
-
-
-def add_types_to_saved_queries(
-    metadata: MetaData, tag: Any, tagged_object: Any, columns: list[str]
-) -> None:
-    saved_query = metadata.tables["saved_query"]
-
-    saved_queries = (
-        select(
-            tag.c.id.label("tag_id"),
-            saved_query.c.id.label("object_id"),
-            literal(ObjectType.query.name).label("object_type"),
-        )
-        .select_from(
-            join(
-                join(saved_query, tag, tag.c.name == "type:query"),
-                tagged_object,
-                and_(
-                    tagged_object.c.tag_id == tag.c.id,
-                    tagged_object.c.object_id == saved_query.c.id,
-                    tagged_object.c.object_type == "query",
-                ),
-                isouter=True,
-                full=False,
-            )
-        )
-        .where(tagged_object.c.tag_id.is_(None))
-    )
-    query = tagged_object.insert().from_select(columns, saved_queries)
-    db.session.execute(query)
-
-
-def add_types_to_datasets(
-    metadata: MetaData, tag: Any, tagged_object: Any, columns: list[str]
-) -> None:
-    tables = metadata.tables["tables"]
-
-    datasets = (
-        select(
-            tag.c.id.label("tag_id"),
-            tables.c.id.label("object_id"),
-            literal(ObjectType.dataset.name).label("object_type"),
-        )
-        .select_from(
-            join(
-                join(tables, tag, tag.c.name == "type:dataset"),
-                tagged_object,
-                and_(
-                    tagged_object.c.tag_id == tag.c.id,
-                    tagged_object.c.object_id == tables.c.id,
-                    tagged_object.c.object_type == "dataset",
-                ),
-                isouter=True,
-                full=False,
-            )
-        )
-        .where(tagged_object.c.tag_id.is_(None))
-    )
-    query = tagged_object.insert().from_select(columns, datasets)
-    db.session.execute(query)
-
-
-def add_types(metadata: MetaData) -> None:
-    """
-    Tag every object according to its type:
-
-      INSERT INTO tagged_object (tag_id, object_id, object_type)
-      SELECT
-        tag.id AS tag_id,
-        slices.id AS object_id,
-        'chart' AS object_type
-      FROM slices
-      JOIN tag
-        ON tag.name = 'type:chart'
-      LEFT OUTER JOIN tagged_object
-        ON tagged_object.tag_id = tag.id
-        AND tagged_object.object_id = slices.id
-        AND tagged_object.object_type = 'chart'
-      WHERE tagged_object.tag_id IS NULL;
-
-      INSERT INTO tagged_object (tag_id, object_id, object_type)
-      SELECT
-        tag.id AS tag_id,
-        dashboards.id AS object_id,
-        'dashboard' AS object_type
-      FROM dashboards
-      JOIN tag
-      ON tag.name = 'type:dashboard'
-      LEFT OUTER JOIN tagged_object
-        ON tagged_object.tag_id = tag.id
-        AND tagged_object.object_id = dashboards.id
-        AND tagged_object.object_type = 'dashboard'
-      WHERE tagged_object.tag_id IS NULL;
-
-      INSERT INTO tagged_object (tag_id, object_id, object_type)
-      SELECT
-        tag.id AS tag_id,
-        saved_query.id AS object_id,
-        'query' AS object_type
-      FROM saved_query
-      JOIN tag
-      ON tag.name = 'type:query';
-      LEFT OUTER JOIN tagged_object
-        ON tagged_object.tag_id = tag.id
-        AND tagged_object.object_id = saved_query.id
-        AND tagged_object.object_type = 'query'
-      WHERE tagged_object.tag_id IS NULL;
-
-      INSERT INTO tagged_object (tag_id, object_id, object_type)
-      SELECT
-        tag.id AS tag_id,
-        tables.id AS object_id,
-        'dataset' AS object_type
-      FROM tables
-      JOIN tag
-        ON tag.name = 'type:dataset'
-      LEFT OUTER JOIN tagged_object
-        ON tagged_object.tag_id = tag.id
-        AND tagged_object.object_id = tables.id
-        AND tagged_object.object_type = 'dataset'
-      WHERE tagged_object.tag_id IS NULL;
-
-    """
-
-    tag = metadata.tables["tag"]
-    tagged_object = metadata.tables["tagged_object"]
-    columns = ["tag_id", "object_id", "object_type"]
-
-    # add a tag for each object type
-    insert = tag.insert()
-    for type_ in ObjectType.__members__:
-        with contextlib.suppress(IntegrityError):  # already exists
-            db.session.execute(insert, name=f"type:{type_}", type=TagType.type)
-
-    add_types_to_charts(metadata, tag, tagged_object, columns)
-    add_types_to_dashboards(metadata, tag, tagged_object, columns)
-    add_types_to_saved_queries(metadata, tag, tagged_object, columns)
-    add_types_to_datasets(metadata, tag, tagged_object, columns)
-
-
-def add_owners_to_charts(
-    metadata: MetaData, tag: Any, tagged_object: Any, columns: list[str]
-) -> None:
-    slices = metadata.tables["slices"]
-
-    charts = (
-        select(
-            tag.c.id.label("tag_id"),
-            slices.c.id.label("object_id"),
-            literal(ObjectType.chart.name).label("object_type"),
-        )
-        .select_from(
-            join(
-                join(
-                    slices,
-                    tag,
-                    tag.c.name == "editor:" + slices.c.created_by_fk,
-                ),
-                tagged_object,
-                and_(
-                    tagged_object.c.tag_id == tag.c.id,
-                    tagged_object.c.object_id == slices.c.id,
-                    tagged_object.c.object_type == "chart",
-                ),
-                isouter=True,
-                full=False,
-            )
-        )
-        .where(tagged_object.c.tag_id.is_(None))
-    )
-    query = tagged_object.insert().from_select(columns, charts)
-    db.session.execute(query)
-
-
-def add_owners_to_dashboards(
-    metadata: MetaData, tag: Any, tagged_object: Any, columns: list[str]
-) -> None:
-    dashboard_table = metadata.tables["dashboards"]
-
-    dashboards = (
-        select(
-            tag.c.id.label("tag_id"),
-            dashboard_table.c.id.label("object_id"),
-            literal(ObjectType.dashboard.name).label("object_type"),
-        )
-        .select_from(
-            join(
-                join(
-                    dashboard_table,
-                    tag,
-                    tag.c.name == "editor:" + dashboard_table.c.created_by_fk,
-                ),
-                tagged_object,
-                and_(
-                    tagged_object.c.tag_id == tag.c.id,
-                    tagged_object.c.object_id == dashboard_table.c.id,
-                    tagged_object.c.object_type == "dashboard",
-                ),
-                isouter=True,
-                full=False,
-            )
-        )
-        .where(tagged_object.c.tag_id.is_(None))
-    )
-    query = tagged_object.insert().from_select(columns, dashboards)
-    db.session.execute(query)
-
-
-def add_owners_to_saved_queries(
-    metadata: MetaData, tag: Any, tagged_object: Any, columns: list[str]
-) -> None:
-    saved_query = metadata.tables["saved_query"]
-
-    saved_queries = (
-        select(
-            tag.c.id.label("tag_id"),
-            saved_query.c.id.label("object_id"),
-            literal(ObjectType.query.name).label("object_type"),
-        )
-        .select_from(
-            join(
-                join(
-                    saved_query,
-                    tag,
-                    tag.c.name == "editor:" + saved_query.c.created_by_fk,
-                ),
-                tagged_object,
-                and_(
-                    tagged_object.c.tag_id == tag.c.id,
-                    tagged_object.c.object_id == saved_query.c.id,
-                    tagged_object.c.object_type == "query",
-                ),
-                isouter=True,
-                full=False,
-            )
-        )
-        .where(tagged_object.c.tag_id.is_(None))
-    )
-    query = tagged_object.insert().from_select(columns, saved_queries)
-    db.session.execute(query)
-
-
-def add_owners_to_datasets(
-    metadata: MetaData, tag: Any, tagged_object: Any, columns: list[str]
-) -> None:
-    tables = metadata.tables["tables"]
-
-    datasets = (
-        select(
-            tag.c.id.label("tag_id"),
-            tables.c.id.label("object_id"),
-            literal(ObjectType.dataset.name).label("object_type"),
-        )
-        .select_from(
-            join(
-                join(
-                    tables,
-                    tag,
-                    tag.c.name == "editor:" + tables.c.created_by_fk,
-                ),
-                tagged_object,
-                and_(
-                    tagged_object.c.tag_id == tag.c.id,
-                    tagged_object.c.object_id == tables.c.id,
-                    tagged_object.c.object_type == "dataset",
-                ),
-                isouter=True,
-                full=False,
-            )
-        )
-        .where(tagged_object.c.tag_id.is_(None))
-    )
-    query = tagged_object.insert().from_select(columns, datasets)
-    db.session.execute(query)
-
-
-def add_owners(metadata: MetaData) -> None:
-    """
-    Tag every object according to its editor:
-
-      INSERT INTO tagged_object (tag_id, object_id, object_type)
-      SELECT
-        tag.id AS tag_id,
-        slices.id AS object_id,
-        'chart' AS object_type
-      FROM slices
-      JOIN tag
-      ON tag.name = CONCAT('editor:', slices.created_by_fk)
-      LEFT OUTER JOIN tagged_object
-        ON tagged_object.tag_id = tag.id
-        AND tagged_object.object_id = slices.id
-        AND tagged_object.object_type = 'chart'
-      WHERE tagged_object.tag_id IS NULL;
-
-      SELECT
-        tag.id AS tag_id,
-        dashboards.id AS object_id,
-        'dashboard' AS object_type
-      FROM dashboards
-      JOIN tag
-      ON tag.name = CONCAT('editor:', dashboards.created_by_fk)
-      LEFT OUTER JOIN tagged_object
-        ON tagged_object.tag_id = tag.id
-        AND tagged_object.object_id = dashboards.id
-        AND tagged_object.object_type = 'dashboard'
-      WHERE tagged_object.tag_id IS NULL;
-
-      SELECT
-        tag.id AS tag_id,
-        saved_query.id AS object_id,
-        'query' AS object_type
-      FROM saved_query
-      JOIN tag
-      ON tag.name = CONCAT('editor:', saved_query.created_by_fk)
-      LEFT OUTER JOIN tagged_object
-        ON tagged_object.tag_id = tag.id
-        AND tagged_object.object_id = saved_query.id
-        AND tagged_object.object_type = 'query'
-      WHERE tagged_object.tag_id IS NULL;
-
-      SELECT
-        tag.id AS tag_id,
-        tables.id AS object_id,
-        'dataset' AS object_type
-      FROM tables
-      JOIN tag
-      ON tag.name = CONCAT('editor:', tables.created_by_fk)
-      LEFT OUTER JOIN tagged_object
-        ON tagged_object.tag_id = tag.id
-        AND tagged_object.object_id = tables.id
-        AND tagged_object.object_type = 'dataset'
-      WHERE tagged_object.tag_id IS NULL;
-
-    """
-
-    tag = metadata.tables["tag"]
-    tagged_object = metadata.tables["tagged_object"]
-    users = metadata.tables["ab_user"]
-    columns = ["tag_id", "object_id", "object_type"]
-
-    # create a custom tag for each user
-    ids = select(users.c.id)
-    insert = tag.insert()
-    for (id_,) in db.session.execute(ids):
-        with contextlib.suppress(IntegrityError):  # already exists
-            db.session.execute(insert, name=f"editor:{id_}", type=TagType.editor)
-    add_owners_to_charts(metadata, tag, tagged_object, columns)
-    add_owners_to_dashboards(metadata, tag, tagged_object, columns)
-    add_owners_to_saved_queries(metadata, tag, tagged_object, columns)
-    add_owners_to_datasets(metadata, tag, tagged_object, columns)
-
-
-def add_favorites(metadata: MetaData) -> None:
-    """
-    Tag every object that was favorited:
-
-      INSERT INTO tagged_object (tag_id, object_id, object_type)
-      SELECT
-        tag.id AS tag_id,
-        favstar.obj_id AS object_id,
-        LOWER(favstar.class_name) AS object_type
-      FROM favstar
-      JOIN tag
-      ON tag.name = CONCAT('favorited_by:', favstar.user_id)
-      LEFT OUTER JOIN tagged_object
-        ON tagged_object.tag_id = tag.id
-        AND tagged_object.object_id = favstar.obj_id
-        AND tagged_object.object_type = LOWER(favstar.class_name)
-      WHERE tagged_object.tag_id IS NULL;
-
-    """
-
-    tag = metadata.tables["tag"]
-    tagged_object = metadata.tables["tagged_object"]
-    users = metadata.tables["ab_user"]
-    favstar = metadata.tables["favstar"]
-    columns = ["tag_id", "object_id", "object_type"]
-
-    # create a custom tag for each user
-    ids = select(users.c.id)
-    insert = tag.insert()
-    for (id_,) in db.session.execute(ids):
-        with contextlib.suppress(IntegrityError):  # already exists
-            db.session.execute(insert, name=f"favorited_by:{id_}", type=TagType.type)
-    favstars = (
-        select(
-            tag.c.id.label("tag_id"),
-            favstar.c.obj_id.label("object_id"),
-            func.lower(favstar.c.class_name).label("object_type"),
-        )
-        .select_from(
-            join(
-                join(
-                    favstar,
-                    tag,
-                    tag.c.name == "favorited_by:" + favstar.c.user_id,
-                ),
-                tagged_object,
-                and_(
-                    tagged_object.c.tag_id == tag.c.id,
-                    tagged_object.c.object_id == favstar.c.obj_id,
-                    tagged_object.c.object_type == func.lower(favstar.c.class_name),
-                ),
-                isouter=True,
-                full=False,
-            )
-        )
-        .where(tagged_object.c.tag_id.is_(None))
-    )
-    query = tagged_object.insert().from_select(columns, favstars)
-    db.session.execute(query)
diff --git a/superset/config.py b/superset/config.py
index 0e8ee06b8eb..c72dbb46bd2 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -1782,11 +1782,6 @@ DASHBOARD_AUTO_REFRESH_INTERVALS = [
     [86400, "24 hours"],
 ]
 
-# Performance optimization: Return only custom tags in dashboard list API
-# When enabled, filters out implicit tags (owner, type, favorited_by) at SQL JOIN level
-# Reduces response payload and query time for dashboards with many editors
-DASHBOARD_LIST_CUSTOM_TAGS_ONLY: bool = False
-
 # This is used as a workaround for the alerts & reports scheduler task to get the time
 # celery beat triggered it, see https://github.com/celery/celery/issues/6974 for details
 CELERY_BEAT_SCHEDULER_EXPIRES = timedelta(weeks=1)
diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py
index eb754ed2753..330090d2a05 100644
--- a/superset/dashboards/api.py
+++ b/superset/dashboards/api.py
@@ -188,7 +188,6 @@ from superset.views.base_api import (
     statsd_metrics,
     validate_feature_flags,
 )
-from superset.views.custom_tags_api_mixin import CustomTagsOptimizationMixin
 from superset.views.error_handling import handle_api_exception
 from superset.views.filters import (
     BaseFilterRelatedUsers,
@@ -259,20 +258,12 @@ BASE_LIST_COLUMNS = [
     "uuid",
 ]
 
-# Full tags (current behavior - includes all tag types)
-FULL_TAG_LIST_COLUMNS = BASE_LIST_COLUMNS + [
+TAG_LIST_COLUMNS = BASE_LIST_COLUMNS + [
     "tags.id",
     "tags.name",
     "tags.type",
 ]
 
-# Custom tags only
-CUSTOM_TAG_LIST_COLUMNS = BASE_LIST_COLUMNS + [
-    "custom_tags.id",
-    "custom_tags.name",
-    "custom_tags.type",
-]
-
 # Fields dropped from a dashboard member dataset when the caller cannot access
 # that datasource on its own: everything describing the dataset's schema,
 # connection, and query construction. The identifying fields the dashboard
@@ -300,9 +291,7 @@ DASHBOARD_DATASET_INACCESSIBLE_FIELDS = (
 
 
 # pylint: disable=too-many-public-methods
-class DashboardRestApi(
-    SoftDeleteApiMixin, CustomTagsOptimizationMixin, BaseSupersetModelRestApi
-):
+class DashboardRestApi(SoftDeleteApiMixin, BaseSupersetModelRestApi):
     datamodel = SQLAInterface(Dashboard)
 
     include_route_methods = RouteMethod.REST_MODEL_VIEW_CRUD_SET | {
@@ -357,17 +346,7 @@ class DashboardRestApi(
         "purge": "write",
     }
 
-    # Default list_columns (used if config not set)
-    list_columns = FULL_TAG_LIST_COLUMNS
-
-    def __init__(self) -> None:
-        # Configure custom tags optimization (mixin handles the logic)
-        self._setup_custom_tags_optimization(
-            config_key="DASHBOARD_LIST_CUSTOM_TAGS_ONLY",
-            full_columns=FULL_TAG_LIST_COLUMNS,
-            custom_columns=CUSTOM_TAG_LIST_COLUMNS,
-        )
-        super().__init__()
+    list_columns = TAG_LIST_COLUMNS
 
     @expose("/", methods=("GET",))
     @protect()
diff --git a/superset/dashboards/schemas.py b/superset/dashboards/schemas.py
index 41ed68d79e5..51dc9370c80 100644
--- a/superset/dashboards/schemas.py
+++ b/superset/dashboards/schemas.py
@@ -292,7 +292,6 @@ class DashboardGetResponseSchema(Schema):
     editors = fields.List(fields.Nested(SubjectResponseSchema))
     viewers = fields.List(fields.Nested(SubjectResponseSchema))
     tags = fields.Nested(TagSchema, many=True)
-    custom_tags = fields.Nested(TagSchema, many=True)
     changed_on_humanized = fields.String(data_key="changed_on_delta_humanized")
     created_on_humanized = fields.String(data_key="created_on_delta_humanized")
     is_managed_externally = fields.Boolean(allow_none=True, dump_default=False)
@@ -302,12 +301,6 @@ class DashboardGetResponseSchema(Schema):
     # pylint: disable=unused-argument
     @post_dump()
     def post_dump(self, serialized: dict[str, Any], **kwargs: Any) -> dict[str, Any]:
-        # Handle custom_tags → tags renaming when flag is enabled
-        # When DASHBOARD_LIST_CUSTOM_TAGS_ONLY=True, FAB populates custom_tags
-        # Rename it to tags for frontend compatibility
-        if "custom_tags" in serialized:
-            serialized["tags"] = serialized.pop("custom_tags")
-
         if security_manager.is_guest_user():
             del serialized["changed_by_name"]
             del serialized["changed_by"]
diff --git a/superset/mcp_service/tag/tool/get_tag_info.py b/superset/mcp_service/tag/tool/get_tag_info.py
index ab4cde6a929..7892823a76f 100644
--- a/superset/mcp_service/tag/tool/get_tag_info.py
+++ b/superset/mcp_service/tag/tool/get_tag_info.py
@@ -54,8 +54,10 @@ async def get_tag_info(request: GetTagInfoRequest, ctx: Context) -> TagInfo | Ta
 
     Returns tag details including name, type, and description.
 
-    Tag types: custom (user-created), type (implicit by object type),
-    editor (implicit by editorship), favorited_by (implicit by favorites).
+    Tag types: custom (user-created), plus legacy implicit types no longer
+    generated for new objects -- type (by object type), editor (by
+    editorship), favorited_by (by favorites) -- kept for tags created before
+    Superset stopped auto-generating them.
 
     To find a tag ID, use the list_tags tool first.
 
diff --git a/superset/mcp_service/tag/tool/list_tags.py b/superset/mcp_service/tag/tool/list_tags.py
index f248dad6c3e..29e5b6cd865 100644
--- a/superset/mcp_service/tag/tool/list_tags.py
+++ b/superset/mcp_service/tag/tool/list_tags.py
@@ -73,8 +73,10 @@ async def list_tags(
 
     Returns tag metadata including name, type, and description.
 
-    Tag types: custom (user-created), type (implicit by object type),
-    editor (implicit by editorship), favorited_by (implicit by favorites).
+    Tag types: custom (user-created), plus legacy implicit types no longer
+    generated for new objects -- type (by object type), editor (by
+    editorship), favorited_by (by favorites) -- kept for tags created before
+    Superset stopped auto-generating them.
 
     Sortable columns for order_column: id, name, changed_on, created_on
     """
diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py
index dbddea06aea..569bb9a6054 100644
--- a/superset/models/dashboard.py
+++ b/superset/models/dashboard.py
@@ -191,16 +191,6 @@ class Dashboard(CoreDashboard, SoftDeleteMixin, AuditMixinNullable, ImportExport
         secondaryjoin="TaggedObject.tag_id == Tag.id",
         viewonly=True,  # cascading deletion already handled by superset.tags.models.ObjectUpdater.after_delete  # noqa: E501
     )
-    custom_tags = relationship(
-        "Tag",
-        overlaps="objects,tag,tags,custom_tags",
-        secondary="tagged_object",
-        primaryjoin="and_(Dashboard.id == TaggedObject.object_id, "
-        "TaggedObject.object_type == 'dashboard')",
-        secondaryjoin="and_(TaggedObject.tag_id == Tag.id, "
-        "cast(Tag.type, String) == 'custom')",  # Filtering at JOIN level
-        viewonly=True,
-    )
     theme = relationship("Theme", foreign_keys=[theme_id])
     published = Column(Boolean, default=False)
     is_managed_externally = Column(Boolean, nullable=False, default=False)
diff --git a/superset/tags/core.py b/superset/tags/core.py
index 00647cea01c..130212c04fc 100644
--- a/superset/tags/core.py
+++ b/superset/tags/core.py
@@ -54,80 +54,38 @@ def _tag_delete_listener_declarations() -> tuple[DeleteListenerDeclaration, ...]
 
 
 def register_sqla_event_listeners() -> None:
+    """Register cleanup of ``tagged_object`` rows on object deletion.
+
+    Only deletion is handled here: Superset no longer auto-generates
+    ``type:``/``editor:``/``favorited_by:`` tags (see ``TagType``'s docstring),
+    so there's nothing left to do on insert/update. Deletion cleanup stays,
+    since it applies to every tag on the object, custom tags included, and
+    ``tagged_object.object_id`` has no foreign key to cascade on its own.
+    """
     import sqlalchemy as sqla
 
-    from superset.connectors.sqla.models import SqlaTable
-    from superset.models.core import FavStar
-    from superset.models.dashboard import Dashboard
-    from superset.models.slice import Slice
     from superset.models.sql_lab import SavedQuery
-    from superset.tags.models import (
-        ChartUpdater,
-        DashboardUpdater,
-        DatasetUpdater,
-        FavStarUpdater,
-        QueryUpdater,
-    )
-
-    declarations: tuple[DeleteListenerDeclaration, ...] = (
-        _tag_delete_listener_declarations()
-    )
-
-    sqla.event.listen(SqlaTable, "after_insert", DatasetUpdater.after_insert)
-    sqla.event.listen(SqlaTable, "after_update", DatasetUpdater.after_update)
-    register_delete_listener(declarations[0])
-
-    sqla.event.listen(Slice, "after_insert", ChartUpdater.after_insert)
-    sqla.event.listen(Slice, "after_update", ChartUpdater.after_update)
-    register_delete_listener(declarations[1])
+    from superset.tags.models import QueryUpdater
 
-    sqla.event.listen(Dashboard, "after_insert", DashboardUpdater.after_insert)
-    sqla.event.listen(Dashboard, "after_update", DashboardUpdater.after_update)
-    register_delete_listener(declarations[2])
+    declarations = _tag_delete_listener_declarations()
 
-    sqla.event.listen(FavStar, "after_insert", FavStarUpdater.after_insert)
-    sqla.event.listen(FavStar, "after_delete", FavStarUpdater.after_delete)
+    register_delete_listener(declarations[0])  # dataset
+    register_delete_listener(declarations[1])  # chart
+    register_delete_listener(declarations[2])  # dashboard
 
-    sqla.event.listen(SavedQuery, "after_insert", QueryUpdater.after_insert)
-    sqla.event.listen(SavedQuery, "after_update", QueryUpdater.after_update)
     sqla.event.listen(SavedQuery, "after_delete", QueryUpdater.after_delete)
 
 
 def clear_sqla_event_listeners() -> None:
     import sqlalchemy as sqla
 
-    from superset.connectors.sqla.models import SqlaTable
-    from superset.models.core import FavStar
-    from superset.models.dashboard import Dashboard
-    from superset.models.slice import Slice
     from superset.models.sql_lab import SavedQuery
-    from superset.tags.models import (
-        ChartUpdater,
-        DashboardUpdater,
-        DatasetUpdater,
-        FavStarUpdater,
-        QueryUpdater,
-    )
-
-    declarations: tuple[DeleteListenerDeclaration, ...] = (
-        _tag_delete_listener_declarations()
-    )
-
-    sqla.event.remove(SqlaTable, "after_insert", DatasetUpdater.after_insert)
-    sqla.event.remove(SqlaTable, "after_update", DatasetUpdater.after_update)
-    remove_delete_listener(declarations[0])
-
-    sqla.event.remove(Slice, "after_insert", ChartUpdater.after_insert)
-    sqla.event.remove(Slice, "after_update", ChartUpdater.after_update)
-    remove_delete_listener(declarations[1])
+    from superset.tags.models import QueryUpdater
 
-    sqla.event.remove(Dashboard, "after_insert", DashboardUpdater.after_insert)
-    sqla.event.remove(Dashboard, "after_update", DashboardUpdater.after_update)
-    remove_delete_listener(declarations[2])
+    declarations = _tag_delete_listener_declarations()
 
-    sqla.event.remove(FavStar, "after_insert", FavStarUpdater.after_insert)
-    sqla.event.remove(FavStar, "after_delete", FavStarUpdater.after_delete)
+    remove_delete_listener(declarations[0])  # dataset
+    remove_delete_listener(declarations[1])  # chart
+    remove_delete_listener(declarations[2])  # dashboard
 
-    sqla.event.remove(SavedQuery, "after_insert", QueryUpdater.after_insert)
-    sqla.event.remove(SavedQuery, "after_update", QueryUpdater.after_update)
     sqla.event.remove(SavedQuery, "after_delete", QueryUpdater.after_delete)
diff --git a/superset/tags/models.py b/superset/tags/models.py
index c60e660b23d..4f99049ae84 100644
--- a/superset/tags/models.py
+++ b/superset/tags/models.py
@@ -21,17 +21,7 @@ import enum
 from typing import TYPE_CHECKING
 
 from flask_appbuilder import Model
-from sqlalchemy import (
-    Column,
-    Enum,
-    exists,
-    ForeignKey,
-    Integer,
-    orm,
-    String,
-    Table,
-    Text,
-)
+from sqlalchemy import Column, Enum, ForeignKey, Integer, orm, String, Table, Text
 from sqlalchemy.engine.base import Connection
 from sqlalchemy.orm import relationship, sessionmaker
 from sqlalchemy.orm.mapper import Mapper
@@ -40,11 +30,9 @@ from superset_core.common.models import Tag as CoreTag
 
 from superset import security_manager
 from superset.models.helpers import AuditMixinNullable
-from superset.subjects.types import SubjectType
 
 if TYPE_CHECKING:
     from superset.connectors.sqla.models import SqlaTable
-    from superset.models.core import FavStar
     from superset.models.dashboard import Dashboard
     from superset.models.slice import Slice
     from superset.models.sql_lab import Query
@@ -63,16 +51,21 @@ class TagType(enum.Enum):
     """
     Types for tags.
 
-    Objects (queries, charts, dashboards, and datasets) will have with implicit tags based
-    on metadata: types, editors and who favorited them. This way, user "alice"
-    can find all their objects by querying for the tag `editor:alice`.
-    """  # noqa: E501
+    ``type``, ``editor``, and ``favorited_by`` are no longer generated: Superset
+    used to auto-tag every query, chart, dashboard, and dataset with implicit
+    tags based on metadata (object type, editors, and who favorited them), but
+    nothing ever surfaced them to the user, so the generation was removed. The
+    values are kept, and rows of these types are still recognized (e.g. exempt
+    from bulk deletion) and filterable via the API, so upgraded deployments that
+    already have such tags, or MCP tooling that queries by tag type, keep
+    working.
+    """
 
     # pylint: disable=invalid-name
     # explicit tags, added manually by the owner
     custom = 1
 
-    # implicit tags, generated automatically
+    # legacy implicit tag types; no longer generated (see docstring above)
     type = 2
     editor = 3
     favorited_by = 4
@@ -155,145 +148,26 @@ def get_tag(
     return tag
 
 
-def get_object_type(class_name: str) -> ObjectType:
-    mapping = {
-        "slice": ObjectType.chart,
-        "dashboard": ObjectType.dashboard,
-        "query": ObjectType.query,
-        "dataset": ObjectType.dataset,
-    }
-    try:
-        return mapping[class_name.lower()]
-    except KeyError as ex:
-        raise Exception(  # pylint: disable=broad-exception-raised
-            f"No mapping found for {class_name}"
-        ) from ex
-
-
 class ObjectUpdater:
-    object_type: str = "default"
-
-    @classmethod
-    def get_editor_user_ids(
-        cls, target: Dashboard | FavStar | Slice | Query | SqlaTable
-    ) -> list[int]:
-        raise NotImplementedError("Subclass should implement `get_editor_user_ids`")
-
-    @classmethod
-    def get_editor_tag_ids(
-        cls,
-        session: orm.Session,  # pylint: disable=disallowed-name
-        target: Dashboard | FavStar | Slice | Query | SqlaTable,
-    ) -> set[int]:
-        tag_ids = set()
-        for user_id in cls.get_editor_user_ids(target):
-            name = f"editor:{user_id}"
-            tag = get_tag(name, session, TagType.editor)
-            tag_ids.add(tag.id)
-        return tag_ids
-
-    @classmethod
-    def _add_editors(
-        cls,
-        session: orm.Session,  # pylint: disable=disallowed-name
-        target: Dashboard | FavStar | Slice | Query | SqlaTable,
-    ) -> None:
-        for user_id in cls.get_editor_user_ids(target):
-            name: str = f"editor:{user_id}"
-            tag = get_tag(name, session, TagType.editor)
-            cls.add_tag_object_if_not_tagged(
-                session, tag_id=tag.id, object_id=target.id, object_type=cls.object_type
-            )
+    """Cleans up ``tagged_object`` rows when a tagged object is deleted.
 
-    @classmethod
-    def add_tag_object_if_not_tagged(
-        cls,
-        session: orm.Session,  # pylint: disable=disallowed-name
-        tag_id: int,
-        object_id: int,
-        object_type: str,
-    ) -> None:
-        # Check if the object is already tagged
-        exists_query = exists().where(
-            TaggedObject.tag_id == tag_id,
-            TaggedObject.object_id == object_id,
-            TaggedObject.object_type == object_type,
-        )
-        already_tagged = session.query(exists_query).scalar()
-
-        # Add TaggedObject to the session if it isn't already tagged
-        if not already_tagged:
-            tagged_object = TaggedObject(
-                tag_id=tag_id, object_id=object_id, object_type=object_type
-            )
-            session.add(tagged_object)
-
-    @classmethod
-    def after_insert(
-        cls,
-        _mapper: Mapper,
-        connection: Connection,
-        target: Dashboard | FavStar | Slice | Query | SqlaTable,
-    ) -> None:
-        with Session(bind=connection) as session:  # pylint: disable=disallowed-name
-            # add `editor:` tags
-            cls._add_editors(session, target)
-
-            # add `type:` tags
-            tag = get_tag(f"type:{cls.object_type}", session, TagType.type)
-            cls.add_tag_object_if_not_tagged(
-                session, tag_id=tag.id, object_id=target.id, object_type=cls.object_type
-            )
-            session.commit()
+    ``TaggedObject.object_id`` is a polymorphic reference with no foreign key
+    (see the comment on that column), so nothing at the database level removes
+    a tag association when the dashboard/chart/query/dataset it points at is
+    deleted. This listener does that cleanup for every tag on the object
+    (custom tags included), independent of how the tag was created.
+    """
 
-    @classmethod
-    def after_update(
-        cls,
-        _mapper: Mapper,
-        connection: Connection,
-        target: Dashboard | FavStar | Slice | Query | SqlaTable,
-    ) -> None:
-        with Session(bind=connection) as session:  # pylint: disable=disallowed-name
-            # Fetch current editor tags
-            existing_tags = (
-                session.query(TaggedObject)
-                .join(Tag)
-                .filter(
-                    TaggedObject.object_type == cls.object_type,
-                    TaggedObject.object_id == target.id,
-                    Tag.type == TagType.editor,
-                )
-                .all()
-            )
-            existing_editor_tag_ids = {tag.tag_id for tag in existing_tags}
-
-            # Determine new editor IDs
-            new_editor_tag_ids = cls.get_editor_tag_ids(session, target)
-
-            # Add missing tags
-            for editor_tag_id in new_editor_tag_ids - existing_editor_tag_ids:
-                tagged_object = TaggedObject(
-                    tag_id=editor_tag_id,
-                    object_id=target.id,
-                    object_type=cls.object_type,
-                )
-                session.add(tagged_object)
-
-            # Remove unnecessary tags
-            for tag in existing_tags:
-                if tag.tag_id not in new_editor_tag_ids:
-                    session.delete(tag)
-            session.commit()
+    object_type: str = "default"
 
     @classmethod
     def after_delete(
         cls,
         _mapper: Mapper,
         connection: Connection,
-        target: Dashboard | FavStar | Slice | Query | SqlaTable,
+        target: Dashboard | Slice | Query | SqlaTable,
     ) -> None:
         with Session(bind=connection) as session:  # pylint: disable=disallowed-name
-            # delete row from `tagged_objects`
             session.query(TaggedObject).filter(
                 TaggedObject.object_type == cls.object_type,
                 TaggedObject.object_id == target.id,
@@ -305,75 +179,14 @@ class ObjectUpdater:
 class ChartUpdater(ObjectUpdater):
     object_type = "chart"
 
-    @classmethod
-    def get_editor_user_ids(cls, target: Slice) -> list[int]:
-        return [
-            s.user.id for s in target.editors if s.type == SubjectType.USER and s.user
-        ]
-
 
 class DashboardUpdater(ObjectUpdater):
     object_type = "dashboard"
 
-    @classmethod
-    def get_editor_user_ids(cls, target: Dashboard) -> list[int]:
-        return [
-            s.user.id for s in target.editors if s.type == SubjectType.USER and s.user
-        ]
-
 
 class QueryUpdater(ObjectUpdater):
     object_type = "query"
 
-    @classmethod
-    def get_editor_user_ids(cls, target: Query) -> list[int]:
-        return [target.user_id]
-
 
 class DatasetUpdater(ObjectUpdater):
     object_type = "dataset"
-
-    @classmethod
-    def get_editor_user_ids(cls, target: SqlaTable) -> list[int]:
-        return [
-            s.user.id for s in target.editors if s.type == SubjectType.USER and s.user
-        ]
-
-
-class FavStarUpdater:
-    @classmethod
-    def after_insert(
-        cls, _mapper: Mapper, connection: Connection, target: FavStar
-    ) -> None:
-        with Session(bind=connection) as session:  # pylint: disable=disallowed-name
-            name = f"favorited_by:{target.user_id}"
-            tag = get_tag(name, session, TagType.favorited_by)
-            tagged_object = TaggedObject(
-                tag_id=tag.id,
-                object_id=target.obj_id,
-                object_type=get_object_type(target.class_name),
-            )
-            session.add(tagged_object)
-            session.commit()
-
-    @classmethod
-    def after_delete(
-        cls, _mapper: Mapper, connection: Connection, target: FavStar
-    ) -> None:
-        with Session(bind=connection) as session:  # pylint: disable=disallowed-name
-            name = f"favorited_by:{target.user_id}"
-            query = (
-                session.query(TaggedObject.id)
-                .join(Tag)
-                .filter(
-                    TaggedObject.object_id == target.obj_id,
-                    Tag.type == TagType.favorited_by,
-                    Tag.name == name,
-                )
-            )
-            ids = [row[0] for row in query]
-            session.query(TaggedObject).filter(TaggedObject.id.in_(ids)).delete(
-                synchronize_session=False
-            )
-
-            session.commit()
diff --git a/superset/views/custom_tags_api_mixin.py b/superset/views/custom_tags_api_mixin.py
deleted file mode 100644
index c04d5d7790a..00000000000
--- a/superset/views/custom_tags_api_mixin.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-"""Mixin for APIs that need custom_tags optimization with frontend compatibility."""
-
-from typing import Any
-
-from flask import current_app, request, Response
-from werkzeug.datastructures import ImmutableMultiDict
-
-
-class CustomTagsOptimizationMixin:
-    """Reusable mixin for APIs that optimize tag queries via custom_tags relationship.
-
-    When enabled via config, this mixin:
-    1. Configures list_columns to use custom_tags (filtered relationship)
-    2. Exposes custom_tags as tags in the response schema
-    3. Rewrites frontend requests from 'tags.*' to 'custom_tags.*'
-    4. Transforms responses to rename 'custom_tags' back to 'tags'
-
-    This provides SQL query optimization (97% reduction) while maintaining
-    frontend compatibility.
-
-    Usage:
-        class MyRestApi(CustomTagsOptimizationMixin, BaseSupersetModelRestApi):
-            def __init__(self):
-                self._setup_custom_tags_optimization(
-                    config_key="MY_API_CUSTOM_TAGS_ONLY",
-                    full_columns=FULL_TAG_COLUMNS,
-                    custom_columns=CUSTOM_TAG_COLUMNS,
-                )
-                super().__init__()
-    """
-
-    _custom_tags_only: bool
-
-    def _setup_custom_tags_optimization(
-        self,
-        config_key: str,
-        full_columns: list[str],
-        custom_columns: list[str],
-    ) -> None:
-        """Configure custom tags optimization based on config.
-
-        Args:
-            config_key: Config key to check (e.g., "DASHBOARD_LIST_CUSTOM_TAGS_ONLY")
-            full_columns: list_columns when optimization disabled (includes all tags)
-            custom_columns: list_columns when optimization enabled (only custom_tags)
-        """
-        self._custom_tags_only = current_app.config.get(config_key, False)
-        self.list_columns = custom_columns if self._custom_tags_only else full_columns
-
-    def _init_model_schemas(self) -> None:
-        """Keep the optimized relationship's public schema name stable."""
-        super()._init_model_schemas()  # type: ignore[misc]
-
-        list_model_schema = getattr(self, "list_model_schema", None)
-        if (
-            self._custom_tags_only
-            and list_model_schema
-            and "custom_tags" in list_model_schema.fields
-        ):
-            list_model_schema.fields["custom_tags"].data_key = "tags"
-
-    def get_list(self, **kwargs: Any) -> Response:
-        """Override to rewrite request parameters for custom_tags optimization.
-
-        When config is enabled, rewrites 'tags.*' → 'custom_tags.*' in request
-        so FAB can find the columns in list_columns.
-        """
-        if self._custom_tags_only:
-            # Parse and rewrite query parameter
-            query_str = request.args.get("q", "")
-            if query_str and "tags." in query_str:
-                # Replace 'tags.' with 'custom_tags.' in select_columns
-                modified_query = query_str.replace("tags.id", "custom_tags.id")
-                modified_query = modified_query.replace("tags.name", "custom_tags.name")
-                modified_query = modified_query.replace("tags.type", "custom_tags.type")
-
-                # Temporarily patch request.args
-                modified_args = request.args.copy()
-                modified_args["q"] = modified_query
-                original_args = request.args
-                request.args = ImmutableMultiDict(modified_args)
-
-                try:
-                    return super().get_list(**kwargs)  # type: ignore
-                finally:
-                    # Restore original args
-                    request.args = original_args
-
-        return super().get_list(**kwargs)  # type: ignore
-
-    def pre_get_list(self, data: dict[str, Any]) -> None:
-        """Rename custom_tags → tags in response for frontend compatibility.
-
-        Called by FAB before sending the list response. This ensures the frontend
-        always receives 'tags' regardless of backend optimization config.
-        """
-        if self._custom_tags_only and "result" in data:
-            for item in data["result"]:
-                if "custom_tags" in item:
-                    item["tags"] = item.pop("custom_tags")
-
-        super().pre_get_list(data)  # type: ignore
diff --git a/tests/integration_tests/dashboards/api_tests.py b/tests/integration_tests/dashboards/api_tests.py
index 7db75664e23..ea8e205798b 100644
--- a/tests/integration_tests/dashboards/api_tests.py
+++ b/tests/integration_tests/dashboards/api_tests.py
@@ -4623,107 +4623,3 @@ class TestDashboardApi(ApiEditorsTestCaseMixin, InsertChartMixin, SupersetTestCa
             # Cleanup
             db.session.delete(dashboard)
             db.session.commit()
-
-
-class TestDashboardCustomTagsFiltering(SupersetTestCase):
-    """Test dashboard list API tags field behavior.
-
-    Note: DASHBOARD_LIST_CUSTOM_TAGS_ONLY config is checked at app startup in
-    DashboardRestApi.__init__(), so these tests verify the current runtime behavior.
-    """
-
-    def setUp(self) -> None:
-        """Set up test fixtures."""
-        self.login(username="admin")
-
-    @pytest.mark.usefixtures("with_tagging_system_feature")
-    def test_dashboard_custom_tags_relationship_filters_correctly(self):
-        """Verify custom_tags filtering at model and API level.
-
-        With DASHBOARD_LIST_CUSTOM_TAGS_ONLY=True in superset_test_config.py:
-        1. dashboard.tags returns ALL tags (custom + editor + type)
-        2. dashboard.custom_tags returns ONLY custom tags
-        3. API response returns ONLY custom tags in the "tags" property
-        """
-        dashboard = Dashboard(
-            dashboard_title="test-custom-only",
-            slug="test-slug-custom",
-            editors=subjects_from_users([self.get_user("admin")]),
-        )
-        db.session.add(dashboard)
-        db.session.flush()
-
-        custom_tag = Tag(name="critical", type=TagType.custom)
-        db.session.add(custom_tag)
-        db.session.flush()
-
-        tagged_obj = TaggedObject(
-            tag_id=custom_tag.id,
-            object_id=dashboard.id,
-            object_type="dashboard",
-        )
-        db.session.add(tagged_obj)
-        db.session.commit()
-
-        try:
-            # 1. MODEL: dashboard.tags returns ALL tags
-            all_tags = dashboard.tags
-            all_tag_names = [t.name for t in all_tags]
-            assert "critical" in all_tag_names, "Should include custom tag"
-            assert any(t.name.startswith("editor:") for t in all_tags), (
-                "Should include editor tags"
-            )
-            assert any(t.name.startswith("type:") for t in all_tags), (
-                "Should include type tags"
-            )
-
-            # 2. MODEL: dashboard.custom_tags returns ONLY custom tags
-            custom_only = dashboard.custom_tags
-            custom_tag_names = [t.name for t in custom_only]
-            assert "critical" in custom_tag_names, "Should include custom tag"
-            assert not any(t.name.startswith("editor:") for t in custom_only), (
-                f"custom_tags should NOT include editor tags, got: {custom_tag_names}"
-            )
-            assert not any(t.name.startswith("type:") for t in custom_only), (
-                f"custom_tags should NOT include type tags, got: {custom_tag_names}"
-            )
-            assert len(custom_only) < len(all_tags), "Should filter out implicit tags"
-
-            # Verify all tags in custom_tags have type=custom
-            for tag in custom_only:
-                assert tag.type == TagType.custom, (
-                    f"Tag {tag.name} has type {tag.type}, expected TagType.custom"
-                )
-
-            # 3. API: With config=True, API returns ONLY custom tags
-            rv = self.client.get("api/v1/dashboard/")
-            data = json.loads(rv.data.decode("utf-8"))
-
-            assert rv.status_code == 200
-            test_dash = next(
-                (d for d in data["result"] if d["id"] == dashboard.id), None
-            )
-            assert test_dash is not None
-            # API returns "tags" (get_list override renames custom_tags→tags)
-            assert "tags" in test_dash, (
-                f"Response should have tags, got: {test_dash.keys()}"
-            )
-
-            # API should return ONLY custom tags
-            api_tag_names = [t["name"] for t in test_dash["tags"]]
-            assert "critical" in api_tag_names, "API should include custom tag"
-            assert not any(
-                t["name"].startswith("editor:") for t in test_dash["tags"]
-            ), f"API should NOT include editor tags, got: {api_tag_names}"
-            assert not any(t["name"].startswith("type:") for t in test_dash["tags"]), (
-                f"API should NOT include type tags, got: {api_tag_names}"
-            )
-            assert len(test_dash["tags"]) == 1, (
-                f"API should return only 1 custom tag, "
-                f"got {len(test_dash['tags'])}: {api_tag_names}"
-            )
-        finally:
-            db.session.delete(dashboard)
-            db.session.commit()
-            db.session.delete(custom_tag)
-            db.session.commit()
diff --git a/tests/integration_tests/superset_test_config.py b/tests/integration_tests/superset_test_config.py
index eb6567a7478..cb88e3cc152 100644
--- a/tests/integration_tests/superset_test_config.py
+++ b/tests/integration_tests/superset_test_config.py
@@ -183,7 +183,4 @@ CUSTOM_TEMPLATE_PROCESSORS = {
 
 PRESERVE_CONTEXT_ON_EXCEPTION = False
 
-# Dashboard API: Return only custom tags (performance optimization)
-DASHBOARD_LIST_CUSTOM_TAGS_ONLY = True
-
 print("Loaded TEST config for INTEGRATION tests")
diff --git a/tests/integration_tests/tagging_tests.py b/tests/integration_tests/tagging_tests.py
deleted file mode 100644
index 27d87b5f5a3..00000000000
--- a/tests/integration_tests/tagging_tests.py
+++ /dev/null
@@ -1,294 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-
-import pytest
-
-from superset.connectors.sqla.models import SqlaTable
-from superset.extensions import db
-from superset.models.core import FavStar
-from superset.models.dashboard import Dashboard
-from superset.models.slice import Slice
-from superset.models.sql_lab import SavedQuery
-from superset.tags.models import TaggedObject
-from superset.utils.core import DatasourceType
-from superset.utils.database import get_main_database
-from tests.integration_tests.base_tests import SupersetTestCase
-from tests.integration_tests.conftest import with_feature_flags
-from tests.integration_tests.fixtures.tags import (
-    with_tagging_system_feature,  # noqa: F401
-)
-
-
-class TestTagging(SupersetTestCase):
-    def query_tagged_object_table(self):
-        query = db.session.query(TaggedObject).all()
-        return query
-
-    def clear_tagged_object_table(self):
-        db.session.query(TaggedObject).delete()
-        db.session.commit()
-
-    @pytest.mark.usefixtures("with_tagging_system_feature")
-    def test_dataset_tagging(self):
-        """
-        Test to make sure that when a new dataset is created,
-        a corresponding tag in the tagged_objects table
-        is created
-        """
-
-        # Remove all existing rows in the tagged_object table
-        self.clear_tagged_object_table()
-
-        # Test to make sure nothing is in the tagged_object table
-        assert [] == self.query_tagged_object_table()
-
-        # Create a dataset and add it to the db
-        test_dataset = SqlaTable(
-            table_name="foo",
-            schema=None,
-            editors=[],
-            database=get_main_database(),
-            sql=None,
-            extra='{"certification": 1}',
-        )
-        db.session.add(test_dataset)
-        db.session.commit()
-
-        # Test to make sure that a dataset tag was added to the tagged_object table
-        tags = self.query_tagged_object_table()
-        assert 1 == len(tags)
-        assert "ObjectType.dataset" == str(tags[0].object_type)
-        assert test_dataset.id == tags[0].object_id
-
-        # Cleanup the db
-        db.session.delete(test_dataset)
-        db.session.commit()
-
-        # Test to make sure the tag is deleted when the associated object is deleted
-        assert [] == self.query_tagged_object_table()
-
-    @pytest.mark.usefixtures("with_tagging_system_feature")
-    def test_chart_tagging(self):
-        """
-        Test to make sure that when a new chart is created,
-        a corresponding tag in the tagged_objects table
-        is created
-        """
-
-        # Remove all existing rows in the tagged_object table
-        self.clear_tagged_object_table()
-
-        # Test to make sure nothing is in the tagged_object table
-        assert [] == self.query_tagged_object_table()
-
-        # Create a chart and add it to the db
-        test_chart = Slice(
-            slice_name="test_chart",
-            datasource_type=DatasourceType.TABLE,
-            viz_type="bubble",
-            datasource_id=1,
-        )
-        db.session.add(test_chart)
-        db.session.commit()
-
-        # Test to make sure that a chart tag was added to the tagged_object table
-        tags = self.query_tagged_object_table()
-        assert 1 == len(tags)
-        assert "ObjectType.chart" == str(tags[0].object_type)
-        assert test_chart.id == tags[0].object_id
-
-        # Cleanup the db
-        db.session.delete(test_chart)
-        db.session.commit()
-
-        # Test to make sure the tag is deleted when the associated object is deleted
-        assert [] == self.query_tagged_object_table()
-
-    @pytest.mark.usefixtures("with_tagging_system_feature")
-    def test_dashboard_tagging(self):
-        """
-        Test to make sure that when a new dashboard is created,
-        a corresponding tag in the tagged_objects table
-        is created
-        """
-
-        # Remove all existing rows in the tagged_object table
-        self.clear_tagged_object_table()
-
-        # Test to make sure nothing is in the tagged_object table
-        assert [] == self.query_tagged_object_table()
-
-        # Create a dashboard and add it to the db
-        test_dashboard = Dashboard()
-        test_dashboard.dashboard_title = "test_dashboard"
-        test_dashboard.slug = "test_slug"
-        test_dashboard.published = True
-
-        db.session.add(test_dashboard)
-        db.session.commit()
-
-        # Test to make sure that a dashboard tag was added to the tagged_object table
-        tags = self.query_tagged_object_table()
-        assert 1 == len(tags)
-        assert "ObjectType.dashboard" == str(tags[0].object_type)
-        assert test_dashboard.id == tags[0].object_id
-
-        # Cleanup the db
-        db.session.delete(test_dashboard)
-        db.session.commit()
-
-        # Test to make sure the tag is deleted when the associated object is deleted
-        assert [] == self.query_tagged_object_table()
-
-    @pytest.mark.usefixtures("with_tagging_system_feature")
-    def test_saved_query_tagging(self):
-        """
-        Test to make sure that when a new saved query is
-        created, a corresponding tag in the tagged_objects
-        table is created
-        """
-
-        # Remove all existing rows in the tagged_object table
-        self.clear_tagged_object_table()
-
-        # Test to make sure nothing is in the tagged_object table
-        assert [] == self.query_tagged_object_table()
-
-        # Create a saved query and add it to the db
-        test_saved_query = SavedQuery(label="test saved query")
-        db.session.add(test_saved_query)
-        db.session.commit()
-
-        # Test to make sure that a saved query tag was added to the tagged_object table
-        tags = self.query_tagged_object_table()
-
-        assert 2 == len(tags)
-
-        assert "ObjectType.query" == str(tags[0].object_type)
-        assert "editor:None" == str(tags[0].tag.name)
-        assert "TagType.editor" == str(tags[0].tag.type)
-        assert test_saved_query.id == tags[0].object_id
-
-        assert "ObjectType.query" == str(tags[1].object_type)
-        assert "type:query" == str(tags[1].tag.name)
-        assert "TagType.type" == str(tags[1].tag.type)
-        assert test_saved_query.id == tags[1].object_id
-
-        # Cleanup the db
-        db.session.delete(test_saved_query)
-        db.session.commit()
-
-        # Test to make sure the tag is deleted when the associated object is deleted
-        assert [] == self.query_tagged_object_table()
-
-    @pytest.mark.usefixtures("with_tagging_system_feature")
-    def test_favorite_tagging(self):
-        """
-        Test to make sure that when a new favorite object is
-        created, a corresponding tag in the tagged_objects
-        table is created
-        """
-
-        # Remove all existing rows in the tagged_object table
-        self.clear_tagged_object_table()
-
-        # Test to make sure nothing is in the tagged_object table
-        assert [] == self.query_tagged_object_table()
-
-        # Create a favorited object and add it to the db
-        test_saved_query = FavStar(user_id=1, class_name="slice", obj_id=1)
-        db.session.add(test_saved_query)
-        db.session.commit()
-
-        # Test to make sure that a favorited object tag was added to the tagged_object table  # noqa: E501
-        tags = self.query_tagged_object_table()
-        assert 1 == len(tags)
-        assert "ObjectType.chart" == str(tags[0].object_type)
-        assert test_saved_query.obj_id == tags[0].object_id
-
-        # Cleanup the db
-        db.session.delete(test_saved_query)
-        db.session.commit()
-
-        # Test to make sure the tag is deleted when the associated object is deleted
-        assert [] == self.query_tagged_object_table()
-
-    @with_feature_flags(TAGGING_SYSTEM=False)
-    def test_tagging_system(self):
-        """
-        Test to make sure that when the TAGGING_SYSTEM
-        feature flag is false, that no tags are created
-        """
-
-        # Remove all existing rows in the tagged_object table
-        self.clear_tagged_object_table()
-
-        # Test to make sure nothing is in the tagged_object table
-        assert [] == self.query_tagged_object_table()
-
-        # Create a dataset and add it to the db
-        test_dataset = SqlaTable(
-            table_name="foo",
-            schema=None,
-            editors=[],
-            database=get_main_database(),
-            sql=None,
-            extra='{"certification": 1}',
-        )
-
-        # Create a chart and add it to the db
-        test_chart = Slice(
-            slice_name="test_chart",
-            datasource_type=DatasourceType.TABLE,
-            viz_type="bubble",
-            datasource_id=1,
-        )
-
-        # Create a dashboard and add it to the db
-        test_dashboard = Dashboard()
-        test_dashboard.dashboard_title = "test_dashboard"
-        test_dashboard.slug = "test_slug"
-        test_dashboard.published = True
-
-        # Create a saved query and add it to the db
-        test_saved_query = SavedQuery(label="test saved query")
-
-        # Create a favorited object and add it to the db
-        test_favorited_object = FavStar(user_id=1, class_name="slice", obj_id=1)
-
-        db.session.add(test_dataset)
-        db.session.add(test_chart)
-        db.session.add(test_dashboard)
-        db.session.add(test_saved_query)
-        db.session.add(test_favorited_object)
-        db.session.commit()
-
-        # Test to make sure that no tags were added to the tagged_object table
-        tags = self.query_tagged_object_table()
-        assert 0 == len(tags)
-
-        # Cleanup the db
-        db.session.delete(test_dataset)
-        db.session.delete(test_chart)
-        db.session.delete(test_dashboard)
-        db.session.delete(test_saved_query)
-        db.session.delete(test_favorited_object)
-        db.session.commit()
-
-        # Test to make sure all the tags are deleted when the associated objects are deleted  # noqa: E501
-        assert [] == self.query_tagged_object_table()
diff --git a/tests/integration_tests/tags/dao_tests.py b/tests/integration_tests/tags/dao_tests.py
index 6273799a379..d523b3f3073 100644
--- a/tests/integration_tests/tags/dao_tests.py
+++ b/tests/integration_tests/tags/dao_tests.py
@@ -22,7 +22,7 @@ from superset import db
 from superset.daos.tag import TagDAO
 from superset.models.dashboard import Dashboard
 from superset.models.slice import Slice
-from superset.tags.models import ObjectType, Tag, TaggedObject
+from superset.tags.models import ObjectType, Tag, TaggedObject, TagType
 from tests.integration_tests.base_tests import SupersetTestCase
 from tests.integration_tests.constants import ADMIN_USERNAME
 from tests.integration_tests.fixtures.tags import (
@@ -336,3 +336,47 @@ class TestTagsDAO(SupersetTestCase):
             .first()
         )
         assert tagged_object is None
+
+    @pytest.mark.usefixtures("with_tagging_system_feature")
+    def test_tagged_object_cleanup_on_dashboard_delete(self):
+        """Deleting a tagged dashboard cleans up its tagged_object rows.
+
+        Regression guard for ObjectUpdater.after_delete, the one part of the
+        old auto-tagging event-listener machinery still registered: unlike
+        editor:/type:/favorited_by: generation (removed, see TagType's
+        docstring), this cleanup applies to every tag on the object -- custom
+        tags included -- and nothing else removes these rows, since
+        TaggedObject.object_id carries no foreign key (see its column
+        comment).
+        """
+        dashboard = Dashboard(
+            dashboard_title="tag cleanup test", slug="tag-cleanup-test"
+        )
+        db.session.add(dashboard)
+        db.session.commit()
+        dashboard_id = dashboard.id
+
+        tag = self.insert_tag(name="cleanup_test_tag", tag_type=TagType.custom)
+        self.insert_tagged_object(
+            tag_id=tag.id, object_id=dashboard_id, object_type=ObjectType.dashboard
+        )
+
+        def tagged_object_count() -> int:
+            return (
+                db.session.query(TaggedObject)
+                .filter(
+                    TaggedObject.object_type == ObjectType.dashboard.name,
+                    TaggedObject.object_id == dashboard_id,
+                )
+                .count()
+            )
+
+        assert tagged_object_count() == 1
+
+        db.session.delete(dashboard)
+        db.session.commit()
+
+        assert tagged_object_count() == 0
+
+        db.session.delete(tag)
+        db.session.commit()
diff --git a/tests/integration_tests/tasks/async_queries_tests.py b/tests/integration_tests/tasks/async_queries_tests.py
index 16d618f0ece..afe9eda6bae 100644
--- a/tests/integration_tests/tasks/async_queries_tests.py
+++ b/tests/integration_tests/tasks/async_queries_tests.py
@@ -36,9 +36,6 @@ from tests.integration_tests.fixtures.birth_names_dashboard import (
     load_birth_names_data,  # noqa: F401
 )
 from tests.integration_tests.fixtures.query_context import get_query_context
-from tests.integration_tests.fixtures.tags import (
-    with_tagging_system_feature,  # noqa: F401
-)
 from tests.integration_tests.test_app import app
 
 
diff --git a/tests/unit_tests/dashboards/api_test.py b/tests/unit_tests/dashboards/api_test.py
index c7090e98a24..af3e517e26e 100644
--- a/tests/unit_tests/dashboards/api_test.py
+++ b/tests/unit_tests/dashboards/api_test.py
@@ -52,7 +52,6 @@ def mock_dashboard() -> MagicMock:
     dash.editors = []
     dash.viewers = []
     dash.tags = []
-    dash.custom_tags = []
     dash.is_managed_externally = False
     dash.uuid = None
     return dash
diff --git a/tests/unit_tests/views/test_custom_tags_api_mixin.py b/tests/unit_tests/views/test_custom_tags_api_mixin.py
deleted file mode 100644
index 99d08e672d5..00000000000
--- a/tests/unit_tests/views/test_custom_tags_api_mixin.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-"""Tests for both renaming mechanisms in ``CustomTagsOptimizationMixin``.
-
-The schema-level ``data_key`` rename only applies to the API's default list
-schema; requests that pass ``select_columns`` make FAB build a fresh schema
-on the fly, so for those the ``pre_get_list`` response rewrite is what keeps
-the public ``tags`` name. Both paths need coverage.
-"""
-
-from typing import Any
-
-from marshmallow import fields, Schema
-
-from superset.views.custom_tags_api_mixin import CustomTagsOptimizationMixin
-
-
-class BaseApi:
-    """Stub for the FAB ``ModelRestApi`` base. The mixin chains via
-    ``super()``, so the stub must define the hooks the mixin overrides."""
-
-    list_model_schema: Schema
-    pre_get_list_calls: int = 0
-
-    def _init_model_schemas(self) -> None:
-        self.list_model_schema = Schema.from_dict(
-            {"custom_tags": fields.List(fields.String())}
-        )()
-
-    def pre_get_list(self, _data: dict[str, Any]) -> None:
-        self.pre_get_list_calls += 1
-
-
-class CustomTagsApi(CustomTagsOptimizationMixin, BaseApi):
-    _custom_tags_only = True
-
-
-class UnoptimizedTagsApi(CustomTagsOptimizationMixin, BaseApi):
-    _custom_tags_only = False
-
-
-def test_custom_tags_schema_uses_public_tags_name() -> None:
-    api = CustomTagsApi()
-
-    api._init_model_schemas()
-
-    assert api.list_model_schema.dump({"custom_tags": ["critical"]}) == {
-        "tags": ["critical"]
-    }
-
-
-def test_custom_tags_schema_keeps_name_when_optimization_disabled() -> None:
-    api = UnoptimizedTagsApi()
-
-    api._init_model_schemas()
-
-    assert api.list_model_schema.dump({"custom_tags": ["critical"]}) == {
-        "custom_tags": ["critical"]
-    }
-
-
-def test_pre_get_list_renames_custom_tags_when_enabled() -> None:
-    api = CustomTagsApi()
-    data: dict[str, Any] = {
-        "result": [
-            {"id": 1, "custom_tags": [{"name": "critical"}]},
-            {"id": 2},
-        ]
-    }
-
-    api.pre_get_list(data)
-
-    assert data["result"][0] == {"id": 1, "tags": [{"name": "critical"}]}
-    assert data["result"][1] == {"id": 2}
-    assert api.pre_get_list_calls == 1
-
-
-def test_pre_get_list_keeps_custom_tags_when_disabled() -> None:
-    api = UnoptimizedTagsApi()
-    data: dict[str, Any] = {"result": [{"id": 1, "custom_tags": []}]}
-
-    api.pre_get_list(data)
-
-    assert data["result"][0] == {"id": 1, "custom_tags": []}
-    assert api.pre_get_list_calls == 1
-
-
-def test_pre_get_list_tolerates_missing_result_key() -> None:
-    api = CustomTagsApi()
-    data: dict[str, Any] = {"count": 0}
-
-    api.pre_get_list(data)
-
-    assert data == {"count": 0}
-    assert api.pre_get_list_calls == 1