How to Use FastCRUD Relationship Auto-Detection: A Complete Guide

FastCRUD automatically joins SQLAlchemy relationships by passing auto_detect_relationships=True to methods like get_joined or get_multi_joined, with optional include_one_to_many=True for one-to-many relations.

FastCRUD's relationship auto-detection eliminates manual join configuration when querying related data in FastAPI applications. This feature, implemented in the benavlabs/fastcrud repository, inspects your SQLAlchemy models for relationship() definitions and generates safe join conditions automatically. You can enable it on a per-query basis while retaining full control over nesting structure and relationship types.

How Relationship Auto-Detection Works

The auto-detection system operates through three internal phases defined in fastcrud/core/field_management.py:

  1. Relationship Discovery – The discover_model_relationships function inspects the SQLAlchemy mapper to identify all relationship() attributes on your model.

  2. Join Configuration – For each discovered relationship, build_relationship_joins_config calls auto_detect_join_condition to generate safe join clauses. By default, it filters for one-to-one relationships only, unless you explicitly enable one-to-many support.

  3. Query Execution – In fastcrud/crud/fast_crud.py, methods like get_joined (around lines 1818-1843) replace manual join parameters with the auto-generated JoinConfig objects when auto_detect_relationships is truthy.

The prepare_joins function in fastcrud/core/query/joins.py then materializes these configurations into actual SQLAlchemy join clauses.

Basic Usage with One-to-One Relationships

By default, FastCRUD only joins one-to-one relationships to prevent accidental Cartesian products. Instantiate your CRUD class and call get_joined with auto_detect_relationships=True:

from fastcrud import FastCRUD
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import Depends, APIRouter

router = APIRouter()
user_crud = FastCRUD(User)

@router.get("/users/{user_id}")
async def read_user(user_id: int, db: AsyncSession = Depends(get_db)):
    return await user_crud.get_joined(
        db,
        schema_to_select=UserReadSchema,
        id=user_id,
        auto_detect_relationships=True,   # Auto-join all one-to-one relations

        nest_joins=True,                  # Return nested dicts instead of flat keys

    )

When nest_joins=True, the response structure places related data under nested dictionaries (e.g., {"id": 1, "tier": {"name": "Premium"}} rather than {"id": 1, "tier_name": "Premium"}).

Including One-to-Many Relationships

To include one-to-many relationships (such as UserPosts), pass include_one_to_many=True. Be cautious with this option on large datasets, as it can create large result sets:

@router.get("/users/{user_id}")
async def read_user_with_posts(user_id: int, db: AsyncSession = Depends(get_db)):
    return await user_crud.get_joined(
        db,
        schema_to_select=UserReadSchema,
        id=user_id,
        auto_detect_relationships=True,
        include_one_to_many=True,   # Enable one-to-many joins

        nest_joins=True,
    )

This configuration is documented in the method signature around lines 1582-1584 of fastcrud/crud/fast_crud.py. The filtering logic resides in build_relationship_joins_config (lines 32-53 of fastcrud/core/field_management.py).

Selective Relationship Detection

Pass a list of relationship names to auto_detect_relationships to join specific relations while bypassing the default one-to-one filter:

@router.get("/users/{user_id}")
async def read_user_selective(user_id: int, db: AsyncSession = Depends(get_db)):
    return await user_crud.get_joined(
        db,
        schema_to_select=UserReadSchema,
        id=user_id,
        auto_detect_relationships=["tier", "posts"],   # Explicit list of relation names

        nest_joins=True,
    )

When you provide a list, the logic in build_relationship_joins_config (lines 38-44) bypasses the include_one_to_many check and joins only the named relationships regardless of their cardinality.

Multi-Record Queries with Auto-Detection

Use get_multi_joined to apply auto-detection to paginated lists. The method reuses the same internal flow as get_joined (see lines 1850-1880 in fastcrud/crud/fast_crud.py):

@router.get("/users")
async def list_users(
    db: AsyncSession = Depends(get_db),
    limit: int = 10,
    offset: int = 0
):
    return await user_crud.get_multi_joined(
        db,
        schema_to_select=UserReadSchema,
        limit=limit,
        offset=offset,
        auto_detect_relationships=True,
        include_one_to_many=False,   # Keep to one-to-one for list views

        nest_joins=True,
    )

This returns a paginated response where each user record contains its nested one-to-one relations.

Error Handling and Constraints

FastCRUD prevents ambiguous query construction by raising a clear error when you attempt to mix auto-detection with manual join parameters. The validation logic exists at lines 1828-1832 of fastcrud/crud/fast_crud.py:


# This raises ValueError

await user_crud.get_joined(
    db,
    schema_to_select=UserReadSchema,
    id=1,
    join_model=Tier,                     # Manual join parameter

    auto_detect_relationships=True,     # Conflicts with manual join

)

The error message states: "Cannot use auto_detect_relationships with manual join parameters." Use either the explicit join_model/joins_config approach or auto-detection, never both.

Summary

  • Enable auto-detection by passing auto_detect_relationships=True (or a list of relationship names) to get_joined, get_multi_joined, get, or get_multi.
  • Default behavior only joins one-to-one relationships; set include_one_to_many=True to include collection-based relations.
  • Nest results using nest_joins=True to receive hierarchical dictionaries instead of flat key hierarchies.
  • Conflict prevention – Never combine auto_detect_relationships with explicit join_model or joins_config parameters.
  • Source implementation resides in fastcrud/core/field_management.py for relationship discovery and fastcrud/crud/fast_crud.py for query execution.

Frequently Asked Questions

What methods support the auto_detect_relationships parameter?

The auto_detect_relationships parameter is available in get, get_multi, get_joined, and get_multi_joined methods of the FastCRUD class defined in fastcrud/crud/fast_crud.py. These methods delegate relationship discovery to build_relationship_joins_config in the field management module when the parameter is truthy.

Why am I getting a ValueError when using auto_detect_relationships?

FastCRUD raises a ValueError if you mix auto_detect_relationships with explicit join parameters like join_model, joins_config, or join_on. This validation at lines 1828-1832 of fastcrud/crud/fast_crud.py prevents ambiguous query construction. Choose either automatic relationship detection or manual join configuration, not both.

Does auto-detection work with many-to-many relationships?

Many-to-many relationships are treated as one-to-many from the perspective of the source side. They require include_one_to_many=True to be joined automatically. However, for complex many-to-many scenarios with association tables requiring specific join conditions, explicit JoinConfig objects remain the recommended approach according to the source implementation.

How do I control the nesting structure of joined results?

Set nest_joins=True to receive nested dictionaries where one-to-one relationships appear as nested objects and one-to-many appear as lists. When nest_joins=False (the default), joined fields use prefixed keys (e.g., tier_name). The nesting logic is handled by prepare_joins in fastcrud/core/query/joins.py, which processes the JoinConfig objects generated by the auto-detection system.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →