Skip to content

Repository files navigation

sqlc-gen-template

CIReleaseLicenseGosqlc

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.

Features

  • 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 *.tmpl under template_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

Installation

Process mode (recommended — required for template_dir)

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@latest

Then 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@latest
version: "2"plugins:
- name: templateprocess:
cmd: "sqlc-gen-template"

WASM mode

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>

Configuration

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
OptionTypeRequiredDescription
template_dirstringyesDirectory (relative to sqlc.yaml) walked for *.tmpl files
extraobjectnoArbitrary key/value map surfaced to templates as .Options

Unknown top-level options are rejected.

Template discovery & output

  • Every file under template_dir with a .tmpl suffix 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 .tmpl suffix 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")

Template context

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):

PathMeaning
.Request.Settings.Engine"postgresql" / "mysql" / "sqlite"
.Request.Catalog.DefaultSchemaDefault 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.NameDatabase type name (e.g. int4)
.Column.NotNull, .Column.IsArray, .Column.UnsignedColumn flags

Template function reference

Naming

FunctionExample input → output
camelCase suser_iduserID
pascalCase suser_idUserID
snakeCase sUserIDuser_id
kebabCase sUserIDuser-id
screamingSnake sUserIDUSER_ID
upperFirst sfooFoo
lowerFirst sFoofoo
goNormalize s1st_user-id_1stUserID
singular susersuser (via go-openapi/inflect)
plural suserusers
camelize suser_idUserId

camelCase, pascalCase, goNormalize preserve common acronyms (ID, URL, URI, UUID, API, HTTP, JSON, XML, SQL, DB, IP, TCP, UDP, TLS, SSL).

Proto navigation

FunctionDescription
findTable req schema nameReturns the named *plugin.Table (empty schema = any schema)
findEnum req schema nameReturns the named *plugin.Enum
queriesByCmd cmd queriesFilters queries by Cmd (e.g. ":one")
hasColumn table nameReports whether table has a column named name
columnComment colReturns the column's comment, stripped of the leading --
option key optionsLooks up a key in the extra map (returns nil if missing)
optionOr key fallback optionsLike option, but returns fallback when the key is missing

Example:

{{ $users := findTable .Request "public" "users" }}
{{ range $users.Columns }}{{ .Name }}: {{ goType . }}
{{ end }}

Language type helpers

Each helper takes a single *plugin.Column and returns the column's type in the target language, honouring NotNull, IsArray, and (where meaningful) Unsigned.

HelperReturnsArray wrapNullable wrap
goType colGo type[]T*T (scalars)
goZeroValue colGo zero valuenil
pyType colPython typeList[T]Optional[T]
tsType colTypeScript typeT[]T | null
rustType colRust typeVec<T>Option<T>
kotlinType colKotlin typeList<T>T?
cppType colC++ typestd::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 }}

Scratch store

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 }}

Sprig

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.

Writing templates

{{- /* 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 }}

Caveats

  • Use process mode for template_dir. sqlc's WASM sandbox provides no filesystem access; os.Stat / filepath.WalkDir return EBADF inside a WASM plugin. template_dir only 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.

Development

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

License

MIT

About

Turn your SQL schema into any code — a sqlc plugin that renders Go templates with Sprig, naming helpers, and language-aware type mappers (Go, Python, TypeScript, Rust, Kotlin, C++).

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages