Attribute System
CRUDCrate uses attributes to configure code generation. This guide covers all available attributes.
Attribute Syntax
Attributes use the #[crudcrate(...)] syntax:
// Struct-level
#[crudcrate(generate_router, api_struct = "Item")]
pub struct Model {
// Field-level
#[crudcrate(primary_key, filterable, sortable)]
pub id: i32,
}
Struct-Level Attributes
Applied to the struct definition:
generate_router
Generates an Axum router function:
#[crudcrate(generate_router)]
pub struct Model { }
// Generates:
pub fn model_router() -> Router { }
routes
Names the route families router() mounts. Omit it for every route, which is the default:
#[crudcrate(generate_router, routes(read, update))]
pub struct Model { }
// Mounts GET /, GET /{id}, PATCH /{id} and PATCH /batch.
// POST and DELETE are not routes at all, so the OpenAPI document does not advertise them.
The families are create, read, update and delete, each covering its single-row and batch
routes. Use it for a table whose rows are a projection of something else: rows a migration or a
registry inserts have no legitimate create or delete, and a handler that refuses one still
advertises the operation.
api_struct
Override the generated struct name:
#[crudcrate(api_struct = "Product")]
pub struct Model { }
// Generates: Product, ProductCreate, ProductUpdate, ProductList
// Instead of: Model, ModelCreate, etc.
name_singular / name_plural
Override resource names (for routing and Content-Range headers):
#[crudcrate(name_singular = "person", name_plural = "people")]
pub struct Model { }
// Routes: /people, /people/:id
// Content-Range: people 0-9/100
operations
Specify a custom CRUDOperations implementation:
#[crudcrate(operations = MyOperations)]
pub struct Model { }
// MyOperations must implement CRUDOperations
description
Add OpenAPI description:
#[crudcrate(description = "User accounts for the application")]
pub struct Model { }
fulltext_language
Set fulltext search language (PostgreSQL):
#[crudcrate(fulltext_language = "spanish")]
pub struct Model { }
// Uses Spanish stemming for fulltext search
Field-Level Attributes
Applied to individual fields:
primary_key
Marks the primary key field:
#[crudcrate(primary_key)]
pub id: i32,
// Required: exactly one field must have this
exclude(...)
Exclude field from specific models:
// Exclude from single model
#[crudcrate(exclude(create))]
pub id: i32,
// Exclude from multiple models
#[crudcrate(exclude(one, list))]
pub password: String,
// Available targets:
// - one: Response model (GET /items/:id)
// - create: Create model (POST /items)
// - update: Update model (PUT /items/:id)
// - list: List model (GET /items)
filterable
Enable filtering on this field:
#[crudcrate(filterable)]
pub status: String,
// Allows: GET /items?filter={"status":"active"}
// Allows: GET /items?status_eq=active
// Allows: GET /items?status_ne=inactive
sortable
Enable sorting on this field:
#[crudcrate(sortable)]
pub created_at: DateTimeUtc,
// Allows: GET /items?sort=["created_at","DESC"]
// Allows: GET /items?sort=created_at&order=desc
fulltext
Include in fulltext search:
#[crudcrate(fulltext)]
pub title: String,
#[crudcrate(fulltext)]
pub description: String,
// Allows: GET /items?q=search terms
// Searches across all fulltext fields
on_create
Default value when creating:
#[crudcrate(on_create = Uuid::new_v4())]
pub id: Uuid,
#[crudcrate(on_create = chrono::Utc::now())]
pub created_at: DateTimeUtc,
#[crudcrate(on_create = "pending".to_string())]
pub status: String,
// Expression is evaluated at insert time
on_update
Default value when updating:
#[crudcrate(on_update = chrono::Utc::now())]
pub updated_at: DateTimeUtc,
// Expression is evaluated on every update
non_db_attr
Marks non-database fields (for relationships):
#[sea_orm(ignore)] // Sea-ORM: ignore in queries
#[crudcrate(non_db_attr)] // CRUDCrate: not a DB column
pub related_items: Vec<Item>,
join(...)
Configure relationship loading:
// Load in get_one only
#[crudcrate(non_db_attr, join(one))]
pub comments: Vec<Comment>,
// Load in both get_one and get_all
#[crudcrate(non_db_attr, join(one, all))]
pub author: Option<User>,
// Limit recursion depth
#[crudcrate(non_db_attr, join(one, all, depth = 2))]
pub nested: Vec<Nested>,
// Full syntax
join(
one, // Include in get_one response
all, // Include in get_all response
depth = 3, // Max recursion depth (default: unlimited up to 5)
)
Lifecycle Hooks
Hook into CRUD operations:
#[crudcrate(
// Pre-operation hooks (before DB operation)
create::one::pre = validate_create,
update::one::pre = validate_update,
delete::one::pre = check_delete_permission,
// Post-operation hooks (after DB operation)
create::one::post = send_welcome_email,
update::one::post = invalidate_cache,
delete::one::post = cleanup_related,
// Full body replacement
create::one::body = custom_create_handler,
)]
pub struct Model { }
// Hook function signatures:
async fn validate_create<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(db: &C, data: &mut ModelCreate) -> Result<(), ApiError>;
async fn send_welcome_email<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(db: &C, created: &Model) -> Result<(), ApiError>;
async fn custom_create_handler<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(db: &C, data: ModelCreate) -> Result<Model, ApiError>;
Combining Attributes
Multiple attributes on one field:
#[crudcrate(primary_key, exclude(create, update), on_create = Uuid::new_v4())]
pub id: Uuid,
#[crudcrate(filterable, sortable, fulltext)]
pub title: String,
#[crudcrate(non_db_attr, join(one, all, depth = 1))]
pub author: Option<User>,
Complete Example
use crudcrate::EntityToModels;
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, DeriveEntityModel, Serialize, Deserialize, EntityToModels)]
#[crudcrate(
generate_router,
api_struct = "Article",
name_singular = "article",
name_plural = "articles",
operations = ArticleOperations,
create::one::post = notify_subscribers,
)]
#[sea_orm(table_name = "articles")]
pub struct Model {
// Primary key with auto-generation
#[sea_orm(primary_key, auto_increment = false)]
#[crudcrate(primary_key, exclude(create, update), on_create = Uuid::new_v4())]
pub id: Uuid,
// Searchable, filterable, sortable title
#[crudcrate(filterable, sortable, fulltext)]
pub title: String,
// Searchable content, excluded from lists
#[crudcrate(fulltext, exclude(list))]
pub content: String,
// Short summary for lists
pub summary: Option<String>,
// Filterable status enum
#[crudcrate(filterable)]
pub status: ArticleStatus,
// Author relationship
#[crudcrate(filterable)]
pub author_id: Uuid,
// Sortable publication date
#[crudcrate(sortable, filterable)]
pub published_at: Option<DateTimeUtc>,
// Auto-managed timestamps
#[crudcrate(sortable, exclude(create, update), on_create = chrono::Utc::now())]
pub created_at: DateTimeUtc,
#[crudcrate(exclude(create, update), on_create = chrono::Utc::now(), on_update = chrono::Utc::now())]
pub updated_at: DateTimeUtc,
// Loaded relationships
#[sea_orm(ignore)]
#[crudcrate(non_db_attr, join(one))]
pub author: Option<User>,
#[sea_orm(ignore)]
#[crudcrate(non_db_attr, join(one))]
pub comments: Vec<Comment>,
}
Attribute Reference Table
| Attribute | Level | Description |
|---|---|---|
generate_router | Struct | Generate Axum router |
routes(...) | Struct | The route families the router mounts |
api_struct | Struct | Custom struct name |
name_singular | Struct | Singular resource name |
name_plural | Struct | Plural resource name |
operations | Struct | Custom operations trait |
description | Struct | OpenAPI description |
fulltext_language | Struct | Fulltext search language |
primary_key | Field | Mark as primary key |
exclude(...) | Field | Exclude from models |
filterable | Field | Enable filtering |
sortable | Field | Enable sorting |
fulltext | Field | Include in fulltext search |
on_create | Field | Default value on create |
on_update | Field | Default value on update |
non_db_attr | Field | Non-database field |
join(...) | Field | Relationship loading config |
Next Steps
- Learn about Filtering
- Configure Relationships
- Implement Custom Operations