pallets/flask · error · KeyError

Tag '{key}' is already registered.

Error message

Tag '{key}' is already registered.

What it means

Raised by `TaggedJSONSerializer.register()` in src/flask/json/tag.py, the serializer Flask uses to encode non-JSON types (tuples, bytes, datetimes, Markup, UUID) in the session cookie. Each JSONTag subclass declares a unique `key` (like ' t' or ' b'); registering a second tag class with a key already in `self.tags` raises KeyError unless `force=True` is passed, preventing one tag from silently shadowing another and corrupting session round-trips.

Source

Thrown at src/flask/json/tag.py:280

        """Register a new tag with this serializer.

        :param tag_class: tag class to register. Will be instantiated with this
            serializer instance.
        :param force: overwrite an existing tag. If false (default), a
            :exc:`KeyError` is raised.
        :param index: index to insert the new tag in the tag order. Useful when
            the new tag is a special case of an existing tag. If ``None``
            (default), the tag is appended to the end of the order.

        :raise KeyError: if the tag key is already registered and ``force`` is
            not true.
        """
        tag = tag_class(self)
        key = tag.key

        if key:
            if not force and key in self.tags:
                raise KeyError(f"Tag '{key}' is already registered.")

            self.tags[key] = tag

        if index is None:
            self.order.append(tag)
        else:
            self.order.insert(index, tag)

    def tag(self, value: t.Any) -> t.Any:
        """Convert a value to a tagged representation if necessary."""
        for tag in self.order:
            if tag.check(value):
                return tag.tag(value)

        return value

    def untag(self, value: dict[str, t.Any]) -> t.Any:
        """Convert a tagged representation back to the original type."""

View on GitHub ↗ (pinned to 6a2f545bfd)

Solutions

  1. Pick a unique `key` for your JSONTag subclass that doesn't collide with built-ins (' d', ' t', ' u', ' m', ' b', ' di', ' ti') or your other tags.
  2. If intentionally replacing an existing tag, pass `force=True`: `serializer.register(MyTag, force=True, index=0)`.
  3. Guard registration so it runs once (module-level flag, or check `key in serializer.tags` before registering) when using an app factory.

Example fix

# before
class TagOrderedDict(JSONTag):
    key = " t"  # collides with built-in TagTuple
serializer.register(TagOrderedDict)

# after
class TagOrderedDict(JSONTag):
    key = " od"
if " od" not in serializer.tags:
    serializer.register(TagOrderedDict, index=0)
Defensive patterns

Strategy: validation

Validate before calling

serializer = app.session_interface.get_signing_serializer  # example context
from flask.json.tag import TaggedJSONSerializer
s = TaggedJSONSerializer()
if not any(t.key == '_myobj' for t in s.tags.values() if t.key):
    s.register(MyTag)

Type guard

def tag_key_free(serializer, key):
    return key not in serializer.tags

Try / catch

try:
    serializer.register(MyTag)
except KeyError:
    pass  # already registered — only safe if it is the SAME tag class; otherwise pick a new key

Prevention

When it happens

Trigger: Calling `app.session_interface.serializer.register(MyTag)` (or `TaggedJSONSerializer().register(...)`) where `MyTag.key` collides with a built-in tag key or with a tag you already registered — including registering the same custom tag class twice (e.g. once per worker init or in a module imported twice).

Common situations: Custom session serialization extensions registering tags at import time in a module that gets re-imported or executed in both the app factory and a blueprint; choosing a short key that collides with Flask's built-ins; app factories that call register() on a shared serializer instance for every created app.

Related errors


AI-assisted analysis of pallets/flask@6a2f545bfd (2026-07-31). Data as JSON: /data/errors/e6e5f55392cffdaf.json. Report an issue: GitHub ↗.