Protocol Buffers (Protobuf) Schema Generation

This section explains how the writer builds the .proto schema from FME features, detailing the metadata that drives the output and the options available for configuration. The focus is exclusively on defining the .proto schema structure, not on the subsequent data writing process.

Quick Links

.proto File Creation Process

Mapping FME Attributes to Protobuf Types

Required Attributes for Protobuf Feature Types

Supported Protobuf Metadata Attributes

Version-Specific Behavior

Output File and Structure

.proto File Creation Process

1.

Determine whether to generate a new .proto file or use an existing schema.

If Generate Protobuf Schema is set to Yes, the writer collects incoming features and builds the new .proto file.

If an existing schema is supplied, .proto file creation is bypassed.

2.

Features are scanned to determine the schema.

The writer reads per-feature attributes and metadata (file options, message/field details, enums, extension ranges, services). Some missing details are filled using safe defaults where supported. When metadata options conflict, the value encountered last will take precedence.

3.

The writer finalizes and outputs the .proto schema file.

The writer picks the Protobuf version (Proto2, Proto3, or Editions 2023), unless a specific version is selected, ensures any required file-level options are set, and finalizes field numbering and root handling. The final <package>.proto file is written, containing imports, packages, options, messages (with nesting), enums, and extension ranges.

Field options and labels will vary based on the final Protobuf version selected (see Version-Specific Behavior).

Mapping FME Attributes to Protobuf Types

This section explains the two modes the writer uses to convert FME attributes into Protobuf field definitions.

Without Metadata (Default Type Mapping)

If no specific Protobuf metadata is provided for an FME attribute, the writer defaults to mapping the basic FME attribute type to a standard Protobuf scalar type.

FME Type

Protobuf Type

string string
bool bool
int8 int32
uint8 uint32
int16 int32
uint16 uint32
int32 int32
uint32 uint32
real32 float
real64 double
real80 Not Supported
encoded_string string
int64 int64
uint64 uint64

With Explicit Metadata (Custom Type Definition)

If Protobuf metadata attributes are provided (see Required Attributes for Protobuf Feature Types), the writer uses them to build custom Protobuf structures, including enums, message references, maps, cardinality, defaults, and field options.

If mandatory metadata is missing, the writer issues warnings and downgrades behavior as described under Required Attributes for Protobuf Feature Types.

The Protobuf metadata must be consistent with the metadata attributes generated when the reader parameter Create Field Metadata Attributes is enabled.

Required Attributes for Protobuf Feature Types

If an item in the table is listed as required, it is required only when the attribute is necessary to successfully create that specific Protobuf structure. The writer will otherwise use a simpler default type (for example, a scalar type) rather than fail.

Protobuf Structure

Required FME Attributes

Details

Message Fields

_[fieldName].type=message

_[fieldName].submessage_type_name

If _[fieldName].type is missing, the default type is string.

Example:

_MyField.type = message

_MyField.submessage_type_name = MyPackage.MyMessage

Generated Result:

Message MyParent {

MyMessage MyField = 1;

}

Enum Fields

_[fieldName].type=enum

_[fieldName].enum_value{N}

Optional:

_[fieldName].enum_type_name

If _[fieldName].enum_type_name is used by different messages, the definition will be hoisted to the file level to prevent repeated nested definitions. Definition may be hoisted to the file level if multiple parent messages share the same enum.

Matching enums are determined by matching enum_type_name and all enum_values{} must be the same.

Example:

_MyField.type = enum

_MyField.enum_type_name = MyEnum

_MyField.enum_value{0} = First

_MyField.enum_value{1} = Second

Generated Result:

Message MyParent {

MyEnum{

First = 1;

Second = 2;

}

MyEnum MyField = 1;

}

Map Fields

_[fieldName].type=message

_[fieldName].is_map=true

_[fieldName].submessage_type_name

Example:

Feature containing the map field:

_MyField.type = message

_MyField.is_map = true

_MyField.submessage_type_name = MyMapEntry

The following feature called MyMapEntry, containing key/value attributes must exist. See section on typing with and without metadata for how key/value types are resolved. Note the word Entry is stripped from the submessage_type_name.

Generated Result:

Message MyParent {

MyMap<keyType, valueType> MyField = 1;

}

Extension Ranges

_[FeatureType].extension_range{N}.start _[FeatureType].extension_range{N}.end

Example:

_MyParent.extension_range{0}.start = 1000

_MyParent.extension_range{0}.end = 1999

_MyParent.extension_range{1}.start = 3999

_MyParent.extension_range{1}.end = 4999

Generated Result:

Message MyParent {

extensions 1000 to 1999;

extensions 3999 to 4999;

}

Note  
  • Extension ranges will be merged if they overlap.
  • 19000 to 19999 are protected ranges and will not be written.
  • Proto3 does not support extension ranges, and will not be written.

Field Numbering

_[fieldName].number

Example:

_MyField.number = 5

Generated Result:

Message MyParent {

String MyField = 5;

}

If no field number is present, the writer will auto-assign one. If field numbers within a message conflict, the first-to-use will gain priority, and subsequent conflicts will be assigned the next available field number. Cascading field number conflicts will not occur

Cardinality

_[fieldName].cardinality

Options:

Required or Optional

IMPLICIT is not yet supported.

Example:

_MyField.cardinality = OPTIONAL

_MyField2.cardinality c= REQUIRED

Generated Result:

Message MyParent {

Optional String MyField = 1;

Required String MyField2 = 2;

}

Supported Cardinality Cheatsheet:

  • Proto2: REQUIRED or OPTIONAL
  • Proto3: OPTIONAL
  • Editions 2023: Not Supported

Defaults

_[fieldName].default

Example:

_MyField.default = “Hello”

Generated Result:

Message MyParent {

String MyField = 1; [Default = “Hello”]

}

Supported Protobuf Metadata Attributes

This section lists the metadata attributes that can be set on FME features to control the generated Protobuf schema.

File / package / imports

Used to write the file header (syntax or edition) and package line

  • Protobuf_edition
  • Protobuf_package
  • Protobuf_syntax

Language-specific options

  • Protobuf_csharp_namespace
  • Protobuf_go_package
  • Protobuf_java_outer_classname
  • Protobuf_java_package
  • Protobuf_objc_class_prefix
  • Protobuf_php_class_prefix
  • Protobuf_php_metadata_namespace
  • Protobuf_php_namespace
  • Protobuf_ruby_package
  • Protobuf_swift_prefix

See Protobuf Documentation for usage examples.

Boolean options

  • Protobuf_cc_enable_arenas
  • Protobuf_cc_generic_services
  • Protobuf_deprecated
  • Protobuf_java_generic_services
  • Protobuf_java_multiple_files
  • Protobuf_java_string_check_utf8
  • Protobuf_py_generic_services

Optimize option

Protobuf_optimize_for (SPEED, CODE_SIZE, or LITE_RUNTIME).

Note  All files that import a file using LITE_RUNTIME must also use LITE_RUNTIME. This will not be resolved automatically by the writer.

Editions FeatureSet options

  • Protobuf_features_enum_type
  • Protobuf_features_field_presence
  • Protobuf_features_json_format
  • Protobuf_features_message_encoding
  • Protobuf_features_repeated_field_encoding
  • Protobuf_features_utf8_validation

Well‑Known Types (WKT)

When a field references a google.protobuf.* type (for example, Timestamp, wrapper types), the writer keeps the fully-qualified name and adds the correct import google/protobuf/… automatically. These are standard imports; they are not redefined in the generated file.

Packages

Use Protobuf_package to set the protobuf file package.

Important  This is strongly recommended by Protobuf standards and is important for proper schema generation.

Creating a unique package name enables the following:

  • Nested Messages
  • Message Grouping by Package
    • A .proto file will be created for each package, and all messages belonging to that package will be grouped.

Messages and Nesting

  • Messages are created for each feature type and can be nested by interpreting dotted feature type names as parent/child relationships. Nested messages are emitted inside their parents. Protobuf_package must be populated to correctly achieve nested messages.
  • Enums first defined inside a message can be “hoisted” to file scope if the same enum type is needed in more than one message (to avoid duplication).

Fields

Name derivation and “list” detection: attribute names are sanitized to valid Protobuf identifiers.

List attributes are recognized and marked as repeated.

Field names must be alphanumeric, with the exception for underscores.

Field names cannot start with an underscore or a number.

Field options supported via metadata include:

  • ctype
  • jstype
  • packed
  • deprecated
  • debug_redact
  • retention
  • lazy
  • unverified_lazy
  • weak

Version-Specific Behavior

How the Version is Chosen

If you set Target Proto Version in the UI, the writer enforces that version when emitting the .proto.

Otherwise in Auto, the writer tries to infer a version from the file-level edition/syntax fields and falls back to Editions 2023 by default.

Note  While the writer can be used to migrate between proto versions, it is not a dedicated syntax migration tool like Google’s Prototiller.

Editions 2023

Auto-enable presence: if any field requests OPTIONAL cardinality and the writer is targeting Editions 2023, it ensures features.field_presence=EXPLICIT.

Output File and Structure

Filename

  • <package>.proto

Header Order

  • syntax/edition imports package file options file-level enums messages (with nested enums/messages) per-message extension ranges

Root Handling

If metadata is present, the writer will walk the dependency tree to find a root. A root will be calculated for each Protobuf_package name, in the case of multiple packages (except for Well-Known Type imports) a generated root will always be created:

  • If a singular root is found, then it will be used.
  • If multiple roots are found, the writer will create a wrapping root (FMEGeneratedRoot) that repeats each top-level root message.
  • If no root could be found, the writer will create a wrapping root (FMEGeneratedRoot) that repeats all top-level messages.

If no metadata is present, the writer will create a wrapping root (FMEGeneratedRoot), that repeats all top-level messages, which in this case will be all feature types.