On this page

DataSync API for JavaScript SDK

DataSync is PubNub's data layer for storing application state (users, channels, memberships, and any custom object type) and keeping every connected client current through real-time events. Use it to model the objects your application works with and to react the moment they change.

DataSync is the successor to App Context.

DataSync entities and SDK entities are different things

This page is about DataSync entities, the records the service stores and treats as the source of truth for your application state. They are created and read through the pubnub.dataSync.* methods documented here.

An SDK entity is a local client-side handle such as pubnub.channel('room-1'). It carries no stored state of its own, it just gives you a scoped surface for subscribing and calling APIs. Refer to SDK entities in Publish and subscribe.

The two meet in the DataSync SDK entities: SDK entities whose only job is to subscribe to a DataSync object's real-time updates.

The classes that your objects conform to (their types and schemas) are defined through the Admin API or the Admin Portal, not through this SDK:

  • Entity classes, which back users, channels, and custom entities: list, read, create, replace, and delete. Partial updates aren't implemented; replace the complete class version instead.
  • Relationship classes, which back memberships and custom relationships: list, read, create, replace, and delete.

A class definition is what decides, for every object of that class, which payload fields are filterable and sortable, which projection each field belongs to, and how long the object lives before it expires. Refer to managing classes for more information.

Every response includes the HTTP status. The remove* methods return the status on its own, every other method adds a data key holding the object, or the array of objects for a list method, and can add a links object with the HATEOAS URLs the service provides (self, and next where it applies). A stored object carries its free-form status and its expiresAt auto-deletion timestamp (ISO 8601) whenever those are set on it.

Every DataSync request must be authorized with a token or signature. A request with no credential fails with a 401; an invalid credential or a credential that doesn't permit the operation fails with a 403.

Supported and recommended asynchronous patterns

PubNub supports Callbacks, Promises, and Async/Await for asynchronous JS operations. The recommended pattern is Async/Await and all sample requests in this document are based on it. This pattern returns a status only on detecting an error. To receive the error status, you must add the try...catch syntax to your code.

Requires Access Manager

DataSync requires that the Access Manager add-on is enabled for your key in the Admin Portal. Read the support page on enabling add-on features on your keys.

Users

Users are built-in objects. There are no top-level name or email fields, all application data lives in the free-form payload. Refer to users for the concept.

A user is an entity of the built-in User entity class, which the service provides at the Global class level. To give your users their own declared, filterable properties, define a subclass of User with Create a new entity class and pass its name as class.

Create user

Creates a user. Supply an id to control the identifier, or omit it to let the server generate one.

Method(s)

1pubnub.dataSync.createUser({
2 id: string,
3 class: string,
4 classLevel: string,
5 data: { classVersion: number, status: string, payload: object },
6}): Promise<DataSync.CreateUserResponse>
* required
ParameterDescription
id
Type: string
Default:
server-generated
User identifier. Omit to let the server generate a UUID. Max 255 characters.
class
Type: string
Default:
User
Name of the entity class this user belongs to. Must be User or one of its subclasses. Set at creation and immutable afterward.
classLevel
Type: string
Default:
service default
Class hierarchy level of class, either Global for a class the service provides or SubKey for one defined on your key set. Set at creation and immutable afterward.
data *
Type: object
Default:
n/a
The mutable user data.
> classVersion *
Type: number
Default:
n/a
Version of the user class schema.
> status
Type: string
Default:
n/a
Free-form lifecycle status. Max 100 characters.
> payload
Type: object
Default:
n/a
Free-form JSON object holding your application data.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.createUser({
3 id: 'user-alice',
4 data: {
5 classVersion: 1,
6 payload: { name: 'Alice', type: 'shopper' },
7 },
8 })
9 console.log(response.data)
10} catch (error) {
11 console.log(error.status)
12}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "user-alice",
5 "entityClassVersion": 1,
6 "payload": {
7 "name": "Alice",
8 "type": "shopper"
9 },
10 "createdAt": "2026-07-13T09:00:00.000Z",
11 "updatedAt": "2026-07-13T09:00:00.000Z",
12 "eTag": "AbQdEfGhIjKlMn"
13 }
14}

Get user

Returns a single user by id.

Method(s)

1pubnub.dataSync.getUser({
2 id: string,
3}): Promise<DataSync.GetUserResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
User identifier.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.getUser({ id: 'user-alice' })
3 console.log(response.data)
4} catch (error) {
5 console.log(error.status)
6}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "user-alice",
5 "entityClassVersion": 1,
6 "payload": {
7 "name": "Alice",
8 "type": "shopper"
9 },
10 "createdAt": "2026-07-13T09:00:00.000Z",
11 "updatedAt": "2026-07-13T09:00:00.000Z",
12 "eTag": "AbQdEfGhIjKlMn"
13 }
14}

Get all users

Returns a paginated list of users. All parameters are optional, so you can call getUsers() with no arguments, or pass a callback as the only argument. For pagination, filtering, and sorting, refer to sorting and pagination.

Method(s)

1pubnub.dataSync.getUsers({
2 class: string,
3 classVersion: number,
4 classLevel: string,
5 cursor: string,
6 limit: number,
7 filter: string,
8 filterFast: string,
9 sort: object | string,
10}): Promise<DataSync.GetUsersResponse>
* required
ParameterDescription
class
Type: string
Default:
all user classes
Entity class name to filter by. Omit to list users across every user class.
classVersion
Type: number
Default:
all versions
Entity class version to list. Omit to list users across every version of the class.
classLevel
Type: string
Default:
service default
Class hierarchy level of class, either Global for a class the service provides or SubKey for one defined on your key set. Used to disambiguate a class name defined at both levels.
cursor
Type: string
Default:
n/a
Opaque pagination cursor. Omit for the first page.
limit
Type: number
Default:
20
Maximum number of users per page. Max 100.
filter
Type: string
Default:
n/a
Filter expression evaluated against eventually consistent storage, so results can briefly lag writes. Supports the full expression language, over properties declared with filtering mode full. Cannot be combined with filterFast.
filterFast
Type: string
Default:
n/a
Filter expression evaluated against strongly consistent storage, so it reflects the latest writes. Accepts up to 10 conditions by default (raisable per keyset), over properties declared with filtering mode simple or full. Cannot be combined with filter.
sort
Type: object | string
Default:
n/a
Order results. An object mapping field to 'asc', 'desc', or null (service default), for example { createdAt: 'desc' }, or a comma-separated string such as 'createdAt:desc'.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.getUsers({
3 limit: 20,
4 sort: { createdAt: 'asc' },
5 })
6 console.log(response.data)
7 console.log(response.meta)
8} catch (error) {
9 console.log(error.status)
10}

Response

1{
2 "status": 200,
3 "data": [
4 {
5 "id": "user-alice",
6 "entityClassVersion": 1,
7 "payload": {
8 "name": "Alice",
9 "type": "shopper"
10 },
11 "createdAt": "2026-07-13T09:00:00.000Z",
12 "updatedAt": "2026-07-13T09:00:00.000Z",
13 "eTag": "AbQdEfGhIjKlMn"
14 }
15 ],
show all 21 lines

Other examples

Filter with filter

filter runs over properties declared with filtering mode full and is evaluated against eventually consistent storage, so a very recent write may not be matched yet. Reference a declared property by its name, not its path.

1try {
2 const response = await pubnub.dataSync.getUsers({
3 filter: 'name LIKE "*Alice*"',
4 })
5 console.log(response.data)
6} catch (error) {
7 console.log(error.status)
8}
Filter with filterFast

filterFast is evaluated against strongly consistent storage, so it matches an object you just wrote. It runs over properties declared with filtering mode simple or full and accepts up to 10 conditions by default (raisable per keyset). It shares the same expression language as filter, so only one of the two can be sent per call.

1try {
2 const response = await pubnub.dataSync.getUsers({
3 filterFast: 'type == "shopper"',
4 })
5 console.log(response.data)
6} catch (error) {
7 console.log(error.status)
8}
Page through results with cursor

Pass no cursor on the first call. Take meta.next_cursor from the response and pass it back as cursor on the next call. Stop when meta.has_next is false.

1try {
2 let cursor
3 let hasNext = true
4 let page = 0
5
6 while (hasNext) {
7 const response = await pubnub.dataSync.getUsers({
8 filterFast: 'type == "shopper"',
9 limit: 20,
10 cursor,
11 })
12 console.log(`Page ${++page}:`, response.data)
13 cursor = response.meta?.next_cursor
14 hasNext = response.meta?.has_next ?? false
15 }
show all 18 lines

Update user

Replaces a user in full (PUT). Send the complete set of fields, any field you omit is cleared. To guard against concurrent writes, pass ifMatchesEtag (see optimistic concurrency with ETags).

Method(s)

1pubnub.dataSync.setUser({
2 id: string,
3 data: { classVersion: number, status: string, payload: object },
4 ifMatchesEtag: string,
5}): Promise<DataSync.SetUserResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
User identifier.
data *
Type: object
Default:
n/a
The replacement user data.
> classVersion *
Type: number
Default:
n/a
Version of the user class schema.
> status
Type: string
Default:
n/a
Free-form lifecycle status. Max 100 characters.
> payload
Type: object
Default:
n/a
Free-form JSON object holding your application data.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The update succeeds only if it still matches, otherwise the server returns 412.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.setUser({
3 id: 'user-alice',
4 data: {
5 classVersion: 1,
6 payload: { name: 'Alice B.', type: 'shopper' },
7 },
8 ifMatchesEtag: 'AbQdEfGhIjKlMn',
9 })
10 console.log(response.data)
11} catch (error) {
12 console.log(error.status)
13}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "user-alice",
5 "entityClassVersion": 1,
6 "payload": {
7 "name": "Alice B.",
8 "type": "shopper"
9 },
10 "createdAt": "2026-07-13T09:00:00.000Z",
11 "updatedAt": "2026-07-13T10:15:00.000Z",
12 "eTag": "CdEfGhIjKlMnOp"
13 }
14}

Patch user

Applies a partial update to a user. Paths can target fields inside payload or top-level stored fields. Refer to partial update for the JSON Pointer rules.

Method(s)

1pubnub.dataSync.updateUser({
2 id: string,
3 add: object,
4 replace: object,
5 remove: string[],
6 move: { from: string, path: string }[],
7 copy: { from: string, path: string }[],
8 test: object,
9 ifMatchesEtag: string,
10}): Promise<DataSync.UpdateUserResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
User identifier.
add
Type: object
Default:
n/a
Full JSON Pointers mapped to values to add. Provide at least one patch operation.
replace
Type: object
Default:
n/a
Full JSON Pointers mapped to replacement values. Provide at least one patch operation.
remove
Type: array
Default:
n/a
Full JSON Pointers to remove. Provide at least one patch operation.
move
Type: array
Default:
n/a
Array of { from, path } JSON Pointer pairs. The value at from is removed and re-added at path.
copy
Type: array
Default:
n/a
Array of { from, path } JSON Pointer pairs. The value at from is duplicated to path.
test
Type: object
Default:
n/a
Full JSON Pointers mapped to expected values. The patch fails if any value does not match.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The patch succeeds only if it still matches, otherwise the server returns 412.

Use a JSON Pointer that starts with /payload/ to target payload fields, or a root pointer (for example /status) to target a top-level field. At least one of add, replace, remove, move, copy, or test must be provided.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.updateUser({
3 id: 'user-alice',
4 replace: { '/payload/name': 'Alice B.', '/status': 'active' },
5 })
6 console.log(response.data)
7} catch (error) {
8 console.log(error.status)
9}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "user-alice",
5 "entityClassVersion": 1,
6 "status": "active",
7 "payload": {
8 "name": "Alice B.",
9 "type": "shopper"
10 },
11 "createdAt": "2026-07-13T09:00:00.000Z",
12 "updatedAt": "2026-07-13T10:20:00.000Z",
13 "eTag": "EfGhIjKlMnOpQr"
14 }
15}

Remove user

Deletes a user by id.

Method(s)

1pubnub.dataSync.removeUser({
2 id: string,
3 ifMatchesEtag: string,
4}): Promise<DataSync.RemoveUserResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
User identifier.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The delete succeeds only if it still matches, otherwise the server returns 412.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.removeUser({ id: 'user-alice' })
3 console.log(response.status)
4} catch (error) {
5 console.log(error.status)
6}

Response

1{
2 "status": 200
3}

Channels

Channels work like users. They have no top-level name field, all application data lives in payload, and they support the same operations. Refer to channels for the concept.

A channel is an entity of the built-in Channel entity class, which the service provides at the Global class level. Subclass it with Create a new entity class to add declared properties, then pass the subclass name as class.

Create channel

Creates a channel. Supply an id to control the identifier, or omit it to let the server generate one.

Method(s)

1pubnub.dataSync.createChannel({
2 id: string,
3 class: string,
4 classLevel: string,
5 data: { classVersion: number, status: string, payload: object },
6}): Promise<DataSync.CreateChannelResponse>
* required
ParameterDescription
id
Type: string
Default:
server-generated
Channel identifier. Omit to let the server generate a UUID. Max 255 characters.
class
Type: string
Default:
Channel
Name of the entity class this channel belongs to. Must be Channel or one of its subclasses. Set at creation and immutable afterward.
classLevel
Type: string
Default:
service default
Class hierarchy level of class, either Global for a class the service provides or SubKey for one defined on your key set. Set at creation and immutable afterward.
data *
Type: object
Default:
n/a
The mutable channel data.
> classVersion *
Type: number
Default:
n/a
Version of the channel class schema.
> status
Type: string
Default:
n/a
Free-form lifecycle status. Max 100 characters.
> payload
Type: object
Default:
n/a
Free-form JSON object holding your application data.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.createChannel({
3 id: 'channel-summer-sale',
4 data: {
5 classVersion: 1,
6 payload: { name: 'Summer Sale', type: 'promotion' },
7 },
8 })
9 console.log(response.data)
10} catch (error) {
11 console.log(error.status)
12}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "channel-summer-sale",
5 "entityClassVersion": 1,
6 "payload": {
7 "name": "Summer Sale",
8 "type": "promotion"
9 },
10 "createdAt": "2026-07-13T09:05:00.000Z",
11 "updatedAt": "2026-07-13T09:05:00.000Z",
12 "eTag": "GhIjKlMnOpQrSt"
13 }
14}

Get channel

Returns a single channel by id.

Method(s)

1pubnub.dataSync.getChannel({
2 id: string,
3}): Promise<DataSync.GetChannelResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Channel identifier.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.getChannel({ id: 'channel-summer-sale' })
3 console.log(response.data)
4} catch (error) {
5 console.log(error.status)
6}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "channel-summer-sale",
5 "entityClassVersion": 1,
6 "payload": {
7 "name": "Summer Sale",
8 "type": "promotion"
9 },
10 "createdAt": "2026-07-13T09:05:00.000Z",
11 "updatedAt": "2026-07-13T09:05:00.000Z",
12 "eTag": "GhIjKlMnOpQrSt"
13 }
14}

Get all channels

Returns a paginated list of channels. All parameters are optional, so you can call getChannels() with no arguments, or pass a callback as the only argument. For pagination, filtering, and sorting, refer to sorting and pagination.

Method(s)

1pubnub.dataSync.getChannels({
2 class: string,
3 classVersion: number,
4 classLevel: string,
5 cursor: string,
6 limit: number,
7 filter: string,
8 filterFast: string,
9 sort: object | string,
10}): Promise<DataSync.GetChannelsResponse>
* required
ParameterDescription
class
Type: string
Default:
all channel classes
Entity class name to filter by. Omit to list channels across every channel class.
classVersion
Type: number
Default:
all versions
Entity class version to list. Omit to list channels across every version of the class.
classLevel
Type: string
Default:
service default
Class hierarchy level of class, either Global for a class the service provides or SubKey for one defined on your key set. Used to disambiguate a class name defined at both levels.
cursor
Type: string
Default:
n/a
Opaque pagination cursor. Omit for the first page.
limit
Type: number
Default:
20
Maximum number of channels per page. Max 100.
filter
Type: string
Default:
n/a
Filter expression evaluated against eventually consistent storage, so results can briefly lag writes. Supports the full expression language, over properties declared with filtering mode full. Cannot be combined with filterFast.
filterFast
Type: string
Default:
n/a
Filter expression evaluated against strongly consistent storage, so it reflects the latest writes. Accepts up to 10 conditions by default (raisable per keyset), over properties declared with filtering mode simple or full. Cannot be combined with filter.
sort
Type: object | string
Default:
n/a
Order results. An object mapping field to 'asc', 'desc', or null (service default), or a comma-separated string such as 'createdAt:desc'.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.getChannels({ limit: 20 })
3 console.log(response.data)
4 console.log(response.meta)
5} catch (error) {
6 console.log(error.status)
7}

Response

1{
2 "status": 200,
3 "data": [
4 {
5 "id": "channel-summer-sale",
6 "entityClassVersion": 1,
7 "payload": {
8 "name": "Summer Sale",
9 "type": "promotion"
10 },
11 "createdAt": "2026-07-13T09:05:00.000Z",
12 "updatedAt": "2026-07-13T09:05:00.000Z",
13 "eTag": "GhIjKlMnOpQrSt"
14 }
15 ],
show all 21 lines

Other examples

Filter with filter

filter runs over properties declared with filtering mode full and is evaluated against eventually consistent storage, so a very recent write may not be matched yet. Reference a declared property by its name, not its path.

1try {
2 const response = await pubnub.dataSync.getChannels({
3 filter: 'name LIKE "*Sale*"',
4 })
5 console.log(response.data)
6} catch (error) {
7 console.log(error.status)
8}
Filter with filterFast

filterFast is evaluated against strongly consistent storage, so it matches an object you just wrote. It runs over properties declared with filtering mode simple or full and accepts up to 10 conditions by default (raisable per keyset). It shares the same expression language as filter, so only one of the two can be sent per call.

1try {
2 const response = await pubnub.dataSync.getChannels({
3 filterFast: 'type == "promotion"',
4 })
5 console.log(response.data)
6} catch (error) {
7 console.log(error.status)
8}
Page through results with cursor

Pass no cursor on the first call. Take meta.next_cursor from the response and pass it back as cursor on the next call. Stop when meta.has_next is false.

1try {
2 let cursor
3 let hasNext = true
4 let page = 0
5
6 while (hasNext) {
7 const response = await pubnub.dataSync.getChannels({
8 filterFast: 'type == "promotion"',
9 limit: 20,
10 cursor,
11 })
12 console.log(`Page ${++page}:`, response.data)
13 cursor = response.meta?.next_cursor
14 hasNext = response.meta?.has_next ?? false
15 }
show all 18 lines

Update channel

Replaces a channel in full (PUT). Refer to optimistic concurrency with ETags for ifMatchesEtag.

Method(s)

1pubnub.dataSync.setChannel({
2 id: string,
3 data: { classVersion: number, status: string, payload: object },
4 ifMatchesEtag: string,
5}): Promise<DataSync.SetChannelResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Channel identifier.
data *
Type: object
Default:
n/a
The replacement channel data.
> classVersion *
Type: number
Default:
n/a
Version of the channel class schema.
> status
Type: string
Default:
n/a
Free-form lifecycle status. Max 100 characters.
> payload
Type: object
Default:
n/a
Free-form JSON object holding your application data.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The update succeeds only if it still matches, otherwise the server returns 412.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.setChannel({
3 id: 'channel-summer-sale',
4 data: {
5 classVersion: 1,
6 payload: { name: 'Summer Sale 2026', type: 'promotion' },
7 },
8 })
9 console.log(response.data)
10} catch (error) {
11 console.log(error.status)
12}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "channel-summer-sale",
5 "entityClassVersion": 1,
6 "payload": {
7 "name": "Summer Sale 2026",
8 "type": "promotion"
9 },
10 "createdAt": "2026-07-13T09:05:00.000Z",
11 "updatedAt": "2026-07-13T11:00:00.000Z",
12 "eTag": "IjKlMnOpQrStUv"
13 }
14}

Patch channel

Applies a partial update to a channel. Paths can target fields inside payload or top-level stored fields. Refer to partial update for the JSON Pointer rules.

Method(s)

1pubnub.dataSync.updateChannel({
2 id: string,
3 add: object,
4 replace: object,
5 remove: string[],
6 move: { from: string, path: string }[],
7 copy: { from: string, path: string }[],
8 test: object,
9 ifMatchesEtag: string,
10}): Promise<DataSync.UpdateChannelResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Channel identifier.
add
Type: object
Default:
n/a
Full JSON Pointers mapped to values to add. Provide at least one patch operation.
replace
Type: object
Default:
n/a
Full JSON Pointers mapped to replacement values. Provide at least one patch operation.
remove
Type: array
Default:
n/a
Full JSON Pointers to remove. Provide at least one patch operation.
move
Type: array
Default:
n/a
Array of { from, path } JSON Pointer pairs. The value at from is removed and re-added at path.
copy
Type: array
Default:
n/a
Array of { from, path } JSON Pointer pairs. The value at from is duplicated to path.
test
Type: object
Default:
n/a
Full JSON Pointers mapped to expected values. The patch fails if any value does not match.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The patch succeeds only if it still matches, otherwise the server returns 412.

Use a JSON Pointer that starts with /payload/ to target payload fields, or a root pointer (for example /status) to target a top-level field. At least one of add, replace, remove, move, copy, or test must be provided.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.updateChannel({
3 id: 'channel-summer-sale',
4 replace: { '/payload/name': 'Summer Sale 2026' },
5 })
6 console.log(response.data)
7} catch (error) {
8 console.log(error.status)
9}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "channel-summer-sale",
5 "entityClassVersion": 1,
6 "payload": {
7 "name": "Summer Sale 2026",
8 "type": "promotion"
9 },
10 "createdAt": "2026-07-13T09:05:00.000Z",
11 "updatedAt": "2026-07-13T11:05:00.000Z",
12 "eTag": "KlMnOpQrStUvWx"
13 }
14}

Remove channel

Deletes a channel by id.

Method(s)

1pubnub.dataSync.removeChannel({
2 id: string,
3 ifMatchesEtag: string,
4}): Promise<DataSync.RemoveChannelResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Channel identifier.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The delete succeeds only if it still matches, otherwise the server returns 412.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.removeChannel({ id: 'channel-summer-sale' })
3 console.log(response.status)
4} catch (error) {
5 console.log(error.status)
6}

Response

1{
2 "status": 200
3}

Memberships

A membership links a user to a channel and carries its own payload. In the running example, Alice is a member of channel-summer-sale with the payload { "role": "viewer" }. Refer to memberships for the concept.

A membership is a relationship of the built-in Global Membership relationship class, whose two sides are surfaced as channelId and userId. Every membership response includes both relationshipClass and relationshipClassVersion. The class is assigned by the service, so the createMembership and setMembership methods take no class parameter.

Relationship classes don't support inheritance, and only the Global Membership class produces memberships, so relationshipClass is always Membership and relationshipClassVersion is the only part that varies.

Create membership

Creates a membership linking a user to a channel. The userId/channelId pair must be unique for the membership class. A membership that duplicates an existing pair is rejected with a 409.

Method(s)

1pubnub.dataSync.createMembership({
2 id: string,
3 userId: string,
4 channelId: string,
5 data: { classVersion: number, status: string, payload: object },
6}): Promise<DataSync.CreateMembershipResponse>
* required
ParameterDescription
id
Type: string
Default:
server-generated
Membership identifier. Omit to let the server generate a UUID. Max 255 characters.
userId *
Type: string
Default:
n/a
Identifier of the user in the membership.
channelId *
Type: string
Default:
n/a
Identifier of the channel in the membership.
data *
Type: object
Default:
n/a
The mutable membership data.
> classVersion *
Type: number
Default:
n/a
Version of the membership class schema.
> status
Type: string
Default:
n/a
Free-form lifecycle status. Max 100 characters.
> payload
Type: object
Default:
n/a
Free-form JSON object holding your application data.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.createMembership({
3 userId: 'user-alice',
4 channelId: 'channel-summer-sale',
5 data: {
6 classVersion: 1,
7 payload: { role: 'viewer' },
8 },
9 })
10 console.log(response.data)
11} catch (error) {
12 console.log(error.status)
13}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "membership-alice-summer-sale",
5 "channelId": "channel-summer-sale",
6 "userId": "user-alice",
7 "relationshipClass": "Membership",
8 "relationshipClassVersion": 1,
9 "payload": {
10 "role": "viewer"
11 },
12 "createdAt": "2026-07-13T09:10:00.000Z",
13 "updatedAt": "2026-07-13T09:10:00.000Z",
14 "eTag": "MnOpQrStUvWxYz"
15 }
show all 16 lines

Get membership

Returns a single membership by id.

Method(s)

1pubnub.dataSync.getMembership({
2 id: string,
3}): Promise<DataSync.GetMembershipResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Membership identifier.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.getMembership({
3 id: 'membership-alice-summer-sale',
4 })
5 console.log(response.data)
6} catch (error) {
7 console.log(error.status)
8}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "membership-alice-summer-sale",
5 "channelId": "channel-summer-sale",
6 "userId": "user-alice",
7 "relationshipClass": "Membership",
8 "relationshipClassVersion": 1,
9 "payload": {
10 "role": "viewer"
11 },
12 "createdAt": "2026-07-13T09:10:00.000Z",
13 "updatedAt": "2026-07-13T09:10:00.000Z",
14 "eTag": "MnOpQrStUvWxYz"
15 }
show all 16 lines

Get all memberships

Returns a paginated list of memberships. All parameters are optional, so you can call getMemberships() with no arguments, or pass a callback as the only argument. Filter by userId to list a user's memberships or by channelId to list a channel's members. For pagination, filtering, and sorting, refer to sorting and pagination.

Method(s)

1pubnub.dataSync.getMemberships({
2 cursor: string,
3 limit: number,
4 filter: string,
5 filterFast: string,
6 sort: object | string,
7 userId: string,
8 channelId: string,
9 classVersion: number,
10}): Promise<DataSync.GetMembershipsResponse>
* required
ParameterDescription
cursor
Type: string
Default:
n/a
Opaque pagination cursor. Omit for the first page.
limit
Type: number
Default:
20
Maximum number of memberships per page. Max 100.
filter
Type: string
Default:
n/a
Filter expression evaluated against eventually consistent storage, so results can briefly lag writes. Supports the full expression language, over properties declared with filtering mode full. Cannot be combined with filterFast.
filterFast
Type: string
Default:
n/a
Filter expression evaluated against strongly consistent storage, so it reflects the latest writes. Accepts up to 10 conditions by default (raisable per keyset), over properties declared with filtering mode simple or full. Cannot be combined with filter.
sort
Type: object | string
Default:
n/a
Order results. An object mapping field to 'asc', 'desc', or null (service default), or a comma-separated string such as 'createdAt:desc'.
userId
Type: string
Default:
n/a
List only memberships for this user.
channelId
Type: string
Default:
n/a
List only memberships for this channel.
classVersion
Type: number
Default:
all versions
Membership class version to list. Omit to list memberships across every version of the class.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.getMemberships({
3 userId: 'user-alice',
4 limit: 20,
5 })
6 console.log(response.data)
7 console.log(response.meta)
8} catch (error) {
9 console.log(error.status)
10}

Response

1{
2 "status": 200,
3 "data": [
4 {
5 "id": "membership-alice-summer-sale",
6 "channelId": "channel-summer-sale",
7 "userId": "user-alice",
8 "relationshipClass": "Membership",
9 "relationshipClassVersion": 1,
10 "payload": {
11 "role": "viewer"
12 },
13 "createdAt": "2026-07-13T09:10:00.000Z",
14 "updatedAt": "2026-07-13T09:10:00.000Z",
15 "eTag": "MnOpQrStUvWxYz"
show all 23 lines

Other examples

List a channel's members with channelId

Pass channelId instead of userId to list the members of a channel rather than a user's memberships.

1try {
2 const response = await pubnub.dataSync.getMemberships({
3 channelId: 'channel-summer-sale',
4 limit: 20,
5 })
6 console.log(response.data)
7} catch (error) {
8 console.log(error.status)
9}
Filter with filter

filter runs over properties declared with filtering mode full and is evaluated against eventually consistent storage, so a very recent write may not be matched yet. Reference a declared property by its name, not its path.

1try {
2 const response = await pubnub.dataSync.getMemberships({
3 userId: 'user-alice',
4 filter: 'role LIKE "*mod*"',
5 })
6 console.log(response.data)
7} catch (error) {
8 console.log(error.status)
9}
Filter with filterFast

filterFast is evaluated against strongly consistent storage, so it matches an object you just wrote. It runs over properties declared with filtering mode simple or full and accepts up to 10 conditions by default (raisable per keyset). It shares the same expression language as filter, so only one of the two can be sent per call.

1try {
2 const response = await pubnub.dataSync.getMemberships({
3 channelId: 'channel-summer-sale',
4 filterFast: 'role == "viewer"',
5 })
6 console.log(response.data)
7} catch (error) {
8 console.log(error.status)
9}
Page through results with cursor

Pass no cursor on the first call. Take meta.next_cursor from the response and pass it back as cursor on the next call. Stop when meta.has_next is false.

1try {
2 let cursor
3 let hasNext = true
4 let page = 0
5
6 while (hasNext) {
7 const response = await pubnub.dataSync.getMemberships({
8 userId: 'user-alice',
9 limit: 20,
10 cursor,
11 })
12 console.log(`Page ${++page}:`, response.data)
13 cursor = response.meta?.next_cursor
14 hasNext = response.meta?.has_next ?? false
15 }
show all 18 lines

Update membership

Replaces a membership in full (PUT). Resend userId, channelId, and classVersion along with the fields you want to keep. For a partial update, use Patch membership. Refer to optimistic concurrency with ETags for ifMatchesEtag.

Method(s)

1pubnub.dataSync.setMembership({
2 id: string,
3 userId: string,
4 channelId: string,
5 data: { classVersion: number, status: string, payload: object },
6 ifMatchesEtag: string,
7}): Promise<DataSync.SetMembershipResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Membership identifier.
userId *
Type: string
Default:
n/a
Identifier of the user in the membership.
channelId *
Type: string
Default:
n/a
Identifier of the channel in the membership.
data *
Type: object
Default:
n/a
The replacement membership data.
> classVersion *
Type: number
Default:
n/a
Version of the membership class schema.
> status
Type: string
Default:
n/a
Free-form lifecycle status. Max 100 characters.
> payload
Type: object
Default:
n/a
Free-form JSON object holding your application data.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The update succeeds only if it still matches, otherwise the server returns 412.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.setMembership({
3 id: 'membership-alice-summer-sale',
4 userId: 'user-alice',
5 channelId: 'channel-summer-sale',
6 data: {
7 classVersion: 1,
8 payload: { role: 'moderator' },
9 },
10 })
11 console.log(response.data)
12} catch (error) {
13 console.log(error.status)
14}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "membership-alice-summer-sale",
5 "channelId": "channel-summer-sale",
6 "userId": "user-alice",
7 "relationshipClass": "Membership",
8 "relationshipClassVersion": 1,
9 "payload": {
10 "role": "moderator"
11 },
12 "createdAt": "2026-07-13T09:10:00.000Z",
13 "updatedAt": "2026-07-13T12:00:00.000Z",
14 "eTag": "OpQrStUvWxYzAb"
15 }
show all 16 lines

Patch membership

Applies a partial update to a membership. Paths can target fields inside payload or top-level stored fields. Refer to partial update for the JSON Pointer rules.

Method(s)

1pubnub.dataSync.updateMembership({
2 id: string,
3 add: object,
4 replace: object,
5 remove: string[],
6 move: { from: string, path: string }[],
7 copy: { from: string, path: string }[],
8 test: object,
9 ifMatchesEtag: string,
10}): Promise<DataSync.UpdateMembershipResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Membership identifier.
add
Type: object
Default:
n/a
Full JSON Pointers mapped to values to add. Provide at least one patch operation.
replace
Type: object
Default:
n/a
Full JSON Pointers mapped to replacement values. Provide at least one patch operation.
remove
Type: array
Default:
n/a
Full JSON Pointers to remove. Provide at least one patch operation.
move
Type: array
Default:
n/a
Array of { from, path } JSON Pointer pairs. The value at from is removed and re-added at path.
copy
Type: array
Default:
n/a
Array of { from, path } JSON Pointer pairs. The value at from is duplicated to path.
test
Type: object
Default:
n/a
Full JSON Pointers mapped to expected values. The patch fails if any value does not match.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The patch succeeds only if it still matches, otherwise the server returns 412.

Use a JSON Pointer that starts with /payload/ to target payload fields, or a root pointer (for example /status) to target a top-level field. At least one of add, replace, remove, move, copy, or test must be provided.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.updateMembership({
3 id: 'membership-alice-summer-sale',
4 replace: { '/payload/role': 'moderator' },
5 })
6 console.log(response.data)
7} catch (error) {
8 console.log(error.status)
9}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "membership-alice-summer-sale",
5 "channelId": "channel-summer-sale",
6 "userId": "user-alice",
7 "relationshipClass": "Membership",
8 "relationshipClassVersion": 1,
9 "payload": {
10 "role": "moderator"
11 },
12 "createdAt": "2026-07-13T09:10:00.000Z",
13 "updatedAt": "2026-07-13T12:05:00.000Z",
14 "eTag": "QrStUvWxYzAbCd"
15 }
show all 16 lines

Remove membership

Deletes a membership by id.

Method(s)

1pubnub.dataSync.removeMembership({
2 id: string,
3 ifMatchesEtag: string,
4}): Promise<DataSync.RemoveMembershipResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Membership identifier.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The delete succeeds only if it still matches, otherwise the server returns 412.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.removeMembership({
3 id: 'membership-alice-summer-sale',
4 })
5 console.log(response.status)
6} catch (error) {
7 console.log(error.status)
8}

Response

1{
2 "status": 200
3}

Entities

Entities are instances of the custom entity classes you define on your key set. In the running example, product is a class and product-sneaker-42 is an instance. Use Create a new entity class to declare a class, and Get entity class by ID to read the property, filtering, and projection declarations that govern its instances.

Entity classes can extend one another. Listing a class also returns the entities of its subclasses, so getEntities({ class: 'product' }) returns every product plus every instance of a class that extends product.

Create entity

Creates an entity of a given class and version.

Method(s)

1pubnub.dataSync.createEntity({
2 id: string,
3 class: string,
4 classLevel: string,
5 data: { classVersion: number, status: string, payload: object },
6}): Promise<DataSync.CreateEntityResponse>
* required
ParameterDescription
id
Type: string
Default:
server-generated
Entity identifier. Omit to let the server generate a UUID. Max 255 characters.
class *
Type: string
Default:
n/a
Name of the entity class this instance belongs to. Set at creation and immutable afterward.
classLevel
Type: string
Default:
service default
Class hierarchy level of class, either Global for a class the service provides or SubKey for one defined on your key set. Set at creation and immutable afterward.
data *
Type: object
Default:
n/a
The mutable entity data.
> classVersion *
Type: number
Default:
n/a
Version of the entity class schema.
> status
Type: string
Default:
n/a
Free-form lifecycle status. Max 100 characters.
> payload
Type: object
Default:
n/a
Free-form JSON object holding your application data.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.createEntity({
3 id: 'product-sneaker-42',
4 class: 'product',
5 data: {
6 classVersion: 1,
7 payload: { name: 'Retro Sneaker', price: 89.99, stock: 12 },
8 },
9 })
10 console.log(response.data)
11} catch (error) {
12 console.log(error.status)
13}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "product-sneaker-42",
5 "entityClass": "product",
6 "entityClassVersion": 1,
7 "payload": {
8 "name": "Retro Sneaker",
9 "price": 89.99,
10 "stock": 12
11 },
12 "createdAt": "2026-07-13T09:15:00.000Z",
13 "updatedAt": "2026-07-13T09:15:00.000Z",
14 "eTag": "QrStUvWxYzAbCd"
15 }
show all 16 lines

Get entity

Returns a single entity by id.

Method(s)

1pubnub.dataSync.getEntity({
2 id: string,
3}): Promise<DataSync.GetEntityResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Entity identifier.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.getEntity({ id: 'product-sneaker-42' })
3 console.log(response.data)
4} catch (error) {
5 console.log(error.status)
6}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "product-sneaker-42",
5 "entityClass": "product",
6 "entityClassVersion": 1,
7 "payload": {
8 "name": "Retro Sneaker",
9 "price": 89.99,
10 "stock": 12
11 },
12 "createdAt": "2026-07-13T09:15:00.000Z",
13 "updatedAt": "2026-07-13T09:15:00.000Z",
14 "eTag": "QrStUvWxYzAbCd"
15 }
show all 16 lines

Get all entities

Returns a paginated list of entities within a class. The class parameter is required, entities are always listed within the context of their class. For pagination, filtering, and sorting, refer to sorting and pagination.

Method(s)

1pubnub.dataSync.getEntities({
2 class: string,
3 classVersion: number,
4 classLevel: string,
5 cursor: string,
6 limit: number,
7 filter: string,
8 filterFast: string,
9 sort: object | string,
10}): Promise<DataSync.GetEntitiesResponse>
* required
ParameterDescription
class *
Type: string
Default:
n/a
Name of the entity class to list. This parameter is required.
classVersion
Type: number
Default:
all versions
Entity class version to list. Omit to list entities across every version of the class.
classLevel
Type: string
Default:
service default
Class hierarchy level of class, either Global for a class the service provides or SubKey for one defined on your key set. Used to disambiguate a class name defined at both levels.
cursor
Type: string
Default:
n/a
Opaque pagination cursor. Omit for the first page.
limit
Type: number
Default:
20
Maximum number of entities per page. Max 100.
filter
Type: string
Default:
n/a
Filter expression evaluated against eventually consistent storage, so results can briefly lag writes. Supports the full expression language, over properties declared with filtering mode full. Cannot be combined with filterFast.
filterFast
Type: string
Default:
n/a
Filter expression evaluated against strongly consistent storage, so it reflects the latest writes. Accepts up to 10 conditions by default (raisable per keyset), over properties declared with filtering mode simple or full. Cannot be combined with filter.
sort
Type: object | string
Default:
n/a
Order results. An object mapping field to 'asc', 'desc', or null (service default), or a comma-separated string such as 'createdAt:desc'.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.getEntities({
3 class: 'product',
4 sort: { price: 'desc' },
5 limit: 20,
6 })
7 console.log(response.data)
8 console.log(response.meta)
9} catch (error) {
10 console.log(error.status)
11}

Response

1{
2 "status": 200,
3 "data": [
4 {
5 "id": "product-sneaker-42",
6 "entityClass": "product",
7 "entityClassVersion": 1,
8 "payload": {
9 "name": "Retro Sneaker",
10 "price": 89.99,
11 "stock": 12
12 },
13 "createdAt": "2026-07-13T09:15:00.000Z",
14 "updatedAt": "2026-07-13T09:15:00.000Z",
15 "eTag": "QrStUvWxYzAbCd"
show all 23 lines

Other examples

Filter with filter

filter runs over properties declared with filtering mode full and is evaluated against eventually consistent storage, so a very recent write may not be matched yet. Reference a declared property by its name (not its path), and combine conditions with && and ||.

1try {
2 const response = await pubnub.dataSync.getEntities({
3 class: 'product',
4 filter: 'name LIKE "*sneaker*" && !(status == "discontinued")',
5 })
6 console.log(response.data)
7} catch (error) {
8 console.log(error.status)
9}
Filter with filterFast

filterFast is evaluated against strongly consistent storage, so it matches an object you just wrote. It runs over properties declared with filtering mode simple or full and accepts up to 10 conditions by default (raisable per keyset). It shares the same expression language as filter, so only one of the two can be sent per call.

1try {
2 const response = await pubnub.dataSync.getEntities({
3 class: 'product',
4 filterFast: 'price < 100 && stock > 0',
5 })
6 console.log(response.data)
7} catch (error) {
8 console.log(error.status)
9}
Page through results with cursor

Pass no cursor on the first call. Take meta.next_cursor from the response and pass it back as cursor on the next call. Stop when meta.has_next is false.

1try {
2 let cursor
3 let hasNext = true
4 let page = 0
5
6 while (hasNext) {
7 const response = await pubnub.dataSync.getEntities({
8 class: 'product',
9 filterFast: 'price < 100',
10 limit: 20,
11 cursor,
12 })
13 console.log(`Page ${++page}:`, response.data)
14 cursor = response.meta?.next_cursor
15 hasNext = response.meta?.has_next ?? false
show all 19 lines

Update entity

Replaces an entity in full (PUT). The entityClass is immutable after creation and cannot be sent. Refer to optimistic concurrency with ETags for ifMatchesEtag.

Method(s)

1pubnub.dataSync.setEntity({
2 id: string,
3 data: { classVersion: number, status: string, payload: object },
4 ifMatchesEtag: string,
5}): Promise<DataSync.SetEntityResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Entity identifier.
data *
Type: object
Default:
n/a
The replacement entity data. The entity class is immutable and cannot be included.
> classVersion *
Type: number
Default:
n/a
Version of the entity class schema.
> status
Type: string
Default:
n/a
Free-form lifecycle status. Max 100 characters.
> payload
Type: object
Default:
n/a
Free-form JSON object holding your application data.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The update succeeds only if it still matches, otherwise the server returns 412.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.setEntity({
3 id: 'product-sneaker-42',
4 data: {
5 classVersion: 1,
6 payload: { name: 'Retro Sneaker', price: 79.99, stock: 8 },
7 },
8 })
9 console.log(response.data)
10} catch (error) {
11 console.log(error.status)
12}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "product-sneaker-42",
5 "entityClass": "product",
6 "entityClassVersion": 1,
7 "payload": {
8 "name": "Retro Sneaker",
9 "price": 79.99,
10 "stock": 8
11 },
12 "createdAt": "2026-07-13T09:15:00.000Z",
13 "updatedAt": "2026-07-13T13:00:00.000Z",
14 "eTag": "StUvWxYzAbCdEf"
15 }
show all 16 lines

Patch entity

Applies a partial update to an entity. Paths can target fields inside payload or top-level stored fields. Refer to partial update for the JSON Pointer rules.

Method(s)

1pubnub.dataSync.updateEntity({
2 id: string,
3 add: object,
4 replace: object,
5 remove: string[],
6 move: { from: string, path: string }[],
7 copy: { from: string, path: string }[],
8 test: object,
9 ifMatchesEtag: string,
10}): Promise<DataSync.UpdateEntityResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Entity identifier.
add
Type: object
Default:
n/a
Full JSON Pointers mapped to values to add. Provide at least one patch operation.
replace
Type: object
Default:
n/a
Full JSON Pointers mapped to replacement values. Provide at least one patch operation.
remove
Type: array
Default:
n/a
Full JSON Pointers to remove. Provide at least one patch operation.
move
Type: array
Default:
n/a
Array of { from, path } JSON Pointer pairs. The value at from is removed and re-added at path.
copy
Type: array
Default:
n/a
Array of { from, path } JSON Pointer pairs. The value at from is duplicated to path.
test
Type: object
Default:
n/a
Full JSON Pointers mapped to expected values. The patch fails if any value does not match.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The patch succeeds only if it still matches, otherwise the server returns 412.

Use a JSON Pointer that starts with /payload/ to target payload fields, or a root pointer (for example /status) to target a top-level field. At least one of add, replace, remove, move, copy, or test must be provided.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.updateEntity({
3 id: 'product-sneaker-42',
4 replace: { '/payload/price': 79.99 },
5 })
6 console.log(response.data)
7} catch (error) {
8 console.log(error.status)
9}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "product-sneaker-42",
5 "entityClass": "product",
6 "entityClassVersion": 1,
7 "payload": {
8 "name": "Retro Sneaker",
9 "price": 79.99,
10 "stock": 12
11 },
12 "createdAt": "2026-07-13T09:15:00.000Z",
13 "updatedAt": "2026-07-13T13:05:00.000Z",
14 "eTag": "UvWxYzAbCdEfGh"
15 }
show all 16 lines

Other examples

Combine patch operations, and guard the write with ifMatchesEtag

A single updateEntity call can mix add, replace, remove, move, copy, and test. The order you write the keys in does not matter, the SDK always sends the operations in the same order: add, replace, remove, move, copy, and test last. Add ifMatchesEtag (the eTag from a prior read) to reject the patch with a 412 if the entity changed since you read it, instead of silently overwriting a concurrent change.

1try {
2 const response = await pubnub.dataSync.updateEntity({
3 id: 'product-sneaker-42',
4 test: { '/payload/stock': 8 },
5 replace: { '/payload/price': 74.99 },
6 add: { '/payload/tags/0': 'clearance' },
7 remove: ['/payload/legacy/field'],
8 move: [{ from: '/payload/legacyName', path: '/payload/displayName' }],
9 copy: [{ from: '/payload/displayName', path: '/payload/previousName' }],
10 ifMatchesEtag: 'StUvWxYzAbCdEf',
11 })
12 console.log(response.data)
13} catch (error) {
14 console.log(error.status)
15}

Remove entity

Deletes an entity by id.

Method(s)

1pubnub.dataSync.removeEntity({
2 id: string,
3 ifMatchesEtag: string,
4}): Promise<DataSync.RemoveEntityResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Entity identifier.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The delete succeeds only if it still matches, otherwise the server returns 412.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.removeEntity({ id: 'product-sneaker-42' })
3 console.log(response.status)
4} catch (error) {
5 console.log(error.status)
6}

Response

1{
2 "status": 200
3}

Relationships

A relationship links two entities and carries its own payload. In the running example, the ProductOwner relationship links seller-bob to product-sneaker-42.

Relationships are instances of the relationship classes you define on your key set. A relationship class declares the cardinality the service enforces (one-to-one, one-to-many, or many-to-many) and, optionally, which entity class each side must belong to. Use Create a new relationship class to declare a class, and Get relationship class by name and version to read it back.

Create relationship

Creates a relationship between two entities. The relationship class's cardinality (one-to-one, one-to-many, or many-to-many) is enforced on create. A relationship that violates its class's cardinality is rejected with a 409.

Method(s)

1pubnub.dataSync.createRelationship({
2 id: string,
3 class: string,
4 entityAId: string,
5 entityBId: string,
6 data: { classVersion: number, status: string, payload: object },
7}): Promise<DataSync.CreateRelationshipResponse>
* required
ParameterDescription
id
Type: string
Default:
server-generated
Relationship identifier. Omit to let the server generate a UUID. Max 255 characters.
class *
Type: string
Default:
n/a
Name of the relationship class this instance belongs to. Set at creation and immutable afterward.
entityAId *
Type: string
Default:
n/a
Identifier of the first linked entity. Immutable after creation.
entityBId *
Type: string
Default:
n/a
Identifier of the second linked entity. Immutable after creation.
data *
Type: object
Default:
n/a
The mutable relationship data.
> classVersion *
Type: number
Default:
n/a
Version of the relationship class schema.
> status
Type: string
Default:
n/a
Free-form lifecycle status. Max 100 characters.
> payload
Type: object
Default:
n/a
Free-form JSON object holding your application data.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.createRelationship({
3 class: 'ProductOwner',
4 entityAId: 'seller-bob',
5 entityBId: 'product-sneaker-42',
6 data: {
7 classVersion: 1,
8 payload: { since: '2026-07-13' },
9 },
10 })
11 console.log(response.data)
12} catch (error) {
13 console.log(error.status)
14}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "rel-bob-owns-sneaker-42",
5 "entityAId": "seller-bob",
6 "entityBId": "product-sneaker-42",
7 "relationshipClass": "ProductOwner",
8 "relationshipClassVersion": 1,
9 "payload": {
10 "since": "2026-07-13"
11 },
12 "createdAt": "2026-07-13T09:20:00.000Z",
13 "updatedAt": "2026-07-13T09:20:00.000Z",
14 "eTag": "WxYzAbCdEfGhIj"
15 }
show all 16 lines

Get relationship

Returns a single relationship by id.

Method(s)

1pubnub.dataSync.getRelationship({
2 id: string,
3}): Promise<DataSync.GetRelationshipResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Relationship identifier.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.getRelationship({
3 id: 'rel-bob-owns-sneaker-42',
4 })
5 console.log(response.data)
6} catch (error) {
7 console.log(error.status)
8}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "rel-bob-owns-sneaker-42",
5 "entityAId": "seller-bob",
6 "entityBId": "product-sneaker-42",
7 "relationshipClass": "ProductOwner",
8 "relationshipClassVersion": 1,
9 "payload": {
10 "since": "2026-07-13"
11 },
12 "createdAt": "2026-07-13T09:20:00.000Z",
13 "updatedAt": "2026-07-13T09:20:00.000Z",
14 "eTag": "WxYzAbCdEfGhIj"
15 }
show all 16 lines

Get all relationships

Returns a paginated list of relationships within a class. The class parameter is required. Filter by entityAId or entityBId to list a specific entity's links. For pagination, filtering, and sorting, refer to sorting and pagination.

Method(s)

1pubnub.dataSync.getRelationships({
2 class: string,
3 classVersion: number,
4 entityAId: string,
5 entityBId: string,
6 cursor: string,
7 limit: number,
8 filter: string,
9 filterFast: string,
10 sort: object | string,
11}): Promise<DataSync.GetRelationshipsResponse>
* required
ParameterDescription
class *
Type: string
Default:
n/a
Name of the relationship class to list. This parameter is required.
classVersion
Type: number
Default:
all versions
Relationship class version to list. Omit to list relationships across every version of the class.
entityAId
Type: string
Default:
n/a
List only relationships whose first entity is this id.
entityBId
Type: string
Default:
n/a
List only relationships whose second entity is this id.
cursor
Type: string
Default:
n/a
Opaque pagination cursor. Omit for the first page.
limit
Type: number
Default:
20
Maximum number of relationships per page. Max 100.
filter
Type: string
Default:
n/a
Filter expression evaluated against eventually consistent storage, so results can briefly lag writes. Supports the full expression language, over properties declared with filtering mode full. Cannot be combined with filterFast.
filterFast
Type: string
Default:
n/a
Filter expression evaluated against strongly consistent storage, so it reflects the latest writes. Accepts up to 10 conditions by default (raisable per keyset), over properties declared with filtering mode simple or full. Cannot be combined with filter.
sort
Type: object | string
Default:
n/a
Order results. An object mapping field to 'asc', 'desc', or null (service default), or a comma-separated string such as 'createdAt:desc'.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.getRelationships({
3 class: 'ProductOwner',
4 entityAId: 'seller-bob',
5 limit: 20,
6 })
7 console.log(response.data)
8 console.log(response.meta)
9} catch (error) {
10 console.log(error.status)
11}

Response

1{
2 "status": 200,
3 "data": [
4 {
5 "id": "rel-bob-owns-sneaker-42",
6 "entityAId": "seller-bob",
7 "entityBId": "product-sneaker-42",
8 "relationshipClass": "ProductOwner",
9 "relationshipClassVersion": 1,
10 "payload": {
11 "since": "2026-07-13"
12 },
13 "createdAt": "2026-07-13T09:20:00.000Z",
14 "updatedAt": "2026-07-13T09:20:00.000Z",
15 "eTag": "WxYzAbCdEfGhIj"
show all 23 lines

Other examples

Pass entityBId instead of entityAId to list relationships where the entity is on the second side of the link.

1try {
2 const response = await pubnub.dataSync.getRelationships({
3 class: 'ProductOwner',
4 entityBId: 'product-sneaker-42',
5 limit: 20,
6 })
7 console.log(response.data)
8} catch (error) {
9 console.log(error.status)
10}
Filter with filter

filter runs over properties declared with filtering mode full and is evaluated against eventually consistent storage, so a very recent write may not be matched yet. Reference a declared property by its name, not its path.

1try {
2 const response = await pubnub.dataSync.getRelationships({
3 class: 'ProductOwner',
4 filter: '!(tier == "platinum")',
5 })
6 console.log(response.data)
7} catch (error) {
8 console.log(error.status)
9}
Filter with filterFast

filterFast is evaluated against strongly consistent storage, so it matches an object you just wrote. It runs over properties declared with filtering mode simple or full and accepts up to 10 conditions by default (raisable per keyset). It shares the same expression language as filter, so only one of the two can be sent per call.

1try {
2 const response = await pubnub.dataSync.getRelationships({
3 class: 'ProductOwner',
4 filterFast: 'tier == "gold"',
5 })
6 console.log(response.data)
7} catch (error) {
8 console.log(error.status)
9}
Page through results with cursor

Pass no cursor on the first call. Take meta.next_cursor from the response and pass it back as cursor on the next call. Stop when meta.has_next is false.

1try {
2 let cursor
3 let hasNext = true
4 let page = 0
5
6 while (hasNext) {
7 const response = await pubnub.dataSync.getRelationships({
8 class: 'ProductOwner',
9 entityAId: 'seller-bob',
10 limit: 20,
11 cursor,
12 })
13 console.log(`Page ${++page}:`, response.data)
14 cursor = response.meta?.next_cursor
15 hasNext = response.meta?.has_next ?? false
show all 19 lines

Update relationship

Replaces a relationship in full (PUT). The linked entity ids are immutable, so resend the same entityAId and entityBId. Refer to optimistic concurrency with ETags for ifMatchesEtag.

Method(s)

1pubnub.dataSync.setRelationship({
2 id: string,
3 entityAId: string,
4 entityBId: string,
5 data: { classVersion: number, status: string, payload: object },
6 ifMatchesEtag: string,
7}): Promise<DataSync.SetRelationshipResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Relationship identifier.
entityAId *
Type: string
Default:
n/a
Identifier of the first linked entity. Must match the value set at creation.
entityBId *
Type: string
Default:
n/a
Identifier of the second linked entity. Must match the value set at creation.
data *
Type: object
Default:
n/a
The replacement relationship data. The relationship class is immutable and cannot be included.
> classVersion *
Type: number
Default:
n/a
Version of the relationship class schema.
> status
Type: string
Default:
n/a
Free-form lifecycle status. Max 100 characters.
> payload
Type: object
Default:
n/a
Free-form JSON object holding your application data.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The update succeeds only if it still matches, otherwise the server returns 412.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.setRelationship({
3 id: 'rel-bob-owns-sneaker-42',
4 entityAId: 'seller-bob',
5 entityBId: 'product-sneaker-42',
6 data: {
7 classVersion: 1,
8 payload: { since: '2026-07-13', tier: 'gold' },
9 },
10 })
11 console.log(response.data)
12} catch (error) {
13 console.log(error.status)
14}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "rel-bob-owns-sneaker-42",
5 "entityAId": "seller-bob",
6 "entityBId": "product-sneaker-42",
7 "relationshipClass": "ProductOwner",
8 "relationshipClassVersion": 1,
9 "payload": {
10 "since": "2026-07-13",
11 "tier": "gold"
12 },
13 "createdAt": "2026-07-13T09:20:00.000Z",
14 "updatedAt": "2026-07-13T14:00:00.000Z",
15 "eTag": "YzAbCdEfGhIjKl"
show all 17 lines

Patch relationship

Applies a partial update to a relationship. Paths can target fields inside payload or top-level stored fields. Refer to partial update for the JSON Pointer rules.

Method(s)

1pubnub.dataSync.updateRelationship({
2 id: string,
3 add: object,
4 replace: object,
5 remove: string[],
6 move: { from: string, path: string }[],
7 copy: { from: string, path: string }[],
8 test: object,
9 ifMatchesEtag: string,
10}): Promise<DataSync.UpdateRelationshipResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Relationship identifier.
add
Type: object
Default:
n/a
Full JSON Pointers mapped to values to add. Provide at least one patch operation.
replace
Type: object
Default:
n/a
Full JSON Pointers mapped to replacement values. Provide at least one patch operation.
remove
Type: array
Default:
n/a
Full JSON Pointers to remove. Provide at least one patch operation.
move
Type: array
Default:
n/a
Array of { from, path } JSON Pointer pairs. The value at from is removed and re-added at path.
copy
Type: array
Default:
n/a
Array of { from, path } JSON Pointer pairs. The value at from is duplicated to path.
test
Type: object
Default:
n/a
Full JSON Pointers mapped to expected values. The patch fails if any value does not match.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The patch succeeds only if it still matches, otherwise the server returns 412.

Use a JSON Pointer that starts with /payload/ to target payload fields, or a root pointer (for example /status) to target a top-level field. At least one of add, replace, remove, move, copy, or test must be provided.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.updateRelationship({
3 id: 'rel-bob-owns-sneaker-42',
4 replace: { '/payload/tier': 'platinum' },
5 })
6 console.log(response.data)
7} catch (error) {
8 console.log(error.status)
9}

Response

1{
2 "status": 200,
3 "data": {
4 "id": "rel-bob-owns-sneaker-42",
5 "entityAId": "seller-bob",
6 "entityBId": "product-sneaker-42",
7 "relationshipClass": "ProductOwner",
8 "relationshipClassVersion": 1,
9 "payload": {
10 "since": "2026-07-13",
11 "tier": "platinum"
12 },
13 "createdAt": "2026-07-13T09:20:00.000Z",
14 "updatedAt": "2026-07-13T14:05:00.000Z",
15 "eTag": "AbCdEfGhIjKlMn"
show all 17 lines

Remove relationship

Deletes a relationship by id.

Method(s)

1pubnub.dataSync.removeRelationship({
2 id: string,
3 ifMatchesEtag: string,
4}): Promise<DataSync.RemoveRelationshipResponse>
* required
ParameterDescription
id *
Type: string
Default:
n/a
Relationship identifier.
ifMatchesEtag
Type: string
Default:
n/a
The eTag from a prior read. The delete succeeds only if it still matches, otherwise the server returns 412.

Sample code

Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
1try {
2 const response = await pubnub.dataSync.removeRelationship({
3 id: 'rel-bob-owns-sneaker-42',
4 })
5 console.log(response.status)
6} catch (error) {
7 console.log(error.status)
8}

Response

1{
2 "status": 200
3}

Real-time updates

DataSync objects can publish create, update, and delete events that you receive in real time with a dataSync listener. Create a DataSync SDK entity for the object, subscribe to it, and attach the listener, described in the Add DataSync listener section of Publish and subscribe.

1const subscription = pubnub.dataSyncEntity('product-sneaker-42').subscription()
2subscription.onDataSync = (event) => console.log(event.message.event, event.message.data)
3subscription.subscribe()

Each event names the change in event, identifies the object kind in objectType (user, channel, membership, entity, or relationship), and carries the object state in data. For a delete event, data holds only the id and, when the service reports it, deletedAt.

Where each event is delivered

A relationship or membership change is never delivered on a channel named after the relationship or membership id, only on the ids of the two entities it links. A create for a user, channel, or entity is delivered only on its own id. An update or delete for a user, channel, or entity is also delivered on the id of every entity, user, or channel connected to it by a relationship or membership, in either direction, at the time of the change:

Change tocreate delivered onupdate / delete delivered on
A user
The user's id
The user's id, plus the id of every entity, user, or channel connected to it by a relationship or membership
A channel
The channel's id
The channel's id, plus the id of every entity, user, or channel connected to it by a relationship or membership
An entity
The entity's id
The entity's id, plus the id of every entity, user, or channel connected to it by a relationship or membership
A membership
Both the userId and the channelId of the membership
Both the userId and the channelId of the membership
A relationship
Both the entityAId and the entityBId of the relationship
Both the entityAId and the entityBId of the relationship

So to see memberships appear and disappear for Alice, observe user-alice rather than the membership id. Unlike entity/user/channel events, a membership or relationship create is delivered on both linked ids too, the same as its update/delete. A client observing both sides of the same link receives the change twice, once per channel. Deduplicate create and update events on data.id and data.updatedAt, and delete events on data.id and data.deletedAt.

Events are off by default and are enabled per class in the Admin Portal. Refer to enabling events for how to turn them on, and receiving events for the listener flow.