Overview

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 of postgresql, 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: rustsqlx, tokio-postgres, tiberius, sibyl. pythonpsycopg3, asyncpg, aiomysql, aiosqlite, duckdb, pyodbc, oracledb, snowflake. typescriptpg, postgres, mysql2, better-sqlite3, node-sqlite, duckdb, wasm-sqlite, kysely, mssql, oracledb, snowflake. gopgx, database-sql, godror, gosnowflake. kotlinjdbc, 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.

Updated

Was this page helpful?