Windmill Rust Patterns
Apply these Windmill-specific patterns when writing Rust code in backend/.
Error Handling
Use Error from windmill_common::error. Return Result<T, Error> or JsonResult<T>:
Never panic in library code. Reserve .unwrap() for compile-time guarantees.
SQLx Patterns
Never use SELECT * — always list columns explicitly. Critical for backwards compatibility when workers lag behind API version:
Use batch operations to avoid N+1:
Use transactions for multi-step operations. Parameterize all queries.
JSON Handling
Prefer Box<serde_json::value::RawValue> over serde_json::Value when storing/passing JSON without inspection:
Only use serde_json::Value when you need to inspect or modify the JSON.
Serde Optimizations
Async & Concurrency
Never block the async runtime. Use spawn_blocking for CPU-intensive work:
Mutex selection: Prefer std::sync::Mutex (or parking_lot::Mutex) for data protection. Only use tokio::sync::Mutex when holding locks across .await points.
Use tokio::sync::mpsc (bounded) for channels. Avoid std::thread::sleep in async contexts.
Module Structure & Visibility
- Use
pub(crate)instead ofpubwhen possible - Place new code in the appropriate crate based on functionality
- API endpoints go in
windmill-api/src/organized by domain - Shared functionality goes in
windmill-common/src/
Code Navigation
Always use rust-analyzer LSP for go-to-definition, find-references, and type info. Do not guess at module paths.
Feature Telemetry
FEATURE_USAGE_KINDS in windmill-api-workspaces/src/workspaces.rs is an allowlist: a
(feature, kind) pair missing from it is dropped by valid_feature_usage_event with a bare
continue — no error, and the route still returns 204. Adding a counter on the frontend without
registering it here records nothing. See docs/feature-telemetry.md.
Axum Handlers
Destructure extractors directly in function signatures:


