A sqlc plugin that renders arbitrary code from
user-supplied Go text/template files. Point it at a templates directory and
it will render each *.tmpl file against sqlc's parsed catalog and queries.
A better equivalent of fdietze/sqlc-gen-from-template with the ergonomics of protoc-contrib/protoc-gen-template: sprig functions, naming helpers, language type mappers, filename templating, partials, and cross-file includes.
- Runs as a process plugin (native binary, reads templates from disk) or a WASM plugin (sandboxed, no filesystem access — templates must be on disk via process mode)
- Directory walk discovers every
*.tmplundertemplate_dir - Filename templating — the output path is itself rendered as a template
- Partials — files whose base name starts with
_are parsed but not emitted - Cross-file
{{ template "name" . }}includes - Sprig v3 function library
- Naming helpers:
camelCase,pascalCase,snakeCase,kebabCase,screamingSnake,singular,plural,camelize,goNormalize, … - Language type helpers:
goType,pyType,tsType,rustType,kotlinType,cppType,goZeroValue - Proto navigation:
findTable,findEnum,queriesByCmd,hasColumn,columnComment,option,optionOr - Per-render scratch store:
setStore/getStore - Language-agnostic — template data is the raw sqlc protobuf; no pre-computed enriched model
sqlc's WASM sandbox has no filesystem access, so reading templates from disk requires running the plugin as a native process.
Go projects — add the binary as a go tool dependency:
go get -tool github.com/sqlc-contrib/sqlc-gen-template/cmd/sqlc-gen-template@latestThen in sqlc.yaml:
version: "2"plugins:
- name: templateprocess:
cmd: "go tool sqlc-gen-template"Other projects — install the binary with Go and reference it by path:
go install github.com/sqlc-contrib/sqlc-gen-template/cmd/sqlc-gen-template@latestversion: "2"plugins:
- name: templateprocess:
cmd: "sqlc-gen-template"WASM mode is supported for distribution convenience (no local binary needed),
but because sqlc's WASM sandbox provides no filesystem access, the plugin
cannot read template files from disk in this mode. template_dir will
always fail with EBADF. Only use WASM mode if you have a use case that does
not require disk-based templates.
version: "2"plugins:
- name: templatewasm:
url: https://github.com/sqlc-contrib/sqlc-gen-template/releases/download/v0.1.1/sqlc-gen-template.wasmsha256: <sha256 from the release assets>sql:
- engine: postgresqlschema: db/schema.sqlqueries: db/queries.sqlcodegen:
- plugin: templateout: ./genoptions:
template_dir: ./templates # requiredextra: # free-form; surfaced as .Optionspackage: dbemit_json_tags: true| Option | Type | Required | Description |
|---|---|---|---|
template_dir | string | yes | Directory (relative to sqlc.yaml) walked for *.tmpl files |
extra | object | no | Arbitrary key/value map surfaced to templates as .Options |
Unknown top-level options are rejected.
- Every file under
template_dirwith a.tmplsuffix is parsed. - All templates are loaded into a single template set, so
{{ template "some-other-file.tmpl" . }}works across files. - Files whose base name starts with
_are partials — parsed but never emitted as output files. - For each non-partial template, the output path is computed as the template's
path relative to
template_dir, with the.tmplsuffix stripped. That path is itself executed as a template, so it can depend on.Options, range contexts, etc.
Example layout:
templates/
_header.tmpl # partial (not emitted)
models.go.tmpl # → gen/models.go
{{ .Options.package }}/schema.sql.tmpl # → gen/db/schema.sql (when .Options.package = "db")
The root value (.) is:
typeContextstruct {
Request*plugin.GenerateRequest// raw sqlc protobufOptionsmap[string]any// the `extra` map from sqlc.yamlTemplateDirstring// the resolved templates directorySqlcVersionstring// hoisted from Request.SqlcVersion
}Useful proto field paths (see the plugin-sdk-go types for the full surface):
| Path | Meaning |
|---|---|
.Request.Settings.Engine | "postgresql" / "mysql" / "sqlite" |
.Request.Catalog.DefaultSchema | Default schema name |
.Request.Catalog.Schemas | []*plugin.Schema |
.Request.Catalog.Schemas[i].Tables | []*plugin.Table |
.Request.Catalog.Schemas[i].Tables[j].Columns | []*plugin.Column |
.Request.Catalog.Schemas[i].Enums | []*plugin.Enum |
.Request.Queries | []*plugin.Query |
.Request.Queries[i].Cmd | ":one", ":many", ":exec", … |
.Request.Queries[i].Params[k].Column | *plugin.Column |
.Column.Type.Name | Database type name (e.g. int4) |
.Column.NotNull, .Column.IsArray, .Column.Unsigned | Column flags |
| Function | Example input → output |
|---|---|
camelCase s | user_id → userID |
pascalCase s | user_id → UserID |
snakeCase s | UserID → user_id |
kebabCase s | UserID → user-id |
screamingSnake s | UserID → USER_ID |
upperFirst s | foo → Foo |
lowerFirst s | Foo → foo |
goNormalize s | 1st_user-id → _1stUserID |
singular s | users → user (via go-openapi/inflect) |
plural s | user → users |
camelize s | user_id → UserId |
camelCase, pascalCase, goNormalize preserve common acronyms
(ID, URL, URI, UUID, API, HTTP, JSON, XML, SQL, DB, IP,
TCP, UDP, TLS, SSL).
| Function | Description |
|---|---|
findTable req schema name | Returns the named *plugin.Table (empty schema = any schema) |
findEnum req schema name | Returns the named *plugin.Enum |
queriesByCmd cmd queries | Filters queries by Cmd (e.g. ":one") |
hasColumn table name | Reports whether table has a column named name |
columnComment col | Returns the column's comment, stripped of the leading -- |
option key options | Looks up a key in the extra map (returns nil if missing) |
optionOr key fallback options | Like option, but returns fallback when the key is missing |
Example:
{{ $users := findTable .Request "public" "users" }}
{{ range $users.Columns }}{{ .Name }}: {{ goType . }}
{{ end }}
Each helper takes a single *plugin.Column and returns the column's type in
the target language, honouring NotNull, IsArray, and (where meaningful)
Unsigned.
| Helper | Returns | Array wrap | Nullable wrap |
|---|---|---|---|
goType col | Go type | []T | *T (scalars) |
goZeroValue col | Go zero value | — | nil |
pyType col | Python type | List[T] | Optional[T] |
tsType col | TypeScript type | T[] | T | null |
rustType col | Rust type | Vec<T> | Option<T> |
kotlinType col | Kotlin type | List<T> | T? |
cppType col | C++ type | std::vector<T> | std::optional<T> |
Covered SQL types include bool, int2/int4/int8 (and their smallint/
integer/bigint/serial aliases), tinyint/mediumint, float4/float8,
numeric/decimal, text/varchar/char/citext, uuid, bytea/blob,
date/time/timestamp/timestamptz, json/jsonb. Unknown types fall
through to each language's catch-all (any, Any, unknown,
serde_json::Value, Any, std::string).
Need a project-specific override? Use optionOr to let template authors pass
type overrides through extra:
{{ $go := optionOr (printf "types.%s" .Type.Name) (goType .) .Options }}
setStore key value writes to a per-render map[string]any and returns an
empty string (so it is safe to call from an action). getStore key reads
back. Useful for collecting imports or accumulating state across templates
in a single render.
{{ setStore "imports" (list "time" "database/sql") }}
...
{{ range getStore "imports" }}import "{{ . }}"
{{ end }}
The full sprig v3TxtFuncMap is
available. Where sprig and this plugin define the same name (e.g.
snakecase/snakeCase), the plugin's helpers take precedence — sprig
functions are registered first and overwritten by the naming, navigation,
language, and store helpers.
{{- /* templates/{{ .Options.package }}/models.go.tmpl */ -}}
package {{ .Options.package }}
{{ range .Request.Catalog.Schemas }}
{{- range .Tables }}
type {{ pascalCase .Rel.Name | singular }} struct {
{{- range .Columns }}
{{ pascalCase .Name }} {{ goType . }} `db:"{{ .Name }}"`
{{- end }}
}
{{ end }}
{{- end }}
- Use process mode for
template_dir. sqlc's WASM sandbox provides no filesystem access;os.Stat/filepath.WalkDirreturnEBADFinside a WASM plugin.template_dironly works when the plugin runs as a native process. See the Installation section. - No
formatter_cmd. The plugin cannot spawn subprocesses. Format the generated output yourself (go fmt ./...,prettier --write,rustfmt, …). - Raw proto surface. Template data is the sqlc SDK protobuf. The plugin pins a specific SDK version; field paths may shift across SDK bumps.
nix develop
go tool ginkgo run -r -coverprofile=coverage.out -covermode=atomic ./...Build the WASM artifact locally:
nix build .#wasm
sha256sum result/bin/sqlc-gen-template.wasm