Skip to content

@protoutil/protoc-gen-sql

A protoc plugin that generates SQL schema and CRUD query files from annotated protobuf definitions. Define your entities once in proto, generate schema for multiple database engines from a single source of truth.

Terminal window
npm install -g @protoutil/protoc-gen-sql

Add the options proto as a dependency in your buf.yaml:

deps:
- buf.build/protoutil/sql

1. Annotate your proto messages

syntax = "proto3";
package myapp.v1;
import "protoutil/sql/v1/options.proto";
message User {
option (protoutil.sql.v1.message) = {
generate: true
indexes: ["email"]
};
string name = 1 [(protoutil.sql.v1.field).omit = true]; // derived at app layer
string email = 2 [(protoutil.sql.v1.field).unique = true];
string bio = 3 [(protoutil.sql.v1.field).nullable = true];
bool active = 4;
}

2. Configure buf.gen.yaml

version: v2
inputs:
- directory: proto
plugins:
- local: protoc-gen-sql
out: generated/postgres
opt:
- engine=postgres
- local: protoc-gen-sql
out: generated/sqlite
opt:
- engine=sqlite

3. Generate

Terminal window
buf generate

This produces:

generated/
├── postgres/
│ ├── schema.sql
│ └── queries/generated/
│ └── myapp_v1_users.sql
└── sqlite/
├── schema.sql
└── queries/generated/
└── myapp_v1_users.sql

Passed via the opt: key in buf.gen.yaml. Each invocation targets one engine.

OptionRequiredValuesDefaultDescription
engineYespostgres mysql sqlite sqlserverTarget database engine
repeated_strategyjson_column array_columnjson_columnHow repeated fields are stored
oneof_strategynullable_columns type_column json_columnnullable_columnsDefault strategy for oneof groups
if_not_existstrue falsetrueEmit IF NOT EXISTS in DDL
emit_create_schematrue falsefalseEmit CREATE SCHEMA IF NOT EXISTS
FieldTypeDefaultDescription
generateboolfalseOpt this message into generation
table_namestringFQN snake_caseOverride the generated table name
indexesrepeated string[]Composite indexes as comma-separated column names
unique_constraintsrepeated string[]Composite unique constraints
skip_queriesrepeated QueryType[]Suppress specific generated queries
foreign_keysrepeated ForeignKey[]Explicit FK column declarations
timestampsTimestampBehaviorBOTHControls created_at / updated_at injection
extra_columnsrepeated FieldOptions[]Additional columns with no proto field
primary_keyPrimaryKeyTypePRIMARY_KEY_TYPE_SERIALStorage type for the id primary key column
FieldTypeDefaultDescription
column_namestringfield nameOverride the column name
column_typestringengine defaultOverride the SQL type
type_overridesmap<string, string>{}Per-engine type overrides
nullableboolfalseEmit NULL instead of NOT NULL
defaultstringSQL DEFAULT expression
indexboolfalseAdd a single-column index
uniqueboolfalseAdd a UNIQUE constraint
omitboolfalseExclude this field entirely
enum_strategyEnumStrategyENUM_STRATEGY_NAMEHow to store a proto enum field
checkstringInline CHECK constraint
skip_queriesrepeated QueryType[]Suppress this column from specific queries

Default proto scalar to SQL type per engine:

Proto typepostgresmysqlsqlitesqlserver
stringTEXTTEXTTEXTNVARCHAR(MAX)
boolBOOLEANTINYINT(1)INTEGERBIT
int32INTEGERINTINTEGERINT
int64BIGINTBIGINTINTEGERBIGINT
uint32BIGINTBIGINT UNSIGNEDINTEGERBIGINT
uint64NUMERIC(20,0)BIGINT UNSIGNEDINTEGERDECIMAL(20,0)
floatREALFLOATREALREAL
doubleDOUBLE PRECISIONDOUBLEREALFLOAT
bytesBYTEABLOBBLOBVARBINARY(MAX)
Proto typepostgresmysqlsqlitesqlserver
google.protobuf.TimestampTIMESTAMPTZDATETIME(6)TEXTDATETIMEOFFSET
google.protobuf.DurationBIGINTBIGINTINTEGERBIGINT
google.protobuf.StructJSONBJSONTEXTNVARCHAR(MAX)
ValueGenerated query
QUERY_TYPE_GETSELECT ... WHERE id = $1 LIMIT 1
QUERY_TYPE_LISTSELECT ... ORDER BY id
QUERY_TYPE_CREATEINSERT INTO ...
QUERY_TYPE_UPDATEUPDATE ... SET ... WHERE id = $1
QUERY_TYPE_DELETEDELETE ... WHERE id = $1
QUERY_TYPE_ALLSuppresses all query generation
Valuecreated_atupdated_at
BOTHInjectedInjected
CREATED_ONLYInjectedOmitted
UPDATED_ONLYOmittedInjected
NONEOmittedOmitted
ValueBehavior
NULLABLE_COLSOne nullable column per variant + CHECK constraint
TYPE_COLUMNDiscriminator column + single value column
JSON_COLUMNEntire oneof serialized to a single JSON column
ValueBehavior
ENUM_STRATEGY_NAMEStore the enum value name as TEXT
ENUM_STRATEGY_INTStore the integer value as INTEGER
ENUM_STRATEGY_CHECK_CONSTRAINTStore as TEXT with CHECK constraint
ENUM_STRATEGY_NATIVE_TYPEUse native database enum type (Postgres only)
ValueBehavior
PRIMARY_KEY_TYPE_SERIALEngine serial integer
PRIMARY_KEY_TYPE_UUIDDatabase-generated UUID
RuleDescription
V01foreign_key.column must not be empty
V02foreign_key.references_table must not be empty
V03FK column must not be a reserved column
V04FK column must not duplicate another column
V05extra_columns entry must have a non-empty column_name
V06extra_columns entry must have a non-empty column_type or matching type_overrides
V07extra_columns entry must not have omit = true
V08extra_columns column_name must not be a reserved column
V09extra_columns column_name must not duplicate another column
V10Indexed column names must exist in the table
V11Unique constraint column names must exist in the table
V12Field column_name override must not be a reserved column
V13Field column name must not duplicate another column
V14ENUM_STRATEGY_NATIVE_TYPE requires engine=postgres
V15type_overrides keys must be valid engine names