---
source_url: https://www.pubnub.com/docs/data-storage/structured-data/projections
title: DataSync projections
updated_at: 2026-09-30T07:20:08.000Z
---

# DataSync projections

## Documentation index

To discover more PubNub resources:

1. Fetch [PubNub's llms.txt](https://www.pubnub.com/llms-full.txt) for a list of available pages in Markdown format.
2. Identify relevant URLs from that index.
3. Fetch the target pages.

Do not assume a path exists, always check the index first.

A projection is a named view over an object's fields. A client's [Access Manager](https://www.pubnub.com/docs/security/access-control/overview.md) 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.

## 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. Set them with [Create a new entity class](https://www.pubnub.com/docs/admin-api/create-a-new-entity-class.md) or [Create a new relationship class](https://www.pubnub.com/docs/admin-api/create-a-new-relationship-class.md), and read them off the class definition with [Get entity class by ID](https://www.pubnub.com/docs/admin-api/get-entity-class-by-id.md) or [Get relationship class by name and version](https://www.pubnub.com/docs/admin-api/get-relationship-class-by-name-and-version.md).

Each property carries its own list of projections. That list defaults to the built-in `__default__` projection alone. A field is broadly visible until you restrict it, and you restrict a field by removing `__default__` from its list and tagging it with a named projection instead.

For example, a Product class can tag `name` and `price` with both `__default__` and `admin`, and tag `cost` with `admin` only. The `__default__` view then contains `name` and `price`, and the `admin` view contains `name`, `price`, and `cost`.

```mermaid
flowchart LR
    CLASS["<b>Product class</b>"]
    BROAD["name and price<br/>default projection, admin"]
    RESTRICTED["cost<br/>admin only"]
    DEFAULT["<b>Default view</b><br/>name, price"]
    ADMIN["<b>admin view</b><br/>name, price, cost"]

    CLASS --> BROAD & RESTRICTED
    BROAD --> DEFAULT & ADMIN
    RESTRICTED --> ADMIN

    class CLASS emphasis
    class BROAD,RESTRICTED muted
```

### 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 uses 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. A broadly visible field can carry several projection tags. A common pattern is to tag sensitive fields with only the restricted projection, while broadly visible fields carry both `__default__` and the restricted projection, making the restricted projection a superset that sees everything plus the sensitive fields.

## 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. Each value is the projection name granted for that resource.

Each object kind uses its own key prefix in this map:

* `datasync:users:` for users
* `datasync:entities:` for generic entities
* `datasync:channels:` for channels
* `datasync:relationships:` for relationships
* `datasync:memberships:` for memberships

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. 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`.

```mermaid
flowchart TB
    REQUEST["<b>Request for an object</b>"]
    EXACT["<b>1. Exact ID in res</b><br/>Use its projection if found"]
    PATTERN["<b>2. Pattern in pat</b><br/>Use its projection if found"]
    DEFAULT["<b>3. Default projection</b><br/>No match found"]

    REQUEST --> EXACT
    EXACT -->|"otherwise"| PATTERN
    PATTERN -->|"otherwise"| DEFAULT

    class EXACT,PATTERN emphasis
```

## What projections enforce

Projections govern reads and writes, and the two 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 with no property definition. `status` is removed unless the class declares a `/status` property tagged with that projection.
* **Reads under __default__**: the response removes declared fields not tagged `__default__`, but keeps payload fields with no property definition.
* **Writes**: naming a field outside the token's resolved projection rejects the whole request with a `403`. DataSync never applies a partial write, so the object is left exactly as it was.
* **Replaces under a named projection**: only replace that projection's payload fields. Payload fields outside the projection keep their stored values, so a restricted client can't destroy data it can't see.
* **Partial updates**: judged on the resulting document, not on the paths they mention. 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` response says only that the `auth` parameter was invalid. It doesn't name the field that was rejected.

## Projections and events

Each change fans out to one event per projection declared on the class. The `__default__` variant publishes to the object's own ID channel with the `__default__` view of the payload. Each named projection publishes to a separate channel named `__<projection>__<id>` carrying that projection's view.

:::warning Projection channels need channel grants
Publishing to `__<projection>__<id>` is an ordinary channel publish, not 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.
:::

If a class declares no properties at all, there is nothing to filter, so a single unfiltered event publishes to the object's own ID channel.

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 directly.

## Designing projections

* Tag broadly visible fields with both `__default__` and a restricted projection so the restricted projection is a superset.
* A class can declare at most 3 projections, including `__default__`. Plan which fields go where before you reach the limit.
* Projections scope events as well as API responses. Each named projection gets its own event channel. Grant subscribe on those channels as carefully as you grant the projection itself.
* 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__`.

Last updated at: 2026-09-30T07:20:08.000Z
