Skip to main content
This page provides a complete grammar reference for the proto3 language. Each section shows the formal grammar production using an EBNF-style notation, followed by examples.
This is a reference document. For a practical introduction to writing .proto files, see the Proto3 language guide.

Lexical elements

letter        = "A" ... "Z" | "a" ... "z"
decimalDigit  = "0" ... "9"
octalDigit    = "0" ... "7"
hexDigit      = "0" ... "9" | "A" ... "F" | "a" ... "f"

ident         = letter { letter | decimalDigit | "_" }
fullIdent     = ident { "." ident }
messageName   = ident
enumName      = ident
fieldName     = ident
oneofName     = ident
mapName       = ident
serviceName   = ident
rpcName       = ident
messageType   = [ "." ] { ident "." } messageName
enumType      = [ "." ] { ident "." } enumName
Reserved keywords: syntax, import, weak, public, package, option, message, enum, service, rpc, returns, oneof, map, extensions, reserved, repeated, optional, required (proto2 only), to, max.
intLit        = decimalLit | octalLit | hexLit
decimalLit    = ( "1" ... "9" ) { decimalDigit }
octalLit      = "0" { octalDigit }
hexLit        = "0" ( "x" | "X" ) hexDigit { hexDigit }

floatLit      = ( decimals "." [ decimals ] [ exponent ]
              | decimals exponent
              | "." decimals [ exponent ] )
              | "inf"
              | "nan"
decimals      = decimalDigit { decimalDigit }
exponent      = ( "e" | "E" ) [ "+" | "-" ] decimals
strLit        = ( "'" { charValue } "'" ) | ( '"' { charValue } '"' )
charValue     = hexEscape | octEscape | charEscape | /[^\0\n\\]/
hexEscape     = '\\' ( "x" | "X" ) hexDigit hexDigit
octEscape     = '\\' octalDigit octalDigit octalDigit
charEscape    = '\\' ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | '\\' | "'" | '"' )
Proto files support two comment styles:
// Single-line comment

/* Multi-line
   comment */

File structure

proto = syntax { topLevelDef | emptyStatement }

topLevelDef = message | enum | extend | service | import | package | option
Every .proto file begins with a syntax declaration.

Syntax statement

syntax = "syntax" "=" ( "'proto3'" | '"proto3"' ) ";"
syntax = "proto3";

Import statement

import = "import" [ "weak" | "public" ] strLit ";"
import "google/protobuf/timestamp.proto";
import public "other/proto/public.proto";
import weak "experimental/feature.proto";
  • public re-exports the imported file: any file that imports this file also transitively imports the public import.
  • weak allows the import to fail silently (used for optional features).

Package statement

package = "package" fullIdent ";"
package com.example.myapp;
The package name is used to avoid name collisions between message types and to generate appropriate namespaces in the target language.

Options

option       = "option" optionName "=" constant ";"
optionName   = ( ident | "(" fullIdent ")" ) { "." ( ident | "(" fullIdent ")" ) }
constant     = fullIdent | ( [ "-" | "+" ] intLit ) | ( [ "-" | "+" ] floatLit )
             | strLit | boolLit | MessageLiteralWithBraces
File-level options appear at the top level, outside any message or service definition:
option java_package = "com.example.myapp";
option java_outer_classname = "MyProtos";
option java_multiple_files = true;
option go_package = "github.com/example/myapp/proto";
option csharp_namespace = "Example.MyApp";
option objc_class_prefix = "MYA";
option optimize_for = SPEED; // SPEED, CODE_SIZE, or LITE_RUNTIME
option cc_enable_arenas = true;
message MyMessage {
  string name = 1 [deprecated = true];
  repeated int32 values = 2 [packed = true];
  bytes data = 3 [ctype = CORD]; // C++ only
}
Custom options use extension syntax with a fully-qualified name in parentheses:
import "google/protobuf/descriptor.proto";

extend google.protobuf.FieldOptions {
  string my_field_option = 51234;
}

message MyMessage {
  string name = 1 [(my_field_option) = "hello"];
}

Message definition

message = "message" messageName messageBody
messageBody = "{" { field | enum | message | option | oneof
                  | mapField | reserved | emptyStatement } "}"

Normal field

field = [ "repeated" | "optional" ] type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
fieldOptions = fieldOption { "," fieldOption }
fieldOption  = optionName "=" constant
fieldNumber  = intLit
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 results_per_page = 3;
  repeated string tags = 4;
  optional string filter = 5;
}

Scalar field types

Proto typeNotesDefault
double64-bit float0
float32-bit float0
int32Variable-length; inefficient for negative numbers0
int64Variable-length; inefficient for negative numbers0
uint32Variable-length unsigned0
uint64Variable-length unsigned0
sint32ZigZag-encoded; efficient for negative numbers0
sint64ZigZag-encoded; efficient for negative numbers0
fixed32Always 4 bytes; efficient if values > 2^280
fixed64Always 8 bytes; efficient if values > 2^560
sfixed32Always 4 bytes, signed0
sfixed64Always 8 bytes, signed0
booltrue or falsefalse
stringUTF-8 or 7-bit ASCII""
bytesArbitrary byte sequenceb""

Oneof

oneof = "oneof" oneofName "{" { option | oneofField | emptyStatement } "}"
oneofField = type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}
At most one field in a oneof can be set at any time. Setting a field in a oneof clears all other fields in the same oneof.

Map field

mapField = "map" "<" keyType "," type ">" mapName "=" fieldNumber
           [ "[" fieldOptions "]" ] ";"
keyType  = "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64"
         | "fixed32" | "fixed64" | "sfixed32" | "sfixed64"
         | "bool"   | "string"
message Project {
  map<string, int32> version_map = 1;
}

Reserved statements

reserved     = "reserved" ( ranges | strFieldNames ) ";"
ranges       = range { "," range }
range        = intLit | ( intLit "to" ( intLit | "max" ) )
strFieldNames = strFieldName { "," strFieldName }
strFieldName  = "'" ident "'" | '"' ident '"'
message Foo {
  // Reserve field numbers 2, 15, and the range 9 through 11.
  reserved 2, 15, 9 to 11;
  // Reserve field names.
  reserved "foo", "bar";
}
If a field number or name is reserved, it cannot be used in the message. This prevents accidental re-use when removing fields.

Enum definition

enum = "enum" enumName enumBody
enumBody = "{" { option | enumField | reserved | emptyStatement } "}"
enumField = ident "=" [ "-" ] intLit [ "[" enumValueOption { "," enumValueOption } "]" ] ";"
enumValueOption = optionName "=" constant
enum Status {
  option allow_alias = true;
  STATUS_UNKNOWN = 0;
  STATUS_STARTED = 1;
  STATUS_RUNNING = 1; // Alias for STATUS_STARTED
  STATUS_FINISHED = 2;
}
  • The first enum value must be zero in proto3.
  • allow_alias = true permits multiple enum values to share the same number.

Service definition

service = "service" serviceName "{" { option | rpc | emptyStatement } "}"
rpc     = "rpc" rpcName "(" [ "stream" ] messageType ")"
          "returns" "(" [ "stream" ] messageType ")"
          ( ( "{" { option | emptyStatement } "}" ) | ";" )
service SearchService {
  // Unary RPC
  rpc Search (SearchRequest) returns (SearchResponse);

  // Server streaming RPC
  rpc ListResults (SearchRequest) returns (stream SearchResponse);

  // Client streaming RPC
  rpc BatchSearch (stream SearchRequest) returns (SearchResponse);

  // Bidirectional streaming RPC
  rpc BidiSearch (stream SearchRequest) returns (stream SearchResponse);
}

Field numbers

Field numbers must be unique within a message. The valid range is 1 to 536,870,911 (2^29 - 1), but numbers 19000 through 19999 are reserved for the Protocol Buffers implementation.
RangeNotes
1 – 15Encode in one byte (use for frequently used fields).
16 – 2047Encode in two bytes.
2048 – 536,870,911Valid but use larger encoding.
19000 – 19999Reserved for the Protocol Buffers implementation. Do not use.

Build docs developers (and LLMs) love