How to Set Up Soft Delete with FastCRUD: Complete Implementation Guide
FastCRUD implements soft delete by detecting specific boolean and timestamp columns on your SQLAlchemy model and updating those fields instead of executing a hard DELETE statement.
Setting up soft delete with FastCRUD requires minimal configuration. The library, maintained in the benavlabs/fastcrud repository, automatically detects soft-delete columns by checking for the presence of is_deleted and deleted_at attributes on your model. When these columns exist, the FastCRUD.delete() method switches from hard deletion to updating these flag fields, allowing you to retain historical data while filtering out "deleted" records from standard queries.
How Soft Delete Works in FastCRUD
The soft-delete mechanism in FastCRUD operates through runtime column detection. In fastcrud/crud/fast_crud.py, the delete() method (around lines 3004-3014) checks whether the target database row has the configured soft-delete attribute using hasattr(db_row, self.is_deleted_column).
If the attribute exists, FastCRUD performs an UPDATE operation that sets the boolean flag to True and optionally populates the timestamp column with the current UTC time. If the columns are absent, the method falls back to a standard SQL DELETE statement. This design makes soft delete entirely opt-in based on your model schema.
Setting Up Soft Delete in Your Model
To enable soft delete, you must add the appropriate columns to your SQLAlchemy model. FastCRUD uses default column names of is_deleted (Boolean) and deleted_at (DateTime), though these are configurable.
Basic Model Configuration
Add the soft-delete columns to your SQLAlchemy declarative base model:
# models/item.py
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from fastcrud.database import Base
from datetime import datetime
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
description = Column(String, nullable=True)
# Soft delete columns using default names
is_deleted = Column(Boolean, default=False, nullable=False)
deleted_at = Column(DateTime, nullable=True, default=None)
With these columns present, any FastCRUD instance created for the Item model will automatically perform soft deletes.
Configuring FastCRUD for Soft Delete
Once your model includes the soft-delete columns, instantiate FastCRUD with or without custom column names depending on your schema.
Using Default Column Names
If your model uses is_deleted and deleted_at, no additional configuration is required:
# crud/item.py
from fastcrud import FastCRUD
from models.item import Item
item_crud = FastCRUD(Item)
# This will perform a soft delete
# UPDATE items SET is_deleted = true, deleted_at = NOW() WHERE id = 42
Customizing Column Names
If your existing database schema uses different column names, such as archived and archived_at, pass these to the FastCRUD constructor. The constructor parameters are defined in fastcrud/crud/fast_crud.py (lines 88-90):
# crud/product.py
from fastcrud import FastCRUD
from models.product import Product
product_crud = FastCRUD(
Product,
is_deleted_column="archived", # Custom boolean flag column
deleted_at_column="archived_at", # Custom timestamp column
)
The EndpointCreator class in fastcrud/endpoint/endpoint_creator.py (lines 81-83) also accepts these parameters, allowing you to propagate custom column names through your entire API layer.
Advanced Soft Delete Configuration
For applications requiring audit trails, FastCRUD provides DeleteConfig to inject additional metadata during soft-delete operations.
Injecting Metadata with DeleteConfig
The DeleteConfig class, located in fastcrud/core/config/crud_configs.py (lines 97-133), supports an auto_fields dictionary. This allows you to automatically populate fields like deleted_by using FastAPI's dependency injection system:
# crud/document.py
from fastcrud import FastCRUD, DeleteConfig
from fastapi import Depends, Cookie
from datetime import datetime
from models.document import Document
async def get_current_user_id(session_token: str = Cookie(None)):
# Your authentication logic here
return 123
def utc_now():
return datetime.utcnow()
delete_config = DeleteConfig(
auto_fields={
"deleted_by": get_current_user_id, # Resolved per-request via DI
"deleted_at": utc_now, # Custom timestamp function
}
)
document_crud = FastCRUD(
Document,
delete_config=delete_config,
)
When document_crud.delete(db, id=1) is called, FastCRUD automatically resolves get_current_user_id and utc_now, injecting the results into the deleted_by and deleted_at columns before committing the update.
Implementing Soft Delete in FastAPI Endpoints
To expose soft-delete functionality through REST endpoints, use the EndpointCreator or crud_router utility. These tools accept the same soft-delete configuration parameters and generate standard DELETE routes that perform soft deletion when configured.
# api/item_router.py
from fastapi import APIRouter, Depends
from fastcrud import EndpointCreator
from database import async_session
from models.item import Item
from schemas.item import CreateItemSchema, UpdateItemSchema
router = APIRouter()
item_endpoint = EndpointCreator(
session=async_session,
model=Item,
create_schema=CreateItemSchema,
update_schema=UpdateItemSchema,
# Optional: override default column names
# is_deleted_column="is_archived",
# deleted_at_column="archived_timestamp",
)
item_endpoint.add_routes_to_router()
router.include_router(item_endpoint.router, prefix="/items", tags=["items"])
The generated DELETE /items/{id} endpoint will now invoke the soft-delete logic, setting is_deleted to True and populating deleted_at rather than removing the record from the database.
Summary
- FastCRUD implements soft delete by detecting specific boolean and timestamp columns on your SQLAlchemy models.
- Default columns are
is_deletedanddeleted_at, but you can customize these via theis_deleted_columnanddeleted_at_columnparameters inFastCRUDorEndpointCreator. - Automatic fallback occurs if soft-delete columns are missing; FastCRUD performs a hard delete instead.
- DeleteConfig enables advanced audit trails by injecting auto-populated fields like
deleted_byusing FastAPI dependency injection. - EndpointCreator propagates soft-delete settings to REST endpoints automatically, requiring no manual route logic.
Frequently Asked Questions
What happens if my model doesn't have the soft-delete columns?
If your SQLAlchemy model lacks the is_deleted column (or your custom-named equivalent), FastCRUD automatically falls back to a hard delete. The delete() method in fastcrud/crud/fast_crud.py checks for the attribute using hasattr(); when absent, it executes a standard SQL DELETE statement instead of updating flag columns.
Can I use different column names for soft delete?
Yes. Both FastCRUD and EndpointCreator accept is_deleted_column and deleted_at_column parameters to match existing schemas. For example, if your database uses archived and archived_at, pass these strings when initializing your CRUD instance. These parameters are defined in fastcrud/crud/fast_crud.py (lines 88-90) and fastcrud/endpoint/endpoint_creator.py (lines 81-83).
How do I track who deleted a record?
Use the DeleteConfig class with the auto_fields dictionary. Located in fastcrud/core/config/crud_configs.py (lines 97-133), this configuration allows you to map column names to callable dependencies. For example, set "deleted_by": get_current_user_id where get_current_user_id is a FastAPI dependency that resolves the current user's ID. FastCRUD automatically injects these values when performing the soft delete.
Does soft delete affect my query results?
FastCRUD's soft-delete mechanism only modifies how the delete() method behaves; it does not automatically filter deleted records from get or get_multi operations. To exclude soft-deleted rows from your queries, you must manually add filters such as is_deleted=False (or your custom column) when calling read methods. This gives you explicit control over whether to include archived data in your results.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →