General guidelines:
required fields (always use optional or repeated), as required fields can never be safely deprecated or omitted without breaking deserialization on older clients.Some specific guidelines on top of the general ones above, or just things that often come up:
Avoid using “explicit version numbers” in your protobuf; instead, have your code test for the existence of a field to determine what to do. Protobufs were written precisely to avoid version-specific logic, and testing for fields is more robust. (See Protection against data override by old Sync clients for the rare exception where a version field helps detect stale carried-over fields.)
Enum entries should be in ALL_CAPS (different from C++!), and for new code, the first entry should be a FOO_UNSPECIFIED = 0 one.
proto2, when an older client parses an unrecognized enum value written by a newer client, the raw integer is placed in unknown_fields() and the field getter returns the default value (the first enum entry, unless [default = ...] is specified). Never use a meaningful enum value as the default, or older clients will misinterpret any newly added enum value as that state.FOO_UNSPECIFIED (never CHECK or NOTREACHED()), since older clients will observe FOO_UNSPECIFIED when receiving enum values introduced in newer versions.Timestamp fields must specify their epoch and unit in a suffix, in this format: _[unix|windows]_epoch_[seconds|millis|micros|nanos], e.g creation_time_unix_epoch_millis.
Similarly, duration fields must specify their unit as _[minutes|seconds|millis|...].
Proto changes also require corresponding changes in proto_visitors.h and (where appropriate) in proto_enum_conversions.h/cc.
Backwards compatibility: In general, all changes must be fully backwards-compatible - consider that a single user might be running different versions of the browser simultaneously! Also note that Sync supports clients up to a few years old, so deprecating/removing an existing field is typically a multi-year process.
Adding fields:
TrimAllSupportedFieldsFromRemoteSpecifics(), wrap the specifics message in // LINT.IfChange(...) and // LINT.ThenChange(//path/to/bridge.cc:TrimAllSupportedFieldsFromRemoteSpecifics) to keep the proto and trimming logic in sync.oneof (e.g. for entity subtypes) where new variants may be added in the future, note that older clients will see the oneof as NOT_SET when receiving a newer variant; bridge code must handle NOT_SET gracefully without crashing or dropping sync metadata tracking.Deprecating fields:
[deprecated = true]. This is the common case, since the browser typically needs to continue supporting the old field for backwards compatibility reasons.reserved entries for both its name and its tag number. Note that Sync protos are imported into the server-side codebase, so any server-side usages must be cleaned up first to avoid breaking the next proto import.TrimAllSupportedFieldsFromRemoteSpecifics()), since they should still be treated as supported for the purpose of trimming. (Otherwise, the removed fields would forever be carried forward in the data.)Deprecating enum values: This is particularly tricky, especially if the default value is not a FOO_UNSPECIFIED one (see above). A common pattern is prepending DEPRECATED_ to the entry name.
For reviewers: