Projections in DataSync
A projection is a named view over an object's fields. A client's Access Manager token determines which view it gets when it reads or writes that object. Projections aren't a separate permission system. They live inside the same Access Manager tokens that already authorize the rest of PubNub.
In Bob's marketplace, Alice's user profile has fields everyone in the sale channel may see, like display_name and avatar_url, and fields only support staff should see, like email and phone. Projections let you draw that line at the field level.
How projections work
A projection is a named tag declared on a property definition, at the class level. A single field can belong to several projections.
The projection names you can grant on an object are the ones its class declares, so read them off the class definition with Get entity class by ID or Get relationship class by name and version, and set them with Create a new entity class or Create a new relationship class.
Each property carries its own list of projections, and that list defaults to the built-in __default__ projection alone. So a field is broadly visible until you say otherwise, and you restrict a field by removing __default__ from its list and tagging it with a named projection instead. That's what makes email and phone admin-only in the example below.
Projection limits
A class can declare at most 3 distinct projection names across all of its properties. __default__ counts toward that 3 whenever any property is tagged with it, which is the default, so in practice you have 2 custom names. Each name is at most 64 characters and must match ^[a-zA-Z0-9][a-zA-Z0-9_-]*$. __default__ is the only name allowed to start with __. Exceeding the limit or using an invalid name fails class creation or update with a 400.
Projections overlap rather than partition. Nothing requires one projection to contain another, but a broadly visible field can carry several projection tags, which is how you make a restricted projection a superset that sees everything __default__ sees plus the sensitive fields:
In the diagram, a token holding the admin projection for user-alice reads all four fields. A token that resolves to __default__ reads only display_name and avatar_url, the two fields tagged __default__. A request authorized with a signature instead of a token isn't projection-filtered and reads all four.
Projection resolution with a token
Access Manager tokens carry a pn-projections map inside their meta section. The map holds two nested maps, res for exact resource ids and pat for regular-expression patterns, and each value is the projection name granted for that resource.
Each object kind has its own key prefix. These prefixes are specific to the projections map, and differ from the resource types used for permission grants:
datasync:users:for usersdatasync:entities:for generic entitiesdatasync:channels:for channelsdatasync:relationships:for relationshipsdatasync:memberships:for memberships
{
"meta": {
"pn-projections": {
"res": {
"datasync:users:user-alice": "admin"
},
"pat": {}
}
}
}
Resolution follows a fixed order: an exact match in res wins first, then a pattern match in pat, and if neither matches, the token falls back to __default__. A token with no pn-projections map also resolves to __default__.
Patterns are anchored regular expressions matched against the whole resource ID, so user-.* matches user-alice but user does not. If several patterns match, which one wins isn't defined, so avoid overlapping patterns. If a token resolves to a projection name that no property on the class declares, the request fails with a 403 error.
Refer to Access control for the full information on tokens, grants, and resource types.
What projections enforce
Projections govern reads and writes, and the two kinds of projection aren't symmetric. __default__ acts as a denylist that lets undeclared payload fields through, while a named projection acts as an allowlist that drops them:
- Reads under a named projection: the response contains only fields tagged with that projection. Anything else is removed, including payload fields that have no property definition.
statusis also removed unless the class declares a/statusproperty tagged with that projection. - Reads under
__default__: the response removes declared fields not tagged__default__, and keeps payload fields that have no property definition. - Writes: naming a field outside the token's resolved projection rejects the whole request with a
403. DataSync never applies part of a rejected write, so the object is left exactly as it was. Under__default__you can also write payload fields that have no property definition. Under a named projection you can't. - Replaces under a named projection only replace that projection's payload fields. Payload fields outside the projection keep their stored values rather than being wiped, so a restricted client can't destroy data it can't see.
statusis checked separately from payload fields. If the projection includes/status, omitting it from a replace clears it. If the projection excludes/status, changing it is rejected. Currently, omitting a stored non-null status is also treated as a change and returns a403.- Partial updates are judged on the resulting document rather than on the paths they mention, so a patch that touches an out-of-projection path but leaves the value unchanged is allowed, while removing an out-of-projection field is rejected.
The 403 says only that the auth parameter was invalid. It doesn't name the field that was rejected.
Projections and events
Projections scope events as well as API responses. Each change fans out to one event per projection declared on the class. The __default__ variant is published to the object's own ID channel with the __default__ view of the payload, and each named projection is published to a separate channel named __<projection>__<id> carrying that projection's view.
Continuing with Bob's marketplace: email and phone belong only to the admin projection, so an update to Alice publishes two events. The user-alice channel carries her __default__ fields, with email and phone withheld. The __admin__user-alice channel carries the admin view, email and phone included.
If a class declares no properties at all, there is nothing to filter, so a single unfiltered event is published to the object's own ID channel.
Projection channels need channel grants
Publishing to __<projection>__<id> is an ordinary channel publish, and it isn't gated by the projection entries in a token. Use Access Manager channel grants to control who can subscribe to projection channels. Anyone who can subscribe there receives the restricted fields.
In an SDK that ships DataSync SDK entities, you select the projection channel with a projection subscription option rather than by naming the prefixed channel. It's the same channel either way, and the same channel grant applies.
Designing projections
- Projections can overlap. A common pattern, as in the
adminprojection above, is to tag sensitive fields with only the restricted projection, while broadly visible fields carry both__default__and the restricted projection, so the restricted projection is a superset that sees everything__default__sees plus the sensitive fields. - Remember the ceiling of 3 projections per class, and that
__default__takes one of the three as soon as any property uses it. In practice that gives you 2 custom projections to model roles. Plan which fields go where before you reach the limit. - Projections apply to events as well as the API, but each named projection gets its own event channel,
__<projection>__<id>. Grant subscribe on those channels as carefully as you grant the projection itself, see Projections and events above. - A token holding a named projection can't write payload fields you haven't declared on the class. If a client needs to store free-form data, leave it under
__default__.