Firestore Rules to restrict write access to a specific field in a document

I believe something along the following would work, it allows clients to update fields except for charge, as well as create documents that don't have the charge field.

service cloud.firestore {
  match /databases/{database}/documents {
    function valid_create() {
        return !(request.resource.data.keys().hasAll(["charge"]));
    }

    function valid_update() {
        return request.resource.data.charge == resource.data.charge
               || (valid_create()
                  && !(resource.data.keys().hasAll(["charge"])))
    }

    match /payments/{userId} {
        allow read: if request.auth.uid == userId;
        allow create: if request.auth.uid == userId
                        && valid_create(); 
        allow update: if request.auth.uid == userId
                        && valid_update(); 
    }
  }
}

The accepted answer does not work on update, in which request.resource.data returns a new map that's already merged with existing data. Because of that, request.resource.data.keys().hasAll([<<field>>]) always return true.

Regardless, what works for me is using .diff():

function meta_is_modified() {
      return 'meta' in resource.data.diff(request.resource.data).affectedKeys();
    }

Here's another version with multiple protected fields:

function invalid_modification() {
  return resource.data.diff(request.resource.data).affectedKeys().hasAny(['field1', 'field2'].toSet());
}

Set type was announced, along with some other cool stuff.


Using Sets to ensure that a document only has fields "a", "b", and "c":

request.resource.data.keys().toSet() == ["a", "b", "c"].toSet()

Similarly, you could make sure that a document only has specified keys, but not others:

(request.resource.data.keys().toSet() - ["required","and","opt","keys"].toSet()).size == 0?`