On this page

DataSync API for C# 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 C# SDK has no dedicated DataSync SDK entity. To observe a DataSync object you subscribe to its channel with an ordinary Channel SDK entity and attach a DataSync listener to the resulting Subscription, a SubscriptionSet, or the PubNub client. Refer to 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.

User and Channel entity classes and the Membership relationship class are predefined on every keyset, so you only need to define classes for your own custom entities and relationships.

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 method returns a PNResult<T> carrying the HTTP Status and, for everything except the Delete* methods, a Result holding the object or the list of objects. 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.

Request execution

Use try/catch when working with the C# SDK.

If a request has invalid parameters (for example, a missing required field), the SDK throws an exception. If the request reaches the server but fails (server error or network issue), the error details are available in the returned status.

1try
2{
3 PNResult<PNPublishResult> publishResponse = await pubnub.Publish()
4 .Message("Why do Java developers wear glasses? Because they can't C#.")
5 .Channel("my_channel")
6 .ExecuteAsync();
7
8 PNStatus status = publishResponse.Status;
9
10 Console.WriteLine("Server status code : " + status.StatusCode.ToString());
11}
12catch (Exception ex)
13{
14 Console.WriteLine($"Request can't be executed due to error: {ex.Message}");
15}
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 properties, all application data lives in the free-form Payload. Refer to users for the concept.

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(CreateUserParameters parameters)
* required
ParameterDescription
Id
Type: string
User identifier. Omit to let the server generate a UUID. Max 255 characters.
EntityClass
Type: string
Name of the user class this instance belongs to. Defaults to "User" on the server if omitted.
EntityClassVersion *
Type: int
Version of the user class schema.
EntityClassLevel
Type: string
Class hierarchy level, "Global" or "SubKey", used to disambiguate classes with the same name defined at different levels.
Status
Type: string
Free-form lifecycle status. Max 100 characters.
Payload
Type: Dictionary<string, object>
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{
3 PNResult<PNDataSyncUserResult> response = await pubnub.DataSync.CreateUser(new CreateUserParameters
4 {
5 Id = "user-alice",
6 EntityClassVersion = 1,
7 Payload = new Dictionary<string, object>
8 {
9 { "name", "Alice" },
10 { "type", "shopper" },
11 },
12 });
13
14 if (!response.Status.Error)
15 {
show all 26 lines

Response

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

Get user

Returns a single user by Id.

Method(s)

1pubnub.DataSync.GetUser(GetUserParameters parameters)
* required
ParameterDescription
Id *
Type: string
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{
3 PNResult<PNDataSyncUserResult> response = await pubnub.DataSync.GetUser(new GetUserParameters
4 {
5 Id = "user-alice",
6 });
7
8 if (!response.Status.Error)
9 {
10 Console.WriteLine(response.Result.Id);
11 }
12 else
13 {
14 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
15 }
show all 20 lines

Response

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

Get all users

Returns a paginated list of users. All parameters are optional, so you can call GetUsers with an empty parameters object. For pagination, filtering, and sorting, refer to sorting and pagination.

Method(s)

1pubnub.DataSync.GetUsers(GetUsersParameters parameters)
* required
ParameterDescription
EntityClass
Type: string
User class to list. Omit to list the Global User class and all of its subclasses.
Cursor
Type: string
Opaque pagination cursor. Omit for the first page.
Limit
Type: int
Maximum number of users per page. Default 20. Max 100.
FilterFast
Type: string
Strongly consistent filter expression, reflects the latest writes. Limited number of conditions. Cannot be combined with Filter.
Filter
Type: string
Eventually consistent filter expression, results can briefly lag writes. Full expression language. Cannot be combined with FilterFast.
Sort
Type: string
Comma-separated fields, each optionally suffixed with :asc or :desc (ascending is the default when a field has no suffix), for example "createdAt:desc,id".
EntityClassVersion
Type: int
User class version to list. Omit to list users across every version of the class.
EntityClassLevel
Type: string
Class hierarchy level, "Global" or "SubKey", used to disambiguate classes with the same name at different levels.

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{
3 PNResult<PNDataSyncUsersListResult> response = await pubnub.DataSync.GetUsers(new GetUsersParameters
4 {
5 Limit = 20,
6 Sort = "createdAt",
7 });
8
9 if (!response.Status.Error)
10 {
11 Console.WriteLine(response.Result.Data.Count);
12 }
13 else
14 {
15 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
show all 21 lines

Response

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

Other examples

Filter with FilterFast

FilterFast reflects the latest writes and runs over properties declared with filtering mode simple or full. Reference a declared property by its name, not its path.

1try
2{
3 PNResult<PNDataSyncUsersListResult> response = await pubnub.DataSync.GetUsers(new GetUsersParameters
4 {
5 FilterFast = "type == \"shopper\"",
6 });
7
8 if (!response.Status.Error)
9 {
10 Console.WriteLine(response.Result.Data.Count);
11 }
12 else
13 {
14 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
15 }
show all 20 lines
Filter with Filter

Filter runs over properties declared with filtering mode full and can briefly lag recent writes. It shares the same expression language as FilterFast, so only one of the two can be sent per call.

1try
2{
3 PNResult<PNDataSyncUsersListResult> response = await pubnub.DataSync.GetUsers(new GetUsersParameters
4 {
5 Filter = "name LIKE \"*Alice*\"",
6 });
7
8 if (!response.Status.Error)
9 {
10 Console.WriteLine(response.Result.Data.Count);
11 }
12 else
13 {
14 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
15 }
show all 20 lines
Page through results with Cursor

Pass no Cursor on the first call. Take Meta.NextCursor from the response and pass it back as Cursor on the next call. Stop when Meta.HasNext is false.

1try
2{
3 string cursor = null;
4 bool hasNext = true;
5 int page = 0;
6
7 while (hasNext)
8 {
9 PNResult<PNDataSyncUsersListResult> response = await pubnub.DataSync.GetUsers(new GetUsersParameters
10 {
11 FilterFast = "type == \"shopper\"",
12 Limit = 20,
13 Cursor = cursor,
14 });
15
show all 32 lines

Set 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 IfMatch (see optimistic concurrency with ETags).

Method(s)

1pubnub.DataSync.SetUser(SetUserParameters parameters)
* required
ParameterDescription
Id *
Type: string
User identifier.
EntityClassVersion *
Type: int
Version of the user class schema.
Status
Type: string
Free-form lifecycle status. Max 100 characters.
Payload
Type: Dictionary<string, object>
Free-form JSON object holding your application data.
IfMatch
Type: string
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{
3 PNResult<PNDataSyncUserResult> response = await pubnub.DataSync.SetUser(new SetUserParameters
4 {
5 Id = "user-alice",
6 EntityClassVersion = 1,
7 Payload = new Dictionary<string, object>
8 {
9 { "name", "Alice B." },
10 { "type", "shopper" },
11 },
12 IfMatch = "AbQdEfGhIjKlMn",
13 });
14
15 if (!response.Status.Error)
show all 27 lines

Response

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

Update user

Applies a partial update to a user with raw JSON Patch operations. Refer to partial update for the operation model.

Method(s)

1pubnub.DataSync.UpdateUser(UpdateUserParameters parameters)
* required
ParameterDescription
Id *
Type: string
User identifier.
Operations *
Type: List<JsonPatchOperation>
One or more JSON Patch operations. Must contain at least one item.
IfMatch
Type: string
The ETag from a prior read. The patch 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{
3 PNResult<PNDataSyncUserResult> response = await pubnub.DataSync.UpdateUser(new UpdateUserParameters
4 {
5 Id = "user-alice",
6 Operations = new List<JsonPatchOperation>
7 {
8 new JsonPatchOperation { Op = JsonPatchOperationType.Replace, Path = "/payload/name", Value = "Alice B." },
9 },
10 });
11
12 if (!response.Status.Error)
13 {
14 Console.WriteLine(response.Result.Id);
15 }
show all 24 lines

Response

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

Remove user

Deletes a user by Id.

Method(s)

1pubnub.DataSync.DeleteUser(DeleteUserParameters parameters)
* required
ParameterDescription
Id *
Type: string
User identifier.
IfMatch
Type: string
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{
3 PNResult<PNDataSyncDeleteUserResult> response = await pubnub.DataSync.DeleteUser(new DeleteUserParameters
4 {
5 Id = "user-alice",
6 });
7
8 Console.WriteLine(response.Status.StatusCode);
9}
10catch (Exception ex)
11{
12 Console.WriteLine($"Request can't be executed due to error: {ex.Message}");
13}

Response

1{
2 "status": 200
3}

Channels

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

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(CreateChannelParameters parameters)
* required
ParameterDescription
Id
Type: string
Channel identifier. Omit to let the server generate a UUID. Max 255 characters.
EntityClass
Type: string
Name of the channel class this instance belongs to. Defaults to "Channel" on the server if omitted.
EntityClassVersion *
Type: int
Version of the channel class schema.
EntityClassLevel
Type: string
Class hierarchy level, "Global" or "SubKey", used to disambiguate classes with the same name defined at different levels.
Status
Type: string
Free-form lifecycle status. Max 100 characters.
Payload
Type: Dictionary<string, object>
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{
3 PNResult<PNDataSyncChannelResult> response = await pubnub.DataSync.CreateChannel(new CreateChannelParameters
4 {
5 Id = "channel-summer-sale",
6 EntityClassVersion = 1,
7 Payload = new Dictionary<string, object>
8 {
9 { "name", "Summer Sale" },
10 { "type", "promotion" },
11 },
12 });
13
14 if (!response.Status.Error)
15 {
show all 26 lines

Response

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

Get channel

Returns a single channel by Id.

Method(s)

1pubnub.DataSync.GetChannel(GetChannelParameters parameters)
* required
ParameterDescription
Id *
Type: string
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{
3 PNResult<PNDataSyncChannelResult> response = await pubnub.DataSync.GetChannel(new GetChannelParameters
4 {
5 Id = "channel-summer-sale",
6 });
7
8 if (!response.Status.Error)
9 {
10 Console.WriteLine(response.Result.Id);
11 }
12 else
13 {
14 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
15 }
show all 20 lines

Response

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

Get all channels

Returns a paginated list of channels. All parameters are optional, so you can call GetChannels with an empty parameters object. For pagination, filtering, and sorting, refer to sorting and pagination.

Method(s)

1pubnub.DataSync.GetChannels(GetChannelsParameters parameters)
* required
ParameterDescription
EntityClass
Type: string
Channel class to list. Omit to list the Global Channel class and all of its subclasses.
Cursor
Type: string
Opaque pagination cursor. Omit for the first page.
Limit
Type: int
Maximum number of channels per page. Default 20. Max 100.
FilterFast
Type: string
Strongly consistent filter expression, reflects the latest writes. Limited number of conditions. Cannot be combined with Filter.
Filter
Type: string
Eventually consistent filter expression, results can briefly lag writes. Full expression language. Cannot be combined with FilterFast.
Sort
Type: string
Comma-separated fields, each optionally suffixed with :asc or :desc (ascending is the default when a field has no suffix).
EntityClassVersion
Type: int
Channel class version to list. Omit to list channels across every version of the class.
EntityClassLevel
Type: string
Class hierarchy level, "Global" or "SubKey", used to disambiguate classes with the same name at different levels.

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{
3 PNResult<PNDataSyncChannelsListResult> response = await pubnub.DataSync.GetChannels(new GetChannelsParameters
4 {
5 Limit = 20,
6 });
7
8 if (!response.Status.Error)
9 {
10 Console.WriteLine(response.Result.Data.Count);
11 }
12 else
13 {
14 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
15 }
show all 20 lines

Response

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

Other examples

Filter with FilterFast

FilterFast reflects the latest writes and runs over properties declared with filtering mode simple or full. Reference a declared property by its name, not its path.

1try
2{
3 PNResult<PNDataSyncChannelsListResult> response = await pubnub.DataSync.GetChannels(new GetChannelsParameters
4 {
5 FilterFast = "type == \"promotion\"",
6 });
7
8 if (!response.Status.Error)
9 {
10 Console.WriteLine(response.Result.Data.Count);
11 }
12 else
13 {
14 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
15 }
show all 20 lines
Filter with Filter

Filter runs over properties declared with filtering mode full and can briefly lag recent writes. It shares the same expression language as FilterFast, so only one of the two can be sent per call.

1try
2{
3 PNResult<PNDataSyncChannelsListResult> response = await pubnub.DataSync.GetChannels(new GetChannelsParameters
4 {
5 Filter = "name LIKE \"*Sale*\"",
6 });
7
8 if (!response.Status.Error)
9 {
10 Console.WriteLine(response.Result.Data.Count);
11 }
12 else
13 {
14 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
15 }
show all 20 lines
Page through results with Cursor

Pass no Cursor on the first call. Take Meta.NextCursor from the response and pass it back as Cursor on the next call. Stop when Meta.HasNext is false.

1try
2{
3 string cursor = null;
4 bool hasNext = true;
5 int page = 0;
6
7 while (hasNext)
8 {
9 PNResult<PNDataSyncChannelsListResult> response = await pubnub.DataSync.GetChannels(new GetChannelsParameters
10 {
11 FilterFast = "type == \"promotion\"",
12 Limit = 20,
13 Cursor = cursor,
14 });
15
show all 32 lines

Set channel

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

Method(s)

1pubnub.DataSync.SetChannel(SetChannelParameters parameters)
* required
ParameterDescription
Id *
Type: string
Channel identifier.
EntityClassVersion *
Type: int
Version of the channel class schema.
Status
Type: string
Free-form lifecycle status. Max 100 characters.
Payload
Type: Dictionary<string, object>
Free-form JSON object holding your application data.
IfMatch
Type: string
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{
3 PNResult<PNDataSyncChannelResult> response = await pubnub.DataSync.SetChannel(new SetChannelParameters
4 {
5 Id = "channel-summer-sale",
6 EntityClassVersion = 1,
7 Payload = new Dictionary<string, object>
8 {
9 { "name", "Summer Sale 2026" },
10 { "type", "promotion" },
11 },
12 });
13
14 if (!response.Status.Error)
15 {
show all 26 lines

Response

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

Update channel

Applies a partial update to a channel with raw JSON Patch operations. Refer to partial update for the operation model.

Method(s)

1pubnub.DataSync.UpdateChannel(UpdateChannelParameters parameters)
* required
ParameterDescription
Id *
Type: string
Channel identifier.
Operations *
Type: List<JsonPatchOperation>
One or more JSON Patch operations. Must contain at least one item.
IfMatch
Type: string
The ETag from a prior read. The patch 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{
3 PNResult<PNDataSyncChannelResult> response = await pubnub.DataSync.UpdateChannel(new UpdateChannelParameters
4 {
5 Id = "channel-summer-sale",
6 Operations = new List<JsonPatchOperation>
7 {
8 new JsonPatchOperation { Op = JsonPatchOperationType.Replace, Path = "/payload/name", Value = "Summer Sale 2026" },
9 },
10 });
11
12 if (!response.Status.Error)
13 {
14 Console.WriteLine(response.Result.Id);
15 }
show all 24 lines

Response

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

Remove channel

Deletes a channel by Id.

Method(s)

1pubnub.DataSync.DeleteChannel(DeleteChannelParameters parameters)
* required
ParameterDescription
Id *
Type: string
Channel identifier.
IfMatch
Type: string
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{
3 PNResult<PNDataSyncDeleteChannelResult> response = await pubnub.DataSync.DeleteChannel(new DeleteChannelParameters
4 {
5 Id = "channel-summer-sale",
6 });
7
8 Console.WriteLine(response.Status.StatusCode);
9}
10catch (Exception ex)
11{
12 Console.WriteLine($"Request can't be executed due to error: {ex.Message}");
13}

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.

Create membership

Creates a membership linking a user to a channel.

Method(s)

1pubnub.DataSync.CreateMembership(CreateMembershipParameters parameters)
* required
ParameterDescription
Id
Type: string
Membership identifier. Omit to let the server generate a UUID. Max 255 characters.
ChannelId *
Type: string
Identifier of the channel in the membership.
UserId *
Type: string
Identifier of the user in the membership.
RelationshipClassVersion *
Type: int
Version of the membership class schema.
Status
Type: string
Free-form lifecycle status. Max 100 characters.
Payload
Type: Dictionary<string, object>
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{
3 PNResult<PNDataSyncMembershipResult> response = await pubnub.DataSync.CreateMembership(new CreateMembershipParameters
4 {
5 ChannelId = "channel-summer-sale",
6 UserId = "user-alice",
7 RelationshipClassVersion = 1,
8 Payload = new Dictionary<string, object>
9 {
10 { "role", "viewer" },
11 },
12 });
13
14 if (!response.Status.Error)
15 {
show all 26 lines

Response

The response already exposes friendly ChannelId and UserId properties, you don't need to translate from a relationship shape. Those property names are specific to CRUD responses (PNDataSyncMembershipResult). A real-time membership event carries the same two ids as RelationshipData.EntityAId and RelationshipData.EntityBId instead, refer to Add DataSync listener for details.

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(GetMembershipParameters parameters)
* required
ParameterDescription
Id *
Type: string
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{
3 PNResult<PNDataSyncMembershipResult> response = await pubnub.DataSync.GetMembership(new GetMembershipParameters
4 {
5 Id = "membership-alice-summer-sale",
6 });
7
8 if (!response.Status.Error)
9 {
10 Console.WriteLine(response.Result.Id);
11 }
12 else
13 {
14 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
15 }
show all 20 lines

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, UserId and ChannelId can be set independently, together, or omitted entirely, the SDK doesn't require at least one of them. 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(GetMembershipsParameters parameters)
* required
ParameterDescription
Cursor
Type: string
Opaque pagination cursor. Omit for the first page.
Limit
Type: int
Maximum number of memberships per page. Default 20. Max 100.
FilterFast
Type: string
Strongly consistent filter expression, reflects the latest writes. Limited number of conditions. Cannot be combined with Filter.
Filter
Type: string
Eventually consistent filter expression, results can briefly lag writes. Full expression language. Cannot be combined with FilterFast.
Sort
Type: string
Comma-separated fields, each optionally suffixed with :asc or :desc (ascending is the default when a field has no suffix).
UserId
Type: string
List only memberships for this user.
ChannelId
Type: string
List only memberships for this channel.
RelationshipClassVersion
Type: int
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{
3 PNResult<PNDataSyncMembershipsListResult> response = await pubnub.DataSync.GetMemberships(new GetMembershipsParameters
4 {
5 UserId = "user-alice",
6 Limit = 20,
7 });
8
9 if (!response.Status.Error)
10 {
11 Console.WriteLine(response.Result.Data.Count);
12 }
13 else
14 {
15 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
show all 21 lines

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{
3 PNResult<PNDataSyncMembershipsListResult> response = await pubnub.DataSync.GetMemberships(new GetMembershipsParameters
4 {
5 ChannelId = "channel-summer-sale",
6 Limit = 20,
7 });
8
9 if (!response.Status.Error)
10 {
11 Console.WriteLine(response.Result.Data.Count);
12 }
13 else
14 {
15 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
show all 21 lines
Filter with FilterFast

FilterFast reflects the latest writes and runs over properties declared with filtering mode simple or full. Reference a declared property by its name, not its path.

1try
2{
3 PNResult<PNDataSyncMembershipsListResult> response = await pubnub.DataSync.GetMemberships(new GetMembershipsParameters
4 {
5 UserId = "user-alice",
6 FilterFast = "role == \"viewer\"",
7 });
8
9 if (!response.Status.Error)
10 {
11 Console.WriteLine(response.Result.Data.Count);
12 }
13 else
14 {
15 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
show all 21 lines
Filter with Filter

Filter runs over properties declared with filtering mode full and can briefly lag recent writes. It shares the same expression language as FilterFast, so only one of the two can be sent per call.

1try
2{
3 PNResult<PNDataSyncMembershipsListResult> response = await pubnub.DataSync.GetMemberships(new GetMembershipsParameters
4 {
5 ChannelId = "channel-summer-sale",
6 Filter = "role LIKE \"*mod*\"",
7 });
8
9 if (!response.Status.Error)
10 {
11 Console.WriteLine(response.Result.Data.Count);
12 }
13 else
14 {
15 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
show all 21 lines
Page through results with Cursor

Pass no Cursor on the first call. Take Meta.NextCursor from the response and pass it back as Cursor on the next call. Stop when Meta.HasNext is false.

1try
2{
3 string cursor = null;
4 bool hasNext = true;
5 int page = 0;
6
7 while (hasNext)
8 {
9 PNResult<PNDataSyncMembershipsListResult> response = await pubnub.DataSync.GetMemberships(new GetMembershipsParameters
10 {
11 UserId = "user-alice",
12 Limit = 20,
13 Cursor = cursor,
14 });
15
show all 32 lines

Set membership

Replaces a membership in full (PUT). The linked user and channel are immutable, so UpdateMembershipParameters doesn't expose ChannelId/UserId at all, there's nothing to resend. Refer to optimistic concurrency with ETags for IfMatch.

Parameter class naming

Unlike Users, Channels, Entities, and Relationships, the Membership parameter class names don't follow the Set<X>Parameters / Update<X>Parameters pattern. The full-replace method SetMembership takes UpdateMembershipParameters, and the partial-patch method UpdateMembership (below) takes PatchMembershipParameters. This is how the SDK is implemented, call the methods and parameter classes exactly as shown.

Method(s)

1pubnub.DataSync.SetMembership(UpdateMembershipParameters parameters)
* required
ParameterDescription
Id *
Type: string
Membership identifier.
RelationshipClassVersion *
Type: int
Version of the membership class schema.
Status
Type: string
Free-form lifecycle status. Max 100 characters.
Payload
Type: Dictionary<string, object>
Free-form JSON object holding your application data.
IfMatch
Type: string
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{
3 PNResult<PNDataSyncMembershipResult> response = await pubnub.DataSync.SetMembership(new UpdateMembershipParameters
4 {
5 Id = "membership-alice-summer-sale",
6 RelationshipClassVersion = 1,
7 Payload = new Dictionary<string, object>
8 {
9 { "role", "moderator" },
10 },
11 });
12
13 if (!response.Status.Error)
14 {
15 Console.WriteLine(response.Result.Id);
show all 25 lines

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

Update membership

Applies a partial update to a membership with raw JSON Patch operations. Refer to partial update for the operation model. See the note above about the parameter class naming for Memberships.

Method(s)

1pubnub.DataSync.UpdateMembership(PatchMembershipParameters parameters)
* required
ParameterDescription
Id *
Type: string
Membership identifier.
Operations *
Type: List<JsonPatchOperation>
One or more JSON Patch operations. Must contain at least one item.
IfMatch
Type: string
The ETag from a prior read. The patch 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{
3 PNResult<PNDataSyncMembershipResult> response = await pubnub.DataSync.UpdateMembership(new PatchMembershipParameters
4 {
5 Id = "membership-alice-summer-sale",
6 Operations = new List<JsonPatchOperation>
7 {
8 new JsonPatchOperation { Op = JsonPatchOperationType.Replace, Path = "/payload/role", Value = "moderator" },
9 },
10 });
11
12 if (!response.Status.Error)
13 {
14 Console.WriteLine(response.Result.Id);
15 }
show all 24 lines

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": "PqRsTuVwXyZaBc"
15 }
show all 16 lines

Remove membership

Deletes a membership by Id.

Method(s)

1pubnub.DataSync.DeleteMembership(DeleteMembershipParameters parameters)
* required
ParameterDescription
Id *
Type: string
Membership identifier.
IfMatch
Type: string
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{
3 PNResult<PNDataSyncDeleteMembershipResult> response = await pubnub.DataSync.DeleteMembership(new DeleteMembershipParameters
4 {
5 Id = "membership-alice-summer-sale",
6 });
7
8 Console.WriteLine(response.Status.StatusCode);
9}
10catch (Exception ex)
11{
12 Console.WriteLine($"Request can't be executed due to error: {ex.Message}");
13}

Response

1{
2 "status": 200
3}

Entities

Entities are instances of the custom classes you define in the Admin Portal. In the running example, product is a class and product-sneaker-42 is an instance. Refer to entities for the concept, and managing classes for how classes are defined.

Create entity

Creates an entity of a given class and version.

Method(s)

1pubnub.DataSync.CreateEntity(CreateEntityParameters parameters)
* required
ParameterDescription
Id
Type: string
Entity identifier. Omit to let the server generate a UUID. Max 255 characters.
EntityClass *
Type: string
Name of the entity class this instance belongs to.
EntityClassVersion *
Type: int
Version of the entity class schema.
EntityClassLevel
Type: string
Class hierarchy level, "Global" or "SubKey", used to disambiguate classes with the same name defined at different levels.
Status
Type: string
Free-form lifecycle status. Max 100 characters.
Payload
Type: Dictionary<string, object>
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{
3 PNResult<PNDataSyncEntityResult> response = await pubnub.DataSync.CreateEntity(new CreateEntityParameters
4 {
5 Id = "product-sneaker-42",
6 EntityClass = "product",
7 EntityClassVersion = 1,
8 Payload = new Dictionary<string, object>
9 {
10 { "name", "Retro Sneaker" },
11 { "price", 89.99 },
12 { "stock", 12 },
13 },
14 });
15
show all 28 lines

Response

1{
2 "status": 200,
3 "data": {
4 "id": "product-sneaker-42",
5 "entityClass": "product",
6 "entityClassVersion": 1,
7 "entityClassLevel": "SubKey",
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 17 lines

Get entity

Returns a single entity by Id.

Method(s)

1pubnub.DataSync.GetEntity(GetEntityParameters parameters)
* required
ParameterDescription
Id *
Type: string
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{
3 PNResult<PNDataSyncEntityResult> response = await pubnub.DataSync.GetEntity(new GetEntityParameters
4 {
5 Id = "product-sneaker-42",
6 });
7
8 if (!response.Status.Error)
9 {
10 Console.WriteLine(response.Result.Id);
11 }
12 else
13 {
14 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
15 }
show all 20 lines

Response

1{
2 "status": 200,
3 "data": {
4 "id": "product-sneaker-42",
5 "entityClass": "product",
6 "entityClassVersion": 1,
7 "entityClassLevel": "SubKey",
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 17 lines

Get all entities

Returns a paginated list of entities within a class. The EntityClass 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(GetEntitiesParameters parameters)
* required
ParameterDescription
EntityClass *
Type: string
Name of the entity class to list.
EntityClassVersion
Type: int
Entity class version to list. Omit to list entities across every version of the class.
EntityClassLevel
Type: string
Class hierarchy level, "Global" or "SubKey", used to disambiguate classes with the same name at different levels.
Cursor
Type: string
Opaque pagination cursor. Omit for the first page.
Limit
Type: int
Maximum number of entities per page. Default 20. Max 100.
FilterFast
Type: string
Strongly consistent filter expression, reflects the latest writes. Limited number of conditions. Cannot be combined with Filter.
Filter
Type: string
Eventually consistent filter expression, results can briefly lag writes. Full expression language. Cannot be combined with FilterFast.
Sort
Type: string
Comma-separated fields, each optionally suffixed with :asc or :desc (ascending is the default when a field has no suffix).

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{
3 PNResult<PNDataSyncEntitiesListResult> response = await pubnub.DataSync.GetEntities(new GetEntitiesParameters
4 {
5 EntityClass = "product",
6 Sort = "price:desc",
7 Limit = 20,
8 });
9
10 if (!response.Status.Error)
11 {
12 Console.WriteLine(response.Result.Data.Count);
13 }
14 else
15 {
show all 22 lines

Response

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

Other examples

Filter with FilterFast

FilterFast reflects the latest writes and runs over properties declared with filtering mode simple or full. Reference a declared property by its name (not its path), and combine conditions with && and ||.

1try
2{
3 PNResult<PNDataSyncEntitiesListResult> response = await pubnub.DataSync.GetEntities(new GetEntitiesParameters
4 {
5 EntityClass = "product",
6 FilterFast = "price < 100 && stock > 0",
7 });
8
9 if (!response.Status.Error)
10 {
11 Console.WriteLine(response.Result.Data.Count);
12 }
13 else
14 {
15 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
show all 21 lines
Filter with Filter

Filter runs over properties declared with filtering mode full and can briefly lag recent writes. It shares the same expression language as FilterFast, so only one of the two can be sent per call.

1try
2{
3 PNResult<PNDataSyncEntitiesListResult> response = await pubnub.DataSync.GetEntities(new GetEntitiesParameters
4 {
5 EntityClass = "product",
6 Filter = "name LIKE \"*sneaker*\" && !(status == \"discontinued\")",
7 });
8
9 if (!response.Status.Error)
10 {
11 Console.WriteLine(response.Result.Data.Count);
12 }
13 else
14 {
15 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
show all 21 lines
Page through results with Cursor

Pass no Cursor on the first call. Take Meta.NextCursor from the response and pass it back as Cursor on the next call. Stop when Meta.HasNext is false.

1try
2{
3 string cursor = null;
4 bool hasNext = true;
5 int page = 0;
6
7 while (hasNext)
8 {
9 PNResult<PNDataSyncEntitiesListResult> response = await pubnub.DataSync.GetEntities(new GetEntitiesParameters
10 {
11 EntityClass = "product",
12 FilterFast = "price < 100",
13 Limit = 20,
14 Cursor = cursor,
15 });
show all 33 lines

Set entity

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

Method(s)

1pubnub.DataSync.SetEntity(SetEntityParameters parameters)
* required
ParameterDescription
Id *
Type: string
Entity identifier.
EntityClassVersion *
Type: int
Version of the entity class schema.
Status
Type: string
Free-form lifecycle status. Max 100 characters.
Payload
Type: Dictionary<string, object>
Free-form JSON object holding your application data.
IfMatch
Type: string
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{
3 PNResult<PNDataSyncEntityResult> response = await pubnub.DataSync.SetEntity(new SetEntityParameters
4 {
5 Id = "product-sneaker-42",
6 EntityClassVersion = 1,
7 Payload = new Dictionary<string, object>
8 {
9 { "name", "Retro Sneaker" },
10 { "price", 79.99 },
11 { "stock", 8 },
12 },
13 });
14
15 if (!response.Status.Error)
show all 27 lines

Response

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

Update entity

Applies a partial update to an entity with raw JSON Patch operations. Refer to partial update for the operation model.

Method(s)

1pubnub.DataSync.UpdateEntity(UpdateEntityParameters parameters)
* required
ParameterDescription
Id *
Type: string
Entity identifier.
Operations *
Type: List<JsonPatchOperation>
One or more JSON Patch operations. Must contain at least one item.
IfMatch
Type: string
The ETag from a prior read. The patch 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{
3 PNResult<PNDataSyncEntityResult> response = await pubnub.DataSync.UpdateEntity(new UpdateEntityParameters
4 {
5 Id = "product-sneaker-42",
6 Operations = new List<JsonPatchOperation>
7 {
8 new JsonPatchOperation { Op = JsonPatchOperationType.Replace, Path = "/payload/price", Value = 79.99 },
9 },
10 });
11
12 if (!response.Status.Error)
13 {
14 Console.WriteLine(response.Result.Id);
15 }
show all 24 lines

Response

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

Other examples

Combine patch operations, and guard the write with IfMatch

A single UpdateEntity call can mix Add, Replace, Remove, Move, Copy, and Test in one Operations list. All operations in the call apply together or not at all. Add IfMatch (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{
3 PNResult<PNDataSyncEntityResult> response = await pubnub.DataSync.UpdateEntity(new UpdateEntityParameters
4 {
5 Id = "product-sneaker-42",
6 Operations = new List<JsonPatchOperation>
7 {
8 new JsonPatchOperation { Op = JsonPatchOperationType.Test, Path = "/payload/stock", Value = 8 },
9 new JsonPatchOperation { Op = JsonPatchOperationType.Replace, Path = "/payload/price", Value = 74.99 },
10 new JsonPatchOperation { Op = JsonPatchOperationType.Add, Path = "/payload/tags/-", Value = "clearance" },
11 new JsonPatchOperation { Op = JsonPatchOperationType.Remove, Path = "/payload/legacy/field" },
12 new JsonPatchOperation { Op = JsonPatchOperationType.Move, Path = "/payload/displayName", From = "/payload/legacyName" },
13 new JsonPatchOperation { Op = JsonPatchOperationType.Copy, Path = "/payload/previousName", From = "/payload/displayName" },
14 },
15 IfMatch = "StUvWxYzAbCdEf",
show all 30 lines

Remove entity

Deletes an entity by Id.

Method(s)

1pubnub.DataSync.DeleteEntity(DeleteEntityParameters parameters)
* required
ParameterDescription
Id *
Type: string
Entity identifier.
IfMatch
Type: string
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{
3 PNResult<PNDataSyncDeleteEntityResult> response = await pubnub.DataSync.DeleteEntity(new DeleteEntityParameters
4 {
5 Id = "product-sneaker-42",
6 });
7
8 Console.WriteLine(response.Status.StatusCode);
9}
10catch (Exception ex)
11{
12 Console.WriteLine($"Request can't be executed due to error: {ex.Message}");
13}

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. Refer to relationships for the concept.

Create relationship

Creates a relationship between two entities.

Method(s)

1pubnub.DataSync.CreateRelationship(CreateRelationshipParameters parameters)
* required
ParameterDescription
Id
Type: string
Relationship identifier. Omit to let the server generate a UUID. Max 255 characters.
EntityAId *
Type: string
Identifier of the first linked entity. Immutable after creation.
EntityBId *
Type: string
Identifier of the second linked entity. Immutable after creation.
RelationshipClass *
Type: string
Name of the relationship class this instance belongs to.
RelationshipClassVersion *
Type: int
Version of the relationship class schema.
Status
Type: string
Free-form lifecycle status. Max 100 characters.
Payload
Type: Dictionary<string, object>
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{
3 PNResult<PNDataSyncRelationshipResult> response = await pubnub.DataSync.CreateRelationship(new CreateRelationshipParameters
4 {
5 EntityAId = "seller-bob",
6 EntityBId = "product-sneaker-42",
7 RelationshipClass = "ProductOwner",
8 RelationshipClassVersion = 1,
9 Payload = new Dictionary<string, object>
10 {
11 { "since", "2026-07-13" },
12 },
13 });
14
15 if (!response.Status.Error)
show all 27 lines

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(GetRelationshipParameters parameters)
* required
ParameterDescription
Id *
Type: string
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{
3 PNResult<PNDataSyncRelationshipResult> response = await pubnub.DataSync.GetRelationship(new GetRelationshipParameters
4 {
5 Id = "rel-bob-owns-sneaker-42",
6 });
7
8 if (!response.Status.Error)
9 {
10 Console.WriteLine(response.Result.Id);
11 }
12 else
13 {
14 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
15 }
show all 20 lines

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 RelationshipClass 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(GetRelationshipsParameters parameters)
* required
ParameterDescription
RelationshipClass *
Type: string
Name of the relationship class to list.
RelationshipClassVersion
Type: int
Relationship class version to list. Omit to list relationships across every version of the class.
EntityAId
Type: string
List only relationships whose first entity is this id.
EntityBId
Type: string
List only relationships whose second entity is this id.
Cursor
Type: string
Opaque pagination cursor. Omit for the first page.
Limit
Type: int
Maximum number of relationships per page. Default 20. Max 100.
FilterFast
Type: string
Strongly consistent filter expression, reflects the latest writes. Limited number of conditions. Cannot be combined with Filter.
Filter
Type: string
Eventually consistent filter expression, results can briefly lag writes. Full expression language. Cannot be combined with FilterFast.
Sort
Type: string
Comma-separated fields, each optionally suffixed with :asc or :desc (ascending is the default when a field has no suffix).

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{
3 PNResult<PNDataSyncRelationshipsListResult> response = await pubnub.DataSync.GetRelationships(new GetRelationshipsParameters
4 {
5 RelationshipClass = "ProductOwner",
6 EntityAId = "seller-bob",
7 Limit = 20,
8 });
9
10 if (!response.Status.Error)
11 {
12 Console.WriteLine(response.Result.Data.Count);
13 }
14 else
15 {
show all 22 lines

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{
3 PNResult<PNDataSyncRelationshipsListResult> response = await pubnub.DataSync.GetRelationships(new GetRelationshipsParameters
4 {
5 RelationshipClass = "ProductOwner",
6 EntityBId = "product-sneaker-42",
7 Limit = 20,
8 });
9
10 if (!response.Status.Error)
11 {
12 Console.WriteLine(response.Result.Data.Count);
13 }
14 else
15 {
show all 22 lines
Filter with FilterFast

FilterFast reflects the latest writes and runs over properties declared with filtering mode simple or full. Reference a declared property by its name, not its path.

1try
2{
3 PNResult<PNDataSyncRelationshipsListResult> response = await pubnub.DataSync.GetRelationships(new GetRelationshipsParameters
4 {
5 RelationshipClass = "ProductOwner",
6 FilterFast = "tier == \"gold\"",
7 });
8
9 if (!response.Status.Error)
10 {
11 Console.WriteLine(response.Result.Data.Count);
12 }
13 else
14 {
15 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
show all 21 lines
Filter with Filter

Filter runs over properties declared with filtering mode full and can briefly lag recent writes. It shares the same expression language as FilterFast, so only one of the two can be sent per call.

1try
2{
3 PNResult<PNDataSyncRelationshipsListResult> response = await pubnub.DataSync.GetRelationships(new GetRelationshipsParameters
4 {
5 RelationshipClass = "ProductOwner",
6 Filter = "!(tier == \"platinum\")",
7 });
8
9 if (!response.Status.Error)
10 {
11 Console.WriteLine(response.Result.Data.Count);
12 }
13 else
14 {
15 Console.WriteLine($"Request can't be executed due to error: {response.Status.ErrorData.Information}");
show all 21 lines
Page through results with Cursor

Pass no Cursor on the first call. Take Meta.NextCursor from the response and pass it back as Cursor on the next call. Stop when Meta.HasNext is false.

1try
2{
3 string cursor = null;
4 bool hasNext = true;
5 int page = 0;
6
7 while (hasNext)
8 {
9 PNResult<PNDataSyncRelationshipsListResult> response = await pubnub.DataSync.GetRelationships(new GetRelationshipsParameters
10 {
11 RelationshipClass = "ProductOwner",
12 EntityAId = "seller-bob",
13 Limit = 20,
14 Cursor = cursor,
15 });
show all 33 lines

Set relationship

Replaces a relationship in full (PUT). The linked entities are immutable, so SetRelationshipParameters doesn't expose EntityAId/EntityBId at all, there's nothing to resend. Refer to optimistic concurrency with ETags for IfMatch.

Method(s)

1pubnub.DataSync.SetRelationship(SetRelationshipParameters parameters)
* required
ParameterDescription
Id *
Type: string
Relationship identifier.
RelationshipClassVersion *
Type: int
Version of the relationship class schema.
Status
Type: string
Free-form lifecycle status. Max 100 characters.
Payload
Type: Dictionary<string, object>
Free-form JSON object holding your application data.
IfMatch
Type: string
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{
3 PNResult<PNDataSyncRelationshipResult> response = await pubnub.DataSync.SetRelationship(new SetRelationshipParameters
4 {
5 Id = "rel-bob-owns-sneaker-42",
6 RelationshipClassVersion = 1,
7 Payload = new Dictionary<string, object>
8 {
9 { "since", "2026-07-13" },
10 { "tier", "gold" },
11 },
12 });
13
14 if (!response.Status.Error)
15 {
show all 26 lines

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

Update relationship

Applies a partial update to a relationship with raw JSON Patch operations. Refer to partial update for the operation model.

Method(s)

1pubnub.DataSync.UpdateRelationship(UpdateRelationshipParameters parameters)
* required
ParameterDescription
Id *
Type: string
Relationship identifier.
Operations *
Type: List<JsonPatchOperation>
One or more JSON Patch operations. Must contain at least one item.
IfMatch
Type: string
The ETag from a prior read. The patch 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{
3 PNResult<PNDataSyncRelationshipResult> response = await pubnub.DataSync.UpdateRelationship(new UpdateRelationshipParameters
4 {
5 Id = "rel-bob-owns-sneaker-42",
6 Operations = new List<JsonPatchOperation>
7 {
8 new JsonPatchOperation { Op = JsonPatchOperationType.Replace, Path = "/payload/tier", Value = "platinum" },
9 },
10 });
11
12 if (!response.Status.Error)
13 {
14 Console.WriteLine(response.Result.Id);
15 }
show all 24 lines

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.DeleteRelationship(DeleteRelationshipParameters parameters)
* required
ParameterDescription
Id *
Type: string
Relationship identifier.
IfMatch
Type: string
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{
3 PNResult<PNDataSyncDeleteRelationshipResult> response = await pubnub.DataSync.DeleteRelationship(new DeleteRelationshipParameters
4 {
5 Id = "rel-bob-owns-sneaker-42",
6 });
7
8 Console.WriteLine(response.Status.StatusCode);
9}
10catch (Exception ex)
11{
12 Console.WriteLine($"Request can't be executed due to error: {ex.Message}");
13}

Response

1{
2 "status": 200
3}

Real-time updates

DataSync objects can publish create, update, and delete events that you receive in real time by subclassing SubscribeCallback (or using SubscribeCallbackExt) and overriding DataSyncEvent. To receive them, subscribe to the object's data channel with a Channel SDK entity and register your listener on the resulting Subscription, a SubscriptionSet, or the PubNub client, as described in Add DataSync listener.

1Channel channel = pubnub.Channel("product-sneaker-42");
2Subscription subscription = channel.Subscription();
3
4subscription.AddListener(new SubscribeCallbackExt(
5 (Pubnub pn, PNDataSyncEventResult dataSyncEvent) =>
6 {
7 string changedId = dataSyncEvent.EntityData?.Id
8 ?? dataSyncEvent.RelationshipData?.Id
9 ?? dataSyncEvent.Id;
10 Console.WriteLine($"{dataSyncEvent.Event} {changedId}");
11 },
12 (Pubnub pn, PNStatus status) => { }));
13
14subscription.Subscribe<object>();

Each event names the change in Event, identifies the object kind in Type, and carries the object state in EntityData or RelationshipData. For a delete event, neither is populated and the id arrives as Id on the event itself, alongside DeletedAt.

Type names the object kind directly, while the state always arrives in one of the two containers:

Change toTypeState arrives in
A user
user
EntityData
A channel
channel
EntityData
An entity
entity
EntityData
A membership
membership
RelationshipData
A relationship
relationship
RelationshipData

Dispatch on Type rather than on which container is populated, because EntityData is shared by three kinds and RelationshipData by two. ClassName tells you the specific class within a kind, for example which subclass of User an event belongs to.

Where each event is delivered

The channels that receive an event depend on the changed object. A relationship or membership never publishes on a channel named after its own id:

Change toOn createOn update or delete
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
Same as create
A relationship
Both the EntityAId and the EntityBId of the relationship
Same as create
Update and delete also fan out to connected objects

An update or delete on a user, channel, or entity is also delivered on the Id channel of every other object connected to it by a relationship or membership, in either direction, at the time of the change. A client subscribed to a connected object's channel receives the event too, even though that object itself did not change. A create does not fan out this way, because nothing can be connected to a brand new object yet.

The connected channels are entity, user, and channel ids. Relationships and memberships never get an id channel of their own, so they never appear in this fan-out as a channel name.

So to see memberships appear and disappear for Alice, subscribe to user-alice rather than the membership id. A client subscribed to both sides of the same link receives the change twice, once per channel. Deduplicate create and update events on the object's Id and UpdatedAt, and delete events on Id and DeletedAt.

For a membership event, RelationshipData.EntityAId is the channel id and RelationshipData.EntityBId is the user id. The service sends these as channelId and userId, and the SDK maps them onto the A and B sides so that one result type serves both memberships and your own relationships.

Projection channels

Each projection declared on the class gets its own event, published to its own channel, and carrying only that projection's view of the payload:

ProjectionChannelPayload
__default__ (the base projection)
The object's own id, for example product-sneaker-42
The fields tagged __default__
Any named projection, for example admin
__<projection>__<id>, for example __admin__product-sneaker-42
The fields tagged with that projection

Filtering applies to Payload and to Status: each is included in a given projection's event only if the class declares it under that projection, falling back to __default__ when the class never declares Status. Id, ETag, CreatedAt, UpdatedAt, and ExpiresAt are never filtered. Every projection's event carries them unchanged.

So one change to an object whose class declares an admin projection publishes two events, and a client subscribed only to product-sneaker-42 never sees the admin-only fields:

1// Base projection: the __default__ view of the payload.
2Subscription baseSubscription = pubnub.Channel("product-sneaker-42").Subscription();
3
4// admin projection: the admin view of the payload.
5Subscription adminSubscription = pubnub.Channel("__admin__product-sneaker-42").Subscription();

If the 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 DataSyncProjections entries in a token. Use Access Manager Channels grants to control who can subscribe to a projection channel. Anyone who can subscribe there receives the restricted fields. Refer to Grant token.

Delete events are never filtered by a projection.

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.