Using the legacy ‘backref’ relationship parameter — SQLAlchemy 2.0 Documentation

Release: 2.0.51 | Release Date: June 15, 2026

# SQLAlchemy 2.0 Documentation

# Using the legacy ‘backref’ relationship parameter

Note

The `relationship.backref` keyword should be considered legacy, and use of `relationship.back_populates` with explicit `relationship()` constructs should be preferred. Using individual `relationship()` constructs provides advantages including that both ORM mapped classes will include their attributes up front as the class is constructed, rather than as a deferred step, and configuration is more straightforward as all arguments are explicit. New PEP 484 features in SQLAlchemy 2.0 also take advantage of attributes being explicitly present in source code rather than using dynamic attribute generation.

See also

For general information about bidirectional relationships, see the following sections:

Working with ORM Related Objects - in the SQLAlchemy Unified Tutorial, presents an overview of bi-directional relationship configuration and behaviors using `relationship.back_populates`

Behavior of save-update cascade with bi-directional relationships - notes on bi-directional `relationship()` behavior regarding `Session` cascade behaviors.

`relationship.back_populates`

The `relationship.backref` keyword argument on the `relationship()` construct allows the automatic generation of a new `relationship()` that will be automatically be added to the ORM mapping for the related class. It will then be placed into a `relationship.back_populates` configuration against the current `relationship()` being configured, with both `relationship()` constructs referring to each other.

Starting with the following example:

    from sqlalchemy import Column, ForeignKey, Integer, String
    from sqlalchemy.orm import DeclarativeBase, relationship

    class Base(DeclarativeBase):
        pass

    class User(Base):
        __tablename__ = "user"
        id = mapped_column(Integer, primary_key=True)
        name = mapped_column(String)

        addresses = relationship("Address", backref="user")

    class Address(Base):
        __tablename__ = "address"
        id = mapped_column(Integer, primary_key=True)
        email = mapped_column(String)
        user_id = mapped_column(Integer, ForeignKey("user.id"))

The above configuration establishes a collection of `Address` objects on `User` called `User.addresses`. It also establishes a `.user` attribute on `Address` which will refer to the parent `User` object. Using `relationship.back_populates` it’s equivalent to the following:

    from sqlalchemy import Column, ForeignKey, Integer, String
    from sqlalchemy.orm import DeclarativeBase, relationship

    class Base(DeclarativeBase):
        pass

    class User(Base):
        __tablename__ = "user"
        id = mapped_column(Integer, primary_key=True)
        name = mapped_column(String)

        addresses = relationship("Address", back_populates="user")

    class Address(Base):
        __tablename__ = "address"
        id = mapped_column(Integer, primary_key=True)
        email = mapped_column(String)
        user_id = mapped_column(Integer, ForeignKey("user.id"))

        user = relationship("User", back_populates="addresses")

The behavior of the `User.addresses` and `Address.user` relationships is that they now behave in a bi-directional way, indicating that changes on one side of the relationship impact the other. An example and discussion of this behavior is in the SQLAlchemy Unified Tutorial at Working with ORM Related Objects.

## Backref Default Arguments

Since `relationship.backref` generates a whole new `relationship()`, the generation process by default will attempt to include corresponding arguments in the new `relationship()` that correspond to the original arguments. As an example, below is a `relationship()` that includes a custom join condition which also includes the `relationship.backref` keyword:

    from sqlalchemy import Column, ForeignKey, Integer, String
    from sqlalchemy.orm import DeclarativeBase, relationship

    class Base(DeclarativeBase):
        pass

    class User(Base):
        __tablename__ = "user"
        id = mapped_column(Integer, primary_key=True)
        name = mapped_column(String)

        addresses = relationship(
            "Address",
            primaryjoin=(
                "and_(User.id==Address.user_id, Address.email.startswith('tony'))"
            ),
            backref="user",
        )

    class Address(Base):
        __tablename__ = "address"
        id = mapped_column(Integer, primary_key=True)
        email = mapped_column(String)
        user_id = mapped_column(Integer, ForeignKey("user.id"))

When the “backref” is generated, the `relationship.primaryjoin` condition is copied to the new `relationship()` as well:

    >>> print(User.addresses.property.primaryjoin)
    "user".id = address.user_id AND address.email LIKE :email_1 || '%%'
    >>>
    >>> print(Address.user.property.primaryjoin)
    "user".id = address.user_id AND address.email LIKE :email_1 || '%%'
    >>>

Other arguments that are transferable include the `relationship.secondary` parameter that refers to a many-to-many association table, as well as the “join” arguments `relationship.primaryjoin` and `relationship.secondaryjoin`; “backref” is smart enough to know that these two arguments should also be “reversed” when generating the opposite side.

## Specifying Backref Arguments

Lots of other arguments for a “backref” are not implicit, and include arguments like `relationship.lazy`, `relationship.remote_side`, `relationship.cascade` and `relationship.cascade_backrefs`. For this case we use the `backref()` function in place of a string; this will store a specific set of arguments that will be transferred to the new `relationship()` when generated:

    # <other imports>
    from sqlalchemy.orm import backref

    class User(Base):
        __tablename__ = "user"
        id = mapped_column(Integer, primary_key=True)
        name = mapped_column(String)

        addresses = relationship(
            "Address",
            backref=backref("user", lazy="joined"),
        )

Where above, we placed a `lazy="joined"` directive only on the `Address.user` side, indicating that when a query against `Address` is made, a join to the `User` entity should be made automatically which will populate the `.user` attribute of each returned `Address`. The `backref()` function formatted the arguments we gave it into a form that is interpreted by the receiving `relationship()` as additional arguments to be applied to the new relationship it creates.