Required structure#
[scythe]
version = "1"
[[sql]]
name = "main"
engine = "postgresql"
schema = ["sql/schema/*.sql"]
queries = ["sql/queries/*.sql"]
output = "src/generated"[scythe]version (required) — currently"1".[[sql]]name (required) — block identifier.[[sql]]engine (required) — one ofpostgresql,mysql,sqlite,duckdb,cockroachdb,mssql,oracle,mariadb,redshift,snowflake.[[sql]]schema / queries (required) — glob patterns for DDL and annotated query files.
Code generation: the array form#
The recommended [[sql.gen]] array syntax supports multiple backends from one SQL block:
[[sql.gen]]
backend = "rust-sqlx"
output = "src/generated/rust"
[[sql.gen]]
backend = "typescript-pg"
output = "src/generated/ts"| Field | Required | Description |
|---|---|---|
backend |
yes | Full backend name, e.g. rust-sqlx, typescript-pg |
output |
yes | Output directory for generated code |
manifest |
no | Path to a partial manifest override |
row_type |
no | Row data-structure style — see below |
outer_join_unions |
no | TypeScript only: emit outer-join nullability as a discriminated union |
structs_only |
no | Suppress query functions, emit types only |
field_case |
no | snake_case or camelCase for generated field names |
namespace |
no | PHP namespace declaration |
extension_functions |
no | Kotlin only: generate extension functions instead of free functions |
serde |
no | Rust only: add serde derives |
derive |
no | Rust only: comma-separated additional derive macros |
row_type#
Python: "dataclass" (default, standard library), "pydantic" (validation), "msgspec" (performance).
TypeScript: "interface" (default), "zod" (schema with inferred types).
field_case#
Accepted by 16 backends (11 TypeScript variants, java-jdbc, java-r2dbc, kotlin-jdbc, kotlin-r2dbc, kotlin-exposed). "snake_case" mirrors SQL names as-is (default); "camelCase" converts them. typescript-kysely requires CamelCasePlugin already installed for this to be meaningful. Two SQL identifiers that collapse to the same field name under this setting produce a hard error.
namespace (PHP only)#
[[sql.gen]]
backend = "php-pdo"
output = "src/generated"
namespace = "App\\Database\\Generated"An empty string omits the namespace declaration entirely.
manifest#
Points to a partial manifest merged over the backend's built-in one, resolved relative to the config file's directory:
[[sql.gen]]
backend = "rust-sqlx"
output = "src/db"
manifest = "manifests/rust-sqlx-custom.toml"# manifests/rust-sqlx-custom.toml
[types.scalars]
decimal = "bigdecimal::BigDecimal"
[imports.rules]
"bigdecimal::" = "use bigdecimal::BigDecimal;"Map-valued tables ([types.scalars], [types.containers], [types.docblock_containers]) are replace-only and reject new keys as typos. [imports.rules] accepts new keys. [naming] supports per-field replacement of struct_case, fn_case, enum_variant_case, and row_suffix.
Breaking change to be aware of: to_pascal_case now normalizes consecutive capitals uniformly — CreateAPIKey becomes CreateApiKeyRow (previously CreateAPIKeyRow). This affects every backend using struct_case = "PascalCase".
Legacy syntax: [sql.gen.<lang>]#
Still supported, but limited to one backend per language:
[sql.gen.rust]
target = "sqlx"
derive = ["Debug", "Clone"]
serde = true
[sql.gen.python]
target = "psycopg3"
[sql.gen.go]
target = "pgx"Valid target values by language: rust — sqlx, tokio-postgres, tiberius, sibyl. python — psycopg3, asyncpg, aiomysql, aiosqlite, duckdb, pyodbc, oracledb, snowflake. typescript — pg, postgres, mysql2, better-sqlite3, node-sqlite, duckdb, wasm-sqlite, kysely, mssql, oracledb, snowflake. go — pgx, database-sql, godror, gosnowflake. kotlin — jdbc, exposed, r2dbc.
Type overrides#
[[sql.type_overrides]]
column = "users.metadata"
type = "json"
[[sql.type_overrides]]
db_type = "uuid"
type = "string"column targets a specific table.column; db_type targets every column resolving to that neutral type. When both are set on separate rules that could match the same column, column takes precedence silently. See Custom Types for worked examples.
Linting configuration#
[lint]
[lint.categories]
safety = "error"
naming = "warn"
performance = "warn"
[lint.rules]
"SC-S03" = "off"
"SC-N03" = "error"Categories: naming, safety, style, performance, antipattern, codegen, security, migration, provenance, drift. Per-rule settings under [lint.rules] override the category default for that one rule.
Sqruff integration#
[lint.sqruff]
enabled = true
[lint.sqruff.rules]
"LT01" = "off"enabled = false skips sqruff entirely. Per-rule entries only support "off". Note that scythe fmt ignores this block and always runs its default rules except LT01.
Inspect configuration#
[inspect]
database_url = "postgres://localhost/dev"
api_schemas = ["public", "api"]
extra_rules = ["./inspect-rules.toml"]Audit configuration#
[audit]
extra_rules = ["./security_rules.toml"]
[[audit.rule]]
id = "USER-001"
name = "no-debug-functions"
severity = "error"
description = "calls to debug-only functions should not ship"
message = "call to debug function `{func}` — remove before merging"
matcher = "function_name_in_set"
[audit.rule.matcher_args]
functions = ["dump_internal_state", "debug_print"]Custom rule IDs must be prefixed USER-. Full matcher list in Security Audit.
Multiple SQL blocks#
[[sql]]
name = "users"
engine = "postgresql"
schema = ["sql/users/schema.sql"]
queries = ["sql/users/queries/*.sql"]
output = "src/generated/users"
[[sql]]
name = "analytics"
engine = "postgresql"
schema = ["sql/analytics/schema.sql"]
queries = ["sql/analytics/queries/*.sql"]
output = "src/generated/analytics"Engine aliases#
| Aliases | Resolves to |
|---|---|
postgresql, postgres, pg, cockroachdb, crdb, duckdb, redshift |
PostgreSQL dialect |
mysql, mariadb |
MySQL dialect |
sqlite, sqlite3 |
SQLite dialect |
mssql, sqlserver, tsql |
MsSql dialect |
oracle |
Oracle dialect |
snowflake |
Snowflake dialect |
DuckDB, Redshift, and CockroachDB alias to the PostgreSQL dialect and receive identical parsing and type resolution.
Error handling#
Scythe treats the following as hard errors rather than silent fallbacks: missing glob-pattern files, typos in config keys (with suggestions), missing manifest files, unrecognized backend options (with suggestions), duplicate field names under field_case, and two identifiers colliding under a naming setting.
Related#
- CLI Reference for how
--configinteracts with each command. - Custom Types for
type_overridesin depth.