Skip to content

Options

Plugins can declare typed options that users pass via buf.gen.yaml, giving them a structured way to configure code generation behavior.

Plugin options

Define plugin options as a dataclass and pass it as the third argument to run():

protoc-gen-hello
from dataclasses import dataclass
from protobuf.plugin import Schema, run


@dataclass
class Options:
    verbose: bool = False


def generate(schema: Schema[Options]) -> None:
    if schema.options.verbose:
        ...


run("protoc-gen-hello", "0.1.0", Options, generate)

Pass options in buf.gen.yaml as a comma-separated opt: value:

buf.gen.yaml
plugins:
  - local: protoc-gen-hello
    out: src/gen
    opt: verbose=true

Supported field types: str, bool, int, float, Literal[...], StrEnum, IntEnum, list[T], dict[str, T]. | None can be added for optional options.

Framework-reserved options

The framework reserves certain option names for all plugins, currently: no_fmt_off and escape_module_with_hash. If your Options dataclass defines a field with a reserved name, run() raises a ValueError.

no_fmt_off

When set, omits the # fmt: off line from the generated file preamble. Use this when you want ruff to format the generated output:

buf.gen.yaml
plugins:
  - local: protoc-gen-hello
    out: src/gen
    opt: no_fmt_off

escape_module_with_hash

Module names are derived from proto file paths, and characters that aren't valid in a Python module name (such as . or -) are replaced with underscores. That replacement can cause separate proto files to map to the same module name. When set, this option appends a short hash suffix, derived from the original unsanitized name, to avoid those collisions:

buf.gen.yaml
plugins:
  - local: protoc-gen-hello
    out: src/gen
    opt: escape_module_with_hash

Example: Sensitive fields plugin

Here is a plugin that generates a _sensitive.py file for each proto file, listing all fields marked with the sensitive custom option from extensions:

protoc-gen-sensitive
#!/usr/bin/env python3
from protobuf.plugin import Schema, run
from gen.options_pb import ext_sensitive


def generate(schema: Schema) -> None:
    for desc in schema.files_to_generate:
        f = schema.generate_file(desc, "_sensitive.py")
        f.preamble(desc)
        f.print()

        sensitive_fields = [
            (msg, field)
            for msg in desc.messages
            for field in msg.fields
            if field.proto.options is not None and field.proto.options[ext_sensitive]
        ]

        with f.scope("SENSITIVE_FIELDS: list[tuple[str, str]] = ["):
            for msg, field in sensitive_fields:
                f.print(f'("{msg.name}", "{field.name}"),')
        f.print("]")


run("protoc-gen-sensitive", "0.1.0", generate)