Learn how to extend descriptor.proto to add custom annotations to files, messages, fields, services, and methods, and how to read them at runtime.
Custom options let you attach user-defined metadata to any part of a .proto file — files, messages, fields, enums, enum values, services, and methods. They are implemented as extensions of the *Options messages defined in descriptor.proto.
Every part of a .proto file has a corresponding options message (FileOptions, MessageOptions, FieldOptions, etc.) defined in google/protobuf/descriptor.proto. These messages reserve extension ranges specifically so you can add your own fields.To define a custom option, you extend the appropriate options message and then apply the extension in your .proto file using the (option_name) syntax.
Extension numbers for custom options must not conflict with other options in the same options message. The ranges are:
Range
Purpose
50000–99999
Private use: single application or organization, or experimental options.
1000–49999
Reserved by Google for official protobuf extensions.
Other ranges
Contact [email protected] to reserve numbers for publicly-shared options.
For options that will only be used within a single codebase, choosing a number in the 50000–99999 range is sufficient. To avoid collisions when sharing options with other teams, use a sub-message to pack multiple options under a single extension number:
At runtime, custom options are accessible through the reflection API. Each options message exposes its extensions through the descriptor system.
C++
Java
Python
#include "google/protobuf/descriptor.h"#include "myproject/options.pb.h"void InspectOptions(const google::protobuf::Descriptor* descriptor) { const google::protobuf::MessageOptions& options = descriptor->options(); if (options.HasExtension(myproject::sensitive_data)) { bool is_sensitive = options.GetExtension(myproject::sensitive_data); std::cout << "sensitive_data = " << is_sensitive << std::endl; } for (int i = 0; i < descriptor->field_count(); ++i) { const google::protobuf::FieldDescriptor* field = descriptor->field(i); const google::protobuf::FieldOptions& field_opts = field->options(); if (field_opts.HasExtension(myproject::max_length)) { uint32_t max = field_opts.GetExtension(myproject::max_length); std::cout << field->name() << " max_length=" << max << std::endl; } }}
import com.google.protobuf.Descriptors.Descriptor;import com.google.protobuf.Descriptors.FieldDescriptor;import myproject.Options;void inspectOptions(Descriptor descriptor) { if (descriptor.getOptions().hasExtension(Options.sensitiveData)) { boolean isSensitive = descriptor.getOptions() .getExtension(Options.sensitiveData); System.out.println("sensitive_data = " + isSensitive); } for (FieldDescriptor field : descriptor.getFields()) { if (field.getOptions().hasExtension(Options.maxLength)) { int maxLen = field.getOptions().getExtension(Options.maxLength); System.out.println(field.getName() + " max_length=" + maxLen); } }}
from myproject import options_pb2def inspect_options(descriptor): opts = descriptor.GetOptions() if opts.HasExtension(options_pb2.sensitive_data): print(f"sensitive_data = {opts.Extensions[options_pb2.sensitive_data]}") for field in descriptor.fields: field_opts = field.GetOptions() if field_opts.HasExtension(options_pb2.max_length): max_len = field_opts.Extensions[options_pb2.max_length] print(f"{field.name} max_length={max_len}")
Custom options defined in syntax = "proto2" files are the standard approach. In syntax = "proto3" and editions files, extensions are allowed only on the *Options messages from descriptor.proto. You cannot add extensions to your own proto3 messages.
Custom options can be annotated with feature_support to control their lifetime in relation to editions. This lets you mark an option as deprecated or removed for a specific edition: