---
source_url: https://www.pubnub.com/docs/sdks/go/api-reference/data-sync
title: DataSync API for Go SDK
updated_at: 2026-09-22T12:01:30.000Z
sdk_name: PubNub Go SDK
sdk_version: v10.0.0
---

# DataSync API for Go SDK

PubNub Go SDK, use the latest version: v10.0.0

Install:

```bash
go get github.com/pubnub/go/v10@v10.0.0
```

## Documentation index

To discover more PubNub resources:

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

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

[DataSync](https://www.pubnub.com/docs/general/data-sync/overview.md) 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](https://www.pubnub.com/docs/sdks/go/api-reference/objects.md).

:::note 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 `pn.DataSync.*` methods documented here.
The Go SDK has no client-side SDK entity concept. A channel ID is just the string you pass to `Subscribe()`, it carries no stored state of its own. To observe a DataSync object in real time, subscribe to its channel and attach a DataSync listener to the PubNub client, as described in [Real-time updates](#real-time-updates).
:::

The classes that your objects conform to (their types and schemas) are defined through the Admin API or the [Admin Portal](https://admin.pubnub.com/), not through this SDK:

* Entity classes, which back users, channels, and custom entities: [list](https://www.pubnub.com/docs/admin-api/get-all-entity-class-entries.md), [read](https://www.pubnub.com/docs/admin-api/get-entity-class-by-id.md), [create](https://www.pubnub.com/docs/admin-api/create-a-new-entity-class.md), [replace](https://www.pubnub.com/docs/admin-api/update-entity-class-with-complete-resource-replacement.md), and [delete](https://www.pubnub.com/docs/admin-api/delete-entity-class-by-id.md). Partial updates aren't implemented; replace the complete class version instead.
* Relationship classes, which back memberships and custom relationships: [list](https://www.pubnub.com/docs/admin-api/get-all-relationship-classes.md), [read](https://www.pubnub.com/docs/admin-api/get-relationship-class-by-name-and-version.md), [create](https://www.pubnub.com/docs/admin-api/create-a-new-relationship-class.md), [replace](https://www.pubnub.com/docs/admin-api/update-relationship-class-with-complete-resource-replacement.md), and [delete](https://www.pubnub.com/docs/admin-api/delete-relationship-class-by-name-and-version.md).

`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](https://www.pubnub.com/docs/sdks/go/api-reference/access-manager.md#grant-token) each field belongs to, and how long the object lives before it expires. Refer to [managing classes](https://www.pubnub.com/docs/general/data-sync/schemas-and-validation.md#managing-classes) for more information.

Every DataSync method returns three values from `Execute()`: a typed response struct, a `StatusResponse` carrying the HTTP `StatusCode` and other PubNub-specific status details, and an `error`. The `RemoveUser`, `RemoveChannel`, `RemoveMembership`, `RemoveEntity`, and `RemoveRelationship` methods return a response holding only `Status`, every other method adds `Data` holding the object, or the slice of objects for a list method, and a list method can add `Links` 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.

###### Authorization

Every DataSync request must be [authorized](https://www.pubnub.com/docs/general/data-sync/access-control.md#authorizing-requests) 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`.

###### Pagination

DataSync pagination is **forward-only**. There is no previous-page cursor, and `PNEntityPaginationMeta` exposes no `PrevCursor` or `HasPrev`. To revisit an earlier page, page from the start again.

All list methods (`GetUsers`, `GetChannels`, `GetMemberships`, `GetEntities`, and `GetRelationships`) accept `Cursor` and `Limit` (default `20`, max `100`) and can return a `Meta` object with `HasNext`, `NextCursor`, and `Limit`. The `Meta` object is a pointer and can be `nil`, so guard the access when you read it, for example checking `response.Meta != nil` before reading `response.Meta.HasNext`.

To page forward, pass the returned `Meta.NextCursor` back as `Cursor` on the next call, and stop when `Meta.HasNext` is `false`. `NextCursor` is an empty string on the last page. Refer to [sorting and pagination](https://www.pubnub.com/docs/general/data-sync/data-operations.md#sorting-and-pagination) for details.

###### Filtering and sorting

All list methods accept two mutually exclusive filter parameters, `FilterFast` and `Filter`. The SDK sends whatever you set, so a call that carries both reaches the server and fails there with a `400`.

| Parameter | Consistency | Properties it can read | Expression complexity |
| --- | --- | --- | --- |
| `FilterFast` | Strongly consistent, reflects the latest writes | Filtering mode `simple` or `full` | Up to 10 conditions by default (raisable per keyset) |
| `Filter` | Eventually consistent, results can briefly lag writes | Filtering mode `full` only | Full expression language |

Reach for `FilterFast` when the query has to see an object you just wrote, and for `Filter` when you need the full expression language over a `full`-indexed property.

:::warning danger
Filter
is not the strongly consistent one
The two names read the wrong way round if you assume `Filter` is the basic option. `FilterFast` is the strongly consistent, limited one. `Filter` is the richer, eventually consistent one.
:::

Both parameters share the same expression language, a string built from a property name, an operator, and a value:

| Operators |  |
| --- | --- |
| Comparison | `==`, `!=`, `<`, `>`, `<=`, `>=` |
| Pattern matching | `LIKE` (case-insensitive), `SLIKE` (case-sensitive), `ILIKE` (case-insensitive, same as `LIKE`) |
| Logical | `&&`, `||`, `!`, and parentheses for grouping |

Values are quoted strings, numbers, `true`, `false`, or `null`. Pattern operators (`LIKE`, `SLIKE`, `ILIKE`) apply to string properties only, and `null` is only valid with `==` and `!=`. Reference a property by its `name`, not its declared `path`, and only properties declared on the class are filterable. To reach a nested `payload` property, declare it as a class property first, then filter by that property's `name`, for example `"price < 100"` for a `product` class that declares `price`.

Some examples:

```go
FilterFast("price < 100")                              // strongly consistent, single condition
FilterFast("(price < 100 && stock > 0) || price > 500") // strongly consistent, grouped conditions
Filter("name LIKE \"*sneaker*\"")                       // eventually consistent, needs filtering mode "full"
Filter("!(status == \"discontinued\")")                 // eventually consistent, negated condition
```

Both parameters only work over properties declared on the class, plus the built-in `id`, `createdAt`, `updatedAt`, and `status` fields, which are always filterable and sortable without being declared. `eTag` and `expiresAt` are neither. The `status` exception does not hold if the class redeclares `/status` scoped to projections your token cannot fully reach. Refer to [filtering](https://www.pubnub.com/docs/general/data-sync/data-operations.md#filtering) for the two filtering tiers, and [property definitions](https://www.pubnub.com/docs/general/data-sync/schemas-and-validation.md#property-definitions) for how properties are declared.

Use `Sort` to order results. Pass a `[]string` where each element is a property name optionally suffixed with `:asc` or `:desc`, the SDK joins them into a comma-separated list for the request. A bare name sorts ascending:

```go
Sort([]string{"price:desc"})        // single field, descending
Sort([]string{"type", "price:desc"}) // type ascending, then price descending
Sort([]string{"createdAt:asc"})     // explicit ascending
```

The `+field` and `-field` prefixes are **not** accepted. A leading `-` is read as part of the property name, so `"-price"` fails with a `400`. Only properties declared on the class with a filtering mode other than `none` can be sorted on when sorting alone or alongside `FilterFast`. Sorting alongside `Filter` additionally requires the field to be declared `full`. The built-in fields `id`, `createdAt`, `updatedAt`, and `status` are always sortable without declaring them on the class.

The SDK checks `FilterFast` and `Filter` for the exclusivity rule above before sending anything, and returns a non-nil `error` from `Execute()` if both are set. It does not otherwise validate the filter expression, `Sort`, or an out-of-range `Limit` locally, each of those only surfaces as an error response from the server.

###### Concurrency (ETag)

Every stored object carries an `ETag`. To guard against concurrent writes, pass the `ETag` you read earlier as `IfMatchETag` on `Set*`, `Update*`, and `Remove*` calls. The SDK sends it as the `If-Match` request header.

If the server-side value has changed in the meantime, the operation fails with a `412`, and you should re-read the object and retry. Refer to [optimistic concurrency with ETags](https://www.pubnub.com/docs/general/data-sync/data-operations.md#optimistic-concurrency-with-etags) for details.

###### Partial updates

The `Update*` methods apply a partial update using raw JSON Patch (RFC 6902). Build the operation list by chaining `Add`, `Remove`, `Replace`, `Move`, `Copy`, and `Test` on the builder, or pass a complete slice through `Operations([]PNJSONPatchOperation)`. Each `PNJSONPatchOperation` has an `Op` (`add`, `remove`, `replace`, `move`, `copy`, or `test`), a `Path` (a full JSON Pointer, for example `/status` or `/payload/price`), and, depending on the operation, a `Value` or a `From`.

Each path is a full JSON Pointer (RFC 6901) and is sent as you write it. The SDK does **not** add a `/payload` prefix. To target a field inside `payload`, include the prefix yourself, for example `/payload/price`, not `/price`. A bare `/price` targets a top-level field named `price`, which doesn't exist. Top-level fields like `/status` can be patched directly. Refer to [partial update](https://www.pubnub.com/docs/general/data-sync/data-operations.md#partial-update) for details.

Correct patch forms:

```go
Add("/payload/tags", "sale")           // adds a value inside payload.tags
Replace("/payload/price", 79.99)       // replaces payload.price
Remove("/payload/tempFlag")            // removes payload.tempFlag
Move("/payload/oldTag", "/payload/tag")
Copy("/payload/price", "/payload/msrp")
Test("/status", "active")              // fails the patch if the current value doesn't match
```

`Move` and `Copy` take a `from` pointer as their first argument instead of a value, matching the RFC's `from`/`path` pair.

Paths address the object's stored property names, the same keys that come back in responses. The class version is `entityClassVersion` on users, channels, and entities, and `relationshipClassVersion` on memberships and relationships. The fields set at creation cannot be patched: `entityClass` and `entityClassLevel` on users, channels, and entities, `relationshipClass`, `entityAId`, and `entityBId` on relationships, and `userId` and `channelId` on memberships.

All operations in the call apply together or not at all.

The `Operations` list must contain at least one operation. The SDK checks this before it sends anything and returns a non-nil `error` from `Execute()` if the list is empty.

###### Expiry (TTL)

An object's `ExpiresAt` is computed once when the object is created, and updates never refresh it. The value is the creation time plus the class TTL, rounded up to the start of the next whole UTC day, so it rarely lands exactly one TTL from the moment you wrote the object.

Entity classes you create default to a 31-day TTL, while the built-in Global `User` and `Channel` classes use 30 days. TTL cannot be disabled. A relationship expires at the earlier expiry time of the two entities it links, fixed when the relationship is created.

Refer to [data expiry](https://www.pubnub.com/docs/general/data-sync/schemas-and-validation.md#data-expiry-ttl) for details.

:::note Synchronous calls, cancellable with a context
Every `DataSync` method is synchronous: build the request by chaining setters on the builder, then call `.Execute()`, which blocks until it returns the typed response, a `StatusResponse`, and an `error`. Use the `WithContext` variant, for example `CreateEntityWithContext(ctx)`, to cancel a call or apply a deadline through a `context.Context`. Check `error` first, then `StatusResponse.StatusCode` for the HTTP status PubNub returned.
:::

:::note Requires Access Manager
DataSync requires that the *Access Manager* add-on is enabled for your key in the [Admin Portal](https://admin.pubnub.com/). Read the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) 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](https://www.pubnub.com/docs/general/data-sync/users-channels-memberships.md#users) for the concept.

A user is an entity of the built-in `User` [entity class](https://www.pubnub.com/docs/admin-api/get-all-entity-class-entries.md), 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](https://www.pubnub.com/docs/admin-api/create-a-new-entity-class.md) and pass its name as `EntityClass`.

### Create user

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

#### Method(s)

```go
pn.DataSync.CreateUser().
    ID(string).
    EntityClass(string).
    EntityClassVersion(int).
    EntityClassLevel(PNEntityClassLevel).
    Status(string).
    Payload(map[string]interface{}).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID`Type: stringDefault: server-generated | User identifier. Omit to let the server generate a UUID. Max 255 characters. |
| `EntityClass`Type: stringDefault: `User` | Name of the entity class this user belongs to. Must be `User` or one of its subclasses. Defaults to `User` on the server if omitted. Set at creation and immutable afterward. |
| `EntityClassVersion` *Type: intDefault: n/a | Version of the user class schema. |
| `EntityClassLevel`Type: `PNEntityClassLevel`Default: service default | Class hierarchy level of `EntityClass`, either `Global` for a class the service provides or `SubKey` for one defined on your keyset. Used to disambiguate classes with the same name defined at different levels. Set at creation and immutable afterward. |
| `Status`Type: stringDefault: n/a | Free-form lifecycle status. Max 100 characters. |
| `Payload`Type: map[string]interfaceDefault: n/a | Free-form JSON object holding your application data. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_createUserBasicUsage demonstrates creating a DataSync user
func Example_createUserBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	resetDataSyncExampleUser(pn)
	// snippet.show

	_, status, err := pn.DataSync.CreateUser().
		ID("user-alice").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Alice", "type": "shopper"}).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("User created, status: %d\n", status.StatusCode)

	// Output:
	// User created, status: 201
}
```

#### Response

```json
{
    "status": 201,
    "data": {
        "id": "user-alice",
        "entityClass": "User",
        "entityClassVersion": 1,
        "entityClassLevel": "Global",
        "payload": { "name": "Alice", "type": "shopper" },
        "createdAt": "2026-07-13T09:00:00.000Z",
        "updatedAt": "2026-07-13T09:00:00.000Z",
        "eTag": "AbCdEfGhIjKlMn"
    }
}
```

### Get user

Returns a single user by `ID`.

#### Method(s)

```go
pn.DataSync.GetUser().
    ID(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | User identifier. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_getUserBasicUsage demonstrates reading a single DataSync user
func Example_getUserBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleUser(pn)
	// snippet.show

	res, status, err := pn.DataSync.GetUser().
		ID("user-alice").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Got user: %s, status: %d\n", res.Data.ID, status.StatusCode)

	// Output:
	// Got user: user-alice, status: 200
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "user-alice",
        "entityClass": "User",
        "entityClassVersion": 1,
        "entityClassLevel": "Global",
        "payload": { "name": "Alice", "type": "shopper" },
        "createdAt": "2026-07-13T09:00:00.000Z",
        "updatedAt": "2026-07-13T09:00:00.000Z",
        "eTag": "AbCdEfGhIjKlMn"
    }
}
```

### Get all users

Returns a paginated list of users. All parameters are optional, so you can call `GetUsers` with no arguments. For pagination, filtering, and sorting, refer to [sorting and pagination](https://www.pubnub.com/docs/general/data-sync/data-operations.md#sorting-and-pagination).

#### Method(s)

```go
pn.DataSync.GetUsers().
    EntityClass(string).
    EntityClassVersion(int).
    EntityClassLevel(PNEntityClassLevel).
    Cursor(string).
    Limit(int).
    FilterFast(string).
    Filter(string).
    Sort([]string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `EntityClass`Type: stringDefault: all user classes | User class to list. Omit to list the Global `User` class and all of its subclasses. |
| `EntityClassVersion`Type: intDefault: all versions | User class version to list. Omit to list users across every version of the class. |
| `EntityClassLevel`Type: `PNEntityClassLevel`Default: service default | Class hierarchy level of `EntityClass`, either `Global` for a class the service provides or `SubKey` for one defined on your keyset. Used to disambiguate a class name defined at both levels. |
| `Cursor`Type: stringDefault: n/a | Opaque pagination cursor. Omit for the first page. |
| `Limit`Type: intDefault: `20` | Maximum number of users per page. Max `100`. |
| `FilterFast`Type: stringDefault: 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`. |
| `Filter`Type: stringDefault: 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`. |
| `Sort`Type: []stringDefault: n/a | Order results. Each entry is a property name optionally suffixed with `:asc` or `:desc`, for example `"createdAt:desc"`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_getUsersBasicUsage demonstrates listing DataSync users
func Example_getUsersBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleUser(pn)
	// snippet.show

	_, status, err := pn.DataSync.GetUsers().
		Limit(20).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Users listed, status: %d\n", status.StatusCode)

	// Output:
	// Users listed, status: 200
}
```

#### Response

```json
{
    "status": 200,
    "data": [
        {
            "id": "user-alice",
            "entityClass": "User",
            "entityClassVersion": 1,
            "entityClassLevel": "Global",
            "payload": { "name": "Alice", "type": "shopper" },
            "createdAt": "2026-07-13T09:00:00.000Z",
            "updatedAt": "2026-07-13T09:00:00.000Z",
            "eTag": "AbCdEfGhIjKlMn"
        }
    ],
    "meta": {
        "next_cursor": "b2Zmc2V0PTIw",
        "has_next": true,
        "limit": 20
    }
}
```

#### Other examples

##### 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). Reference a declared property by its `name`, not its `path`. It shares the same expression language as `Filter`, so only one of the two can be sent per call.

```go
// Example_getUsersFilterFast demonstrates filtering users with FilterFast
func Example_getUsersFilterFast() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleUser(pn)
	// snippet.show

	_, status, err := pn.DataSync.GetUsers().
		FilterFast("type == \"shopper\"").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Filtered users retrieved, status: %d\n", status.StatusCode)

	// Output:
	// Filtered users retrieved, status: 200
}
```

##### 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`. It shares the same expression language as `FilterFast`, so only one of the two can be sent per call.

```go
// Example_getUsersFilter demonstrates filtering users with Filter
func Example_getUsersFilter() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleUser(pn)
	// snippet.show

	_, status, err := pn.DataSync.GetUsers().
		Filter("name LIKE \"*Alice*\"").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Filtered users retrieved, status: %d\n", status.StatusCode)

	// Output:
	// Filtered users retrieved, status: 200
}
```

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

```go
// Example_getUsersPagination demonstrates paging through the user list
func Example_getUsersPagination() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleUser(pn)
	// snippet.show

	res, status, err := pn.DataSync.GetUsers().
		Limit(20).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Users page retrieved, status: %d\n", status.StatusCode)

	if res.Meta != nil && res.Meta.HasNext {
		_, _, err = pn.DataSync.GetUsers().
			Cursor(res.Meta.NextCursor).
			Limit(20).
			Execute()

		if err != nil {
			fmt.Printf("Error: %v\n", err)
		}
	}

	// Output:
	// Users page retrieved, status: 200
}
```

### Set user

Replaces a user in full (PUT). Send the complete set of fields, any field you omit is cleared. `EntityClass` is immutable after creation and cannot be sent. Refer to [optimistic concurrency with ETags](https://www.pubnub.com/docs/general/data-sync/data-operations.md#optimistic-concurrency-with-etags) for `IfMatchETag`. For a partial update, use [Update user](#update-user).

#### Method(s)

```go
pn.DataSync.SetUser().
    ID(string).
    EntityClassVersion(int).
    Status(string).
    Payload(map[string]interface{}).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | User identifier. |
| `EntityClassVersion` *Type: intDefault: n/a | Version of the user class schema. |
| `Status`Type: stringDefault: n/a | Free-form lifecycle status. Max 100 characters. |
| `Payload`Type: map[string]interfaceDefault: n/a | Free-form JSON object holding your application data. |
| `IfMatchETag`Type: stringDefault: n/a | The `ETag` from a prior read. The update succeeds only if it still matches, otherwise the server returns `412`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_setUserBasicUsage demonstrates replacing a DataSync user in full
func Example_setUserBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleUser(pn)
	// snippet.show

	_, status, err := pn.DataSync.SetUser().
		ID("user-alice").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Alice B.", "type": "shopper"}).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("User replaced, status: %d\n", status.StatusCode)

	// Output:
	// User replaced, status: 200
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "user-alice",
        "entityClass": "User",
        "entityClassVersion": 1,
        "entityClassLevel": "Global",
        "payload": { "name": "Alice B.", "type": "shopper" },
        "createdAt": "2026-07-13T09:00:00.000Z",
        "updatedAt": "2026-07-13T10:15:00.000Z",
        "eTag": "CdEfGhIjKlMnOp"
    }
}
```

### Update user

Applies a partial update to a user with raw JSON Patch operations. Refer to [partial update](https://www.pubnub.com/docs/general/data-sync/data-operations.md#partial-update) for the operation model.

#### Method(s)

```go
pn.DataSync.UpdateUser().
    ID(string).
    Add(string, interface{}).
    Remove(string).
    Replace(string, interface{}).
    Move(string, string).
    Copy(string, string).
    Test(string, interface{}).
    Operations([]PNJSONPatchOperation).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | User identifier. |
| `Operations`Type: `[]PNJSONPatchOperation`Default: n/a | At least one patch operation is required before `Execute()`. Build the list with chained `Add`, `Remove`, `Replace`, `Move`, `Copy`, and `Test` calls, or pass a complete slice through `Operations`. |
| `IfMatchETag`Type: stringDefault: 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.

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_updateUserBasicUsage demonstrates applying a JSON Patch update to a DataSync user
func Example_updateUserBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleUser(pn)
	// snippet.show

	_, status, err := pn.DataSync.UpdateUser().
		ID("user-alice").
		Replace("/status", "active").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("User updated, status: %d\n", status.StatusCode)

	// Output:
	// User updated, status: 200
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "user-alice",
        "status": "active",
        "entityClass": "User",
        "entityClassVersion": 1,
        "entityClassLevel": "Global",
        "payload": { "name": "Alice B.", "type": "shopper" },
        "createdAt": "2026-07-13T09:00:00.000Z",
        "updatedAt": "2026-07-13T10:20:00.000Z",
        "eTag": "EfGhIjKlMnOpQr"
    }
}
```

### Remove user

Deletes a user by `ID`.

#### Method(s)

```go
pn.DataSync.RemoveUser().
    ID(string).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | User identifier. |
| `IfMatchETag`Type: stringDefault: n/a | The `ETag` from a prior read. The delete succeeds only if it still matches, otherwise the server returns `412`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_removeUserBasicUsage demonstrates deleting a DataSync user
func Example_removeUserBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleUser(pn)
	// snippet.show

	_, status, err := pn.DataSync.RemoveUser().
		ID("user-alice").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("User removed, status: %d\n", status.StatusCode)

	// Output:
	// User removed, status: 200
}

// ==================== Channels ====================
```

#### Response

```json
{
    "status": 200
}
```

## 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](https://www.pubnub.com/docs/general/data-sync/users-channels-memberships.md#channels) for the concept.

A channel is an entity of the built-in `Channel` [entity class](https://www.pubnub.com/docs/admin-api/get-all-entity-class-entries.md), which the service provides at the `Global` class level. Subclass it with [Create a new entity class](https://www.pubnub.com/docs/admin-api/create-a-new-entity-class.md) to add declared properties, then pass the subclass name as `EntityClass`.

### Create channel

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

#### Method(s)

```go
pn.DataSync.CreateChannel().
    ID(string).
    EntityClass(string).
    EntityClassVersion(int).
    EntityClassLevel(PNEntityClassLevel).
    Status(string).
    Payload(map[string]interface{}).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID`Type: stringDefault: server-generated | Channel identifier. Omit to let the server generate a UUID. Max 255 characters. |
| `EntityClass`Type: stringDefault: `Channel` | Name of the entity class this channel belongs to. Must be `Channel` or one of its subclasses. Defaults to `Channel` on the server if omitted. Set at creation and immutable afterward. |
| `EntityClassVersion` *Type: intDefault: n/a | Version of the channel class schema. |
| `EntityClassLevel`Type: `PNEntityClassLevel`Default: service default | Class hierarchy level of `EntityClass`, either `Global` for a class the service provides or `SubKey` for one defined on your keyset. Used to disambiguate classes with the same name defined at different levels. Set at creation and immutable afterward. |
| `Status`Type: stringDefault: n/a | Free-form lifecycle status. Max 100 characters. |
| `Payload`Type: map[string]interfaceDefault: n/a | Free-form JSON object holding your application data. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_createChannelBasicUsage demonstrates creating a DataSync channel
func Example_createChannelBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	resetDataSyncExampleChannel(pn)
	// snippet.show

	_, status, err := pn.DataSync.CreateChannel().
		ID("channel-summer-sale").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Summer Sale", "type": "promotion"}).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Channel created, status: %d\n", status.StatusCode)

	// Output:
	// Channel created, status: 201
}
```

#### Response

```json
{
    "status": 201,
    "data": {
        "id": "channel-summer-sale",
        "entityClass": "Channel",
        "entityClassVersion": 1,
        "entityClassLevel": "Global",
        "payload": { "name": "Summer Sale", "type": "promotion" },
        "createdAt": "2026-07-13T09:05:00.000Z",
        "updatedAt": "2026-07-13T09:05:00.000Z",
        "eTag": "GhIjKlMnOpQrSt"
    }
}
```

### Get channel

Returns a single channel by `ID`.

#### Method(s)

```go
pn.DataSync.GetChannel().
    ID(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Channel identifier. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_getChannelBasicUsage demonstrates reading a single DataSync channel
func Example_getChannelBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleChannel(pn)
	// snippet.show

	_, status, err := pn.DataSync.GetChannel().
		ID("channel-summer-sale").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Channel retrieved, status: %d\n", status.StatusCode)

	// Output:
	// Channel retrieved, status: 200
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "channel-summer-sale",
        "entityClass": "Channel",
        "entityClassVersion": 1,
        "entityClassLevel": "Global",
        "payload": { "name": "Summer Sale", "type": "promotion" },
        "createdAt": "2026-07-13T09:05:00.000Z",
        "updatedAt": "2026-07-13T09:05:00.000Z",
        "eTag": "GhIjKlMnOpQrSt"
    }
}
```

### Get all channels

Returns a paginated list of channels. All parameters are optional, so you can call `GetChannels` with no arguments. For pagination, filtering, and sorting, refer to [sorting and pagination](https://www.pubnub.com/docs/general/data-sync/data-operations.md#sorting-and-pagination).

#### Method(s)

```go
pn.DataSync.GetChannels().
    EntityClass(string).
    EntityClassVersion(int).
    EntityClassLevel(PNEntityClassLevel).
    Cursor(string).
    Limit(int).
    FilterFast(string).
    Filter(string).
    Sort([]string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `EntityClass`Type: stringDefault: all channel classes | Channel class to list. Omit to list the Global `Channel` class and all of its subclasses. |
| `EntityClassVersion`Type: intDefault: all versions | Channel class version to list. Omit to list channels across every version of the class. |
| `EntityClassLevel`Type: `PNEntityClassLevel`Default: service default | Class hierarchy level of `EntityClass`, either `Global` for a class the service provides or `SubKey` for one defined on your keyset. Used to disambiguate a class name defined at both levels. |
| `Cursor`Type: stringDefault: n/a | Opaque pagination cursor. Omit for the first page. |
| `Limit`Type: intDefault: `20` | Maximum number of channels per page. Max `100`. |
| `FilterFast`Type: stringDefault: 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`. |
| `Filter`Type: stringDefault: 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`. |
| `Sort`Type: []stringDefault: n/a | Order results. Each entry is a property name optionally suffixed with `:asc` or `:desc`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_getChannelsBasicUsage demonstrates listing DataSync channels
func Example_getChannelsBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleChannel(pn)
	// snippet.show

	_, status, err := pn.DataSync.GetChannels().
		Limit(20).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Channels listed, status: %d\n", status.StatusCode)

	// Output:
	// Channels listed, status: 200
}
```

#### Response

```json
{
    "status": 200,
    "data": [
        {
            "id": "channel-summer-sale",
            "entityClass": "Channel",
            "entityClassVersion": 1,
            "entityClassLevel": "Global",
            "payload": { "name": "Summer Sale", "type": "promotion" },
            "createdAt": "2026-07-13T09:05:00.000Z",
            "updatedAt": "2026-07-13T09:05:00.000Z",
            "eTag": "GhIjKlMnOpQrSt"
        }
    ],
    "meta": {
        "next_cursor": null,
        "has_next": false,
        "limit": 20
    }
}
```

#### Other examples

##### 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). Reference a declared property by its `name`, not its `path`. It shares the same expression language as `Filter`, so only one of the two can be sent per call.

```go
// Example_getChannelsFilterFast demonstrates filtering channels with FilterFast
func Example_getChannelsFilterFast() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleChannel(pn)
	// snippet.show

	_, status, err := pn.DataSync.GetChannels().
		FilterFast("type == \"promotion\"").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Filtered channels retrieved, status: %d\n", status.StatusCode)

	// Output:
	// Filtered channels retrieved, status: 200
}
```

##### 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`. It shares the same expression language as `FilterFast`, so only one of the two can be sent per call.

```go
// Example_getChannelsFilter demonstrates filtering channels with Filter
func Example_getChannelsFilter() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleChannel(pn)
	// snippet.show

	_, status, err := pn.DataSync.GetChannels().
		Filter("name LIKE \"*Sale*\"").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Filtered channels retrieved, status: %d\n", status.StatusCode)

	// Output:
	// Filtered channels retrieved, status: 200
}
```

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

```go
// Example_getChannelsPagination demonstrates paging through the channel list
func Example_getChannelsPagination() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleChannel(pn)
	// snippet.show

	res, status, err := pn.DataSync.GetChannels().
		Limit(20).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Channels page retrieved, status: %d\n", status.StatusCode)

	if res.Meta != nil && res.Meta.HasNext {
		_, _, err = pn.DataSync.GetChannels().
			Cursor(res.Meta.NextCursor).
			Limit(20).
			Execute()

		if err != nil {
			fmt.Printf("Error: %v\n", err)
		}
	}

	// Output:
	// Channels page retrieved, status: 200
}
```

### Set channel

Replaces a channel in full (PUT). Send the complete set of fields, any field you omit is cleared. `EntityClass` is immutable after creation and cannot be sent. Refer to [optimistic concurrency with ETags](https://www.pubnub.com/docs/general/data-sync/data-operations.md#optimistic-concurrency-with-etags) for `IfMatchETag`. For a partial update, use [Update channel](#update-channel).

#### Method(s)

```go
pn.DataSync.SetChannel().
    ID(string).
    EntityClassVersion(int).
    Status(string).
    Payload(map[string]interface{}).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Channel identifier. |
| `EntityClassVersion` *Type: intDefault: n/a | Version of the channel class schema. |
| `Status`Type: stringDefault: n/a | Free-form lifecycle status. Max 100 characters. |
| `Payload`Type: map[string]interfaceDefault: n/a | Free-form JSON object holding your application data. |
| `IfMatchETag`Type: stringDefault: n/a | The `ETag` from a prior read. The update succeeds only if it still matches, otherwise the server returns `412`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_setChannelBasicUsage demonstrates replacing a DataSync channel in full
func Example_setChannelBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleChannel(pn)
	// snippet.show

	_, status, err := pn.DataSync.SetChannel().
		ID("channel-summer-sale").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Summer Sale 2026", "type": "promotion"}).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Channel replaced, status: %d\n", status.StatusCode)

	// Output:
	// Channel replaced, status: 200
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "channel-summer-sale",
        "entityClass": "Channel",
        "entityClassVersion": 1,
        "entityClassLevel": "Global",
        "payload": { "name": "Summer Sale 2026", "type": "promotion" },
        "createdAt": "2026-07-13T09:05:00.000Z",
        "updatedAt": "2026-07-13T11:00:00.000Z",
        "eTag": "IjKlMnOpQrStUv"
    }
}
```

### Update channel

Applies a partial update to a channel with raw JSON Patch operations. Refer to [partial update](https://www.pubnub.com/docs/general/data-sync/data-operations.md#partial-update) for the operation model.

#### Method(s)

```go
pn.DataSync.UpdateChannel().
    ID(string).
    Add(string, interface{}).
    Remove(string).
    Replace(string, interface{}).
    Move(string, string).
    Copy(string, string).
    Test(string, interface{}).
    Operations([]PNJSONPatchOperation).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Channel identifier. |
| `Operations`Type: `[]PNJSONPatchOperation`Default: n/a | At least one patch operation is required before `Execute()`. Build the list with chained `Add`, `Remove`, `Replace`, `Move`, `Copy`, and `Test` calls, or pass a complete slice through `Operations`. |
| `IfMatchETag`Type: stringDefault: 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.

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_updateChannelBasicUsage demonstrates applying a JSON Patch update to a DataSync channel
func Example_updateChannelBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleChannel(pn)
	// snippet.show

	_, status, err := pn.DataSync.UpdateChannel().
		ID("channel-summer-sale").
		Replace("/status", "active").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Channel updated, status: %d\n", status.StatusCode)

	// Output:
	// Channel updated, status: 200
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "channel-summer-sale",
        "status": "active",
        "entityClass": "Channel",
        "entityClassVersion": 1,
        "entityClassLevel": "Global",
        "payload": { "name": "Summer Sale 2026", "type": "promotion" },
        "createdAt": "2026-07-13T09:05:00.000Z",
        "updatedAt": "2026-07-13T11:05:00.000Z",
        "eTag": "KlMnOpQrStUvWx"
    }
}
```

### Remove channel

Deletes a channel by `ID`.

#### Method(s)

```go
pn.DataSync.RemoveChannel().
    ID(string).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Channel identifier. |
| `IfMatchETag`Type: stringDefault: n/a | The `ETag` from a prior read. The delete succeeds only if it still matches, otherwise the server returns `412`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_removeChannelBasicUsage demonstrates deleting a DataSync channel
func Example_removeChannelBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleChannel(pn)
	// snippet.show

	_, status, err := pn.DataSync.RemoveChannel().
		ID("channel-summer-sale").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Channel removed, status: %d\n", status.StatusCode)

	// Output:
	// Channel removed, status: 200
}

// ==================== Memberships ====================
```

#### Response

```json
{
    "status": 200
}
```

## 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](https://www.pubnub.com/docs/general/data-sync/users-channels-memberships.md#memberships) for the concept.

A membership is a relationship of the built-in Global `Membership` [relationship class](https://www.pubnub.com/docs/admin-api/get-all-relationship-classes.md), 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](https://www.pubnub.com/docs/general/data-sync/schemas-and-validation.md#relationship-class-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)

```go
pn.DataSync.CreateMembership().
    ID(string).
    ChannelID(string).
    UserID(string).
    RelationshipClassVersion(int).
    Status(string).
    Payload(map[string]interface{}).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID`Type: stringDefault: server-generated | Membership identifier. Omit to let the server generate a UUID. Max 255 characters. |
| `ChannelID` *Type: stringDefault: n/a | Identifier of the channel in the membership. |
| `UserID` *Type: stringDefault: n/a | Identifier of the user in the membership. |
| `RelationshipClassVersion` *Type: intDefault: n/a | Version of the Membership relationship class schema. |
| `Status`Type: stringDefault: n/a | Free-form lifecycle status. Max 100 characters. |
| `Payload`Type: map[string]interfaceDefault: n/a | Free-form JSON object holding your application data. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_createMembershipBasicUsage demonstrates linking a user to a channel
func Example_createMembershipBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleUser(pn)
	seedDataSyncExampleChannel(pn)
	// snippet.show

	_, status, err := pn.DataSync.CreateMembership().
		ID("membership-alice-summer-sale").
		ChannelID("channel-summer-sale").
		UserID("user-alice").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"role": "viewer"}).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Membership created, status: %d\n", status.StatusCode)

	// Output:
	// Membership created, status: 201
}
```

#### Response

```json
{
    "status": 201,
    "data": {
        "id": "membership-alice-summer-sale",
        "channelId": "channel-summer-sale",
        "userId": "user-alice",
        "relationshipClass": "Membership",
        "relationshipClassVersion": 1,
        "payload": { "role": "viewer" },
        "createdAt": "2026-07-13T09:10:00.000Z",
        "updatedAt": "2026-07-13T09:10:00.000Z",
        "eTag": "MnOpQrStUvWxYz"
    }
}
```

### Get membership

Returns a single membership by `ID`.

#### Method(s)

```go
pn.DataSync.GetMembership().
    ID(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Membership identifier. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_getMembershipBasicUsage demonstrates reading a single membership
func Example_getMembershipBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleMembership(pn)
	// snippet.show

	_, status, err := pn.DataSync.GetMembership().
		ID("membership-alice-summer-sale").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Membership retrieved, status: %d\n", status.StatusCode)

	// Output:
	// Membership retrieved, status: 200
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "membership-alice-summer-sale",
        "channelId": "channel-summer-sale",
        "userId": "user-alice",
        "relationshipClass": "Membership",
        "relationshipClassVersion": 1,
        "payload": { "role": "viewer" },
        "createdAt": "2026-07-13T09:10:00.000Z",
        "updatedAt": "2026-07-13T09:10:00.000Z",
        "eTag": "MnOpQrStUvWxYz"
    }
}
```

### Get all memberships

Returns a paginated list of memberships. All parameters are optional, so you can call `GetMemberships` with no arguments. `UserID` and `ChannelID` can be set independently, together, or omitted entirely. 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](https://www.pubnub.com/docs/general/data-sync/data-operations.md#sorting-and-pagination).

#### Method(s)

```go
pn.DataSync.GetMemberships().
    UserID(string).
    ChannelID(string).
    RelationshipClassVersion(int).
    Cursor(string).
    Limit(int).
    FilterFast(string).
    Filter(string).
    Sort([]string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `UserID`Type: stringDefault: n/a | List only memberships for this user. |
| `ChannelID`Type: stringDefault: n/a | List only memberships for this channel. |
| `RelationshipClassVersion`Type: intDefault: all versions | Membership class version to list. Omit to list memberships across every version of the class. |
| `Cursor`Type: stringDefault: n/a | Opaque pagination cursor. Omit for the first page. |
| `Limit`Type: intDefault: `20` | Maximum number of memberships per page. Max `100`. |
| `FilterFast`Type: stringDefault: 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`. |
| `Filter`Type: stringDefault: 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`. |
| `Sort`Type: []stringDefault: n/a | Order results. Each entry is a property name optionally suffixed with `:asc` or `:desc`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_getMembershipsBasicUsage demonstrates listing memberships
func Example_getMembershipsBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleMembership(pn)
	// snippet.show

	_, status, err := pn.DataSync.GetMemberships().
		UserID("user-alice").
		Limit(20).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Memberships listed, status: %d\n", status.StatusCode)

	// Output:
	// Memberships listed, status: 200
}
```

#### Response

```json
{
    "status": 200,
    "data": [
        {
            "id": "membership-alice-summer-sale",
            "channelId": "channel-summer-sale",
            "userId": "user-alice",
            "relationshipClass": "Membership",
            "relationshipClassVersion": 1,
            "payload": { "role": "viewer" },
            "createdAt": "2026-07-13T09:10:00.000Z",
            "updatedAt": "2026-07-13T09:10:00.000Z",
            "eTag": "MnOpQrStUvWxYz"
        }
    ],
    "meta": {
        "next_cursor": null,
        "has_next": false,
        "limit": 20
    }
}
```

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

```go
// Example_getMembershipsByChannelID demonstrates listing a channel's members
func Example_getMembershipsByChannelID() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleMembership(pn)
	// snippet.show

	_, status, err := pn.DataSync.GetMemberships().
		ChannelID("channel-summer-sale").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Channel members listed, status: %d\n", status.StatusCode)

	// Output:
	// Channel members listed, status: 200
}
```

##### 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). Reference a declared property by its `name`, not its `path`. It shares the same expression language as `Filter`, so only one of the two can be sent per call.

```go
// Example_getMembershipsFilterFast demonstrates filtering memberships with FilterFast
func Example_getMembershipsFilterFast() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleMembership(pn)
	// snippet.show

	_, status, err := pn.DataSync.GetMemberships().
		FilterFast("status == \"active\"").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Filtered memberships retrieved, status: %d\n", status.StatusCode)

	// Output:
	// Filtered memberships retrieved, status: 200
}
```

##### 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`. It shares the same expression language as `FilterFast`, so only one of the two can be sent per call.

```go
// Example_getMembershipsFilter demonstrates filtering memberships with Filter
func Example_getMembershipsFilter() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleMembership(pn)
	// snippet.show

	_, status, err := pn.DataSync.GetMemberships().
		Filter("status == \"active\"").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Filtered memberships retrieved, status: %d\n", status.StatusCode)

	// Output:
	// Filtered memberships retrieved, status: 200
}
```

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

```go
// Example_getMembershipsPagination demonstrates paging through the membership list
func Example_getMembershipsPagination() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleMembership(pn)
	// snippet.show

	res, status, err := pn.DataSync.GetMemberships().
		Limit(20).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Memberships page retrieved, status: %d\n", status.StatusCode)

	if res.Meta != nil && res.Meta.HasNext {
		_, _, err = pn.DataSync.GetMemberships().
			Cursor(res.Meta.NextCursor).
			Limit(20).
			Execute()

		if err != nil {
			fmt.Printf("Error: %v\n", err)
		}
	}

	// Output:
	// Memberships page retrieved, status: 200
}
```

### Set membership

Replaces a membership in full (PUT). Send the complete set of fields, any field you omit is cleared. The linked user and channel ids are immutable, so `SetMembership` doesn't expose `UserID`/`ChannelID` at all, there's nothing to resend. Refer to [optimistic concurrency with ETags](https://www.pubnub.com/docs/general/data-sync/data-operations.md#optimistic-concurrency-with-etags) for `IfMatchETag`. For a partial update, use [Update membership](#update-membership).

#### Method(s)

```go
pn.DataSync.SetMembership().
    ID(string).
    RelationshipClassVersion(int).
    Status(string).
    Payload(map[string]interface{}).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Membership identifier. |
| `RelationshipClassVersion` *Type: intDefault: n/a | Version of the Membership relationship class schema. |
| `Status`Type: stringDefault: n/a | Free-form lifecycle status. Max 100 characters. |
| `Payload`Type: map[string]interfaceDefault: n/a | Free-form JSON object holding your application data. |
| `IfMatchETag`Type: stringDefault: n/a | The `ETag` from a prior read. The update succeeds only if it still matches, otherwise the server returns `412`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_setMembershipBasicUsage demonstrates replacing a membership in full
func Example_setMembershipBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleMembership(pn)
	// snippet.show

	_, status, err := pn.DataSync.SetMembership().
		ID("membership-alice-summer-sale").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"role": "moderator"}).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Membership replaced, status: %d\n", status.StatusCode)

	// Output:
	// Membership replaced, status: 200
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "membership-alice-summer-sale",
        "channelId": "channel-summer-sale",
        "userId": "user-alice",
        "relationshipClass": "Membership",
        "relationshipClassVersion": 1,
        "payload": { "role": "moderator" },
        "createdAt": "2026-07-13T09:10:00.000Z",
        "updatedAt": "2026-07-13T12:00:00.000Z",
        "eTag": "OpQrStUvWxYzAb"
    }
}
```

### Update membership

Applies a partial update to a membership with raw JSON Patch operations. Refer to [partial update](https://www.pubnub.com/docs/general/data-sync/data-operations.md#partial-update) for the operation model.

#### Method(s)

```go
pn.DataSync.UpdateMembership().
    ID(string).
    Add(string, interface{}).
    Remove(string).
    Replace(string, interface{}).
    Move(string, string).
    Copy(string, string).
    Test(string, interface{}).
    Operations([]PNJSONPatchOperation).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Membership identifier. |
| `Operations`Type: `[]PNJSONPatchOperation`Default: n/a | At least one patch operation is required before `Execute()`. Build the list with chained `Add`, `Remove`, `Replace`, `Move`, `Copy`, and `Test` calls, or pass a complete slice through `Operations`. |
| `IfMatchETag`Type: stringDefault: 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.

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_updateMembershipBasicUsage demonstrates applying a JSON Patch update to a membership
func Example_updateMembershipBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleMembership(pn)
	// snippet.show

	_, status, err := pn.DataSync.UpdateMembership().
		ID("membership-alice-summer-sale").
		Replace("/status", "active").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Membership updated, status: %d\n", status.StatusCode)

	// Output:
	// Membership updated, status: 200
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "membership-alice-summer-sale",
        "status": "active",
        "channelId": "channel-summer-sale",
        "userId": "user-alice",
        "relationshipClass": "Membership",
        "relationshipClassVersion": 1,
        "payload": { "role": "moderator" },
        "createdAt": "2026-07-13T09:10:00.000Z",
        "updatedAt": "2026-07-13T12:05:00.000Z",
        "eTag": "QrStUvWxYzAbCd"
    }
}
```

### Remove membership

Deletes a membership by `ID`.

#### Method(s)

```go
pn.DataSync.RemoveMembership().
    ID(string).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Membership identifier. |
| `IfMatchETag`Type: stringDefault: n/a | The `ETag` from a prior read. The delete succeeds only if it still matches, otherwise the server returns `412`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_removeMembershipBasicUsage demonstrates deleting a membership
func Example_removeMembershipBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	seedDataSyncExampleMembership(pn)
	// snippet.show

	_, status, err := pn.DataSync.RemoveMembership().
		ID("membership-alice-summer-sale").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Membership removed, status: %d\n", status.StatusCode)

	// Output:
	// Membership removed, status: 200
}

// ==================== Entities ====================
// These snippets are compiled but not executed as tests. They use the `product`
// class, which must be provisioned via the Admin API; the Go SDK cannot create
// classes, and `product` is not present on the CI DS_* keyset.
```

#### Response

```json
{
    "status": 200
}
```

## Entities

Entities are instances of the custom [entity classes](https://www.pubnub.com/docs/admin-api/get-all-entity-class-entries.md) you define on your keyset. In the running example, `product` is a class and `product-sneaker-42` is an instance. Use [Create a new entity class](https://www.pubnub.com/docs/admin-api/create-a-new-entity-class.md) to declare a class, and [Get entity class by ID](https://www.pubnub.com/docs/admin-api/get-entity-class-by-id.md) 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().EntityClass("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)

```go
pn.DataSync.CreateEntity().
    ID(string).
    EntityClass(string).
    EntityClassVersion(int).
    EntityClassLevel(PNEntityClassLevel).
    Status(string).
    Payload(map[string]interface{}).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID`Type: stringDefault: server-generated | Entity identifier. Omit to let the server generate a UUID. Max 255 characters. |
| `EntityClass` *Type: stringDefault: n/a | Name of the entity class this instance belongs to. Set at creation and immutable afterward. |
| `EntityClassVersion` *Type: intDefault: n/a | Version of the entity class schema. |
| `EntityClassLevel`Type: `PNEntityClassLevel`Default: service default | Class hierarchy level of `EntityClass`, either `Global` for a class the service provides or `SubKey` for one defined on your keyset. Used to disambiguate classes with the same name defined at different levels. Set at creation and immutable afterward. |
| `Status`Type: stringDefault: n/a | Free-form lifecycle status. Max 100 characters. |
| `Payload`Type: map[string]interfaceDefault: n/a | Free-form JSON object holding your application data. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_createEntityBasicUsage demonstrates creating a custom DataSync entity
func Example_createEntityBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	// snippet.show

	_, status, err := pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Entity created, status: %d\n", status.StatusCode)
}
```

#### Response

```json
{
    "status": 201,
    "data": {
        "id": "product-sneaker-42",
        "entityClass": "product",
        "entityClassVersion": 1,
        "entityClassLevel": "SubKey",
        "payload": { "name": "Retro Sneaker", "price": 89.99, "stock": 12 },
        "createdAt": "2026-07-13T09:15:00.000Z",
        "updatedAt": "2026-07-13T09:15:00.000Z",
        "eTag": "StUvWxYzAbCdEf"
    }
}
```

### Get entity

Returns a single entity by `ID`.

#### Method(s)

```go
pn.DataSync.GetEntity().
    ID(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Entity identifier. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_getEntityBasicUsage demonstrates reading a single entity
func Example_getEntityBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.GetEntity().
		ID("product-sneaker-42").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Entity retrieved, status: %d\n", status.StatusCode)
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "product-sneaker-42",
        "entityClass": "product",
        "entityClassVersion": 1,
        "entityClassLevel": "SubKey",
        "payload": { "name": "Retro Sneaker", "price": 89.99, "stock": 12 },
        "createdAt": "2026-07-13T09:15:00.000Z",
        "updatedAt": "2026-07-13T09:15:00.000Z",
        "eTag": "StUvWxYzAbCdEf"
    }
}
```

### 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](https://www.pubnub.com/docs/general/data-sync/data-operations.md#sorting-and-pagination).

#### Method(s)

```go
pn.DataSync.GetEntities().
    EntityClass(string).
    EntityClassVersion(int).
    EntityClassLevel(PNEntityClassLevel).
    Cursor(string).
    Limit(int).
    FilterFast(string).
    Filter(string).
    Sort([]string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `EntityClass` *Type: stringDefault: n/a | Name of the entity class to list. |
| `EntityClassVersion`Type: intDefault: all versions | Entity class version to list. Omit to list entities across every version of the class. |
| `EntityClassLevel`Type: `PNEntityClassLevel`Default: service default | Class hierarchy level of `EntityClass`, either `Global` for a class the service provides or `SubKey` for one defined on your keyset. Used to disambiguate a class name defined at both levels. |
| `Cursor`Type: stringDefault: n/a | Opaque pagination cursor. Omit for the first page. |
| `Limit`Type: intDefault: `20` | Maximum number of entities per page. Max `100`. |
| `FilterFast`Type: stringDefault: 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`. |
| `Filter`Type: stringDefault: 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`. |
| `Sort`Type: []stringDefault: n/a | Order results. Each entry is a property name optionally suffixed with `:asc` or `:desc`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_getEntitiesBasicUsage demonstrates listing entities of a class
func Example_getEntitiesBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.GetEntities().
		EntityClass("product").
		Limit(20).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Entities listed, status: %d\n", status.StatusCode)
}
```

#### Response

```json
{
    "status": 200,
    "data": [
        {
            "id": "product-sneaker-42",
            "entityClass": "product",
            "entityClassVersion": 1,
            "entityClassLevel": "SubKey",
            "payload": { "name": "Retro Sneaker", "price": 89.99, "stock": 12 },
            "createdAt": "2026-07-13T09:15:00.000Z",
            "updatedAt": "2026-07-13T09:15:00.000Z",
            "eTag": "StUvWxYzAbCdEf"
        }
    ],
    "meta": {
        "next_cursor": null,
        "has_next": false,
        "limit": 20
    }
}
```

#### Other examples

##### 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). Reference a declared property by its `name`, not its `path`, and combine conditions with `&&` and `||`. It shares the same expression language as `Filter`, so only one of the two can be sent per call.

```go
// Example_getEntitiesFilterFast demonstrates filtering entities with FilterFast
func Example_getEntitiesFilterFast() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.GetEntities().
		EntityClass("product").
		FilterFast("price < 100 && stock > 0").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Filtered entities retrieved, status: %d\n", status.StatusCode)
}
```

##### 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 `||`. It shares the same expression language as `FilterFast`, so only one of the two can be sent per call.

```go
// Example_getEntitiesFilter demonstrates filtering entities with Filter
func Example_getEntitiesFilter() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.GetEntities().
		EntityClass("product").
		Filter("name LIKE \"*Sneaker*\" || price > 500").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Filtered entities retrieved, status: %d\n", status.StatusCode)
}
```

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

```go
// Example_getEntitiesPagination demonstrates paging through the entity list
func Example_getEntitiesPagination() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	// snippet.show

	res, status, err := pn.DataSync.GetEntities().
		EntityClass("product").
		Limit(20).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Entities page retrieved, status: %d\n", status.StatusCode)

	if res.Meta != nil && res.Meta.HasNext {
		_, _, err = pn.DataSync.GetEntities().
			EntityClass("product").
			Cursor(res.Meta.NextCursor).
			Limit(20).
			Execute()

		if err != nil {
			fmt.Printf("Error: %v\n", err)
		}
	}
}
```

### Set entity

Replaces an entity in full (PUT). Send the complete set of fields, any field you omit is cleared. `EntityClass` is immutable after creation and cannot be sent. Refer to [optimistic concurrency with ETags](https://www.pubnub.com/docs/general/data-sync/data-operations.md#optimistic-concurrency-with-etags) for `IfMatchETag`. For a partial update, use [Update entity](#update-entity).

#### Method(s)

```go
pn.DataSync.SetEntity().
    ID(string).
    EntityClassVersion(int).
    Status(string).
    Payload(map[string]interface{}).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Entity identifier. |
| `EntityClassVersion` *Type: intDefault: n/a | Version of the entity class schema. |
| `Status`Type: stringDefault: n/a | Free-form lifecycle status. Max 100 characters. |
| `Payload`Type: map[string]interfaceDefault: n/a | Free-form JSON object holding your application data. |
| `IfMatchETag`Type: stringDefault: n/a | The `ETag` from a prior read. The update succeeds only if it still matches, otherwise the server returns `412`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_setEntityBasicUsage demonstrates replacing an entity in full
func Example_setEntityBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.SetEntity().
		ID("product-sneaker-42").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 79.99, "stock": 12}).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Entity replaced, status: %d\n", status.StatusCode)
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "product-sneaker-42",
        "entityClass": "product",
        "entityClassVersion": 1,
        "entityClassLevel": "SubKey",
        "payload": { "name": "Retro Sneaker", "price": 79.99, "stock": 12 },
        "createdAt": "2026-07-13T09:15:00.000Z",
        "updatedAt": "2026-07-13T13:00:00.000Z",
        "eTag": "UvWxYzAbCdEfGh"
    }
}
```

### Update entity

Applies a partial update to an entity with raw JSON Patch operations. Refer to [partial update](https://www.pubnub.com/docs/general/data-sync/data-operations.md#partial-update) for the operation model.

#### Method(s)

```go
pn.DataSync.UpdateEntity().
    ID(string).
    Add(string, interface{}).
    Remove(string).
    Replace(string, interface{}).
    Move(string, string).
    Copy(string, string).
    Test(string, interface{}).
    Operations([]PNJSONPatchOperation).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Entity identifier. |
| `Operations`Type: `[]PNJSONPatchOperation`Default: n/a | At least one patch operation is required before `Execute()`. Build the list with chained `Add`, `Remove`, `Replace`, `Move`, `Copy`, and `Test` calls, or pass a complete slice through `Operations`. |
| `IfMatchETag`Type: stringDefault: 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.

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_updateEntityBasicUsage demonstrates applying a JSON Patch update to an entity
func Example_updateEntityBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.UpdateEntity().
		ID("product-sneaker-42").
		Replace("/payload/stock", 8).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Entity updated, status: %d\n", status.StatusCode)
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "product-sneaker-42",
        "entityClass": "product",
        "entityClassVersion": 1,
        "entityClassLevel": "SubKey",
        "payload": { "name": "Retro Sneaker", "price": 79.99, "stock": 8 },
        "createdAt": "2026-07-13T09:15:00.000Z",
        "updatedAt": "2026-07-13T13:05:00.000Z",
        "eTag": "WxYzAbCdEfGhIj"
    }
}
```

#### Other examples

##### Combine patch operations, and guard the write with IfMatchETag

A single `UpdateEntity` call can mix `Add`, `Replace`, `Remove`, `Move`, `Copy`, and `Test`. All operations in the call apply together or not at all. Add `IfMatchETag` (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.

```go
// Example_updateEntityMultipleOperations demonstrates combining patch operations with a concurrency guard
func Example_updateEntityMultipleOperations() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	// snippet.show

	current, _, err := pn.DataSync.GetEntity().
		ID("product-sneaker-42").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	_, status, err := pn.DataSync.UpdateEntity().
		ID("product-sneaker-42").
		Add("/payload/tags", []string{"sale"}).
		Replace("/payload/stock", 8).
		IfMatchETag(current.Data.ETag).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Entity patched with guard, status: %d\n", status.StatusCode)
}
```

### Remove entity

Deletes an entity by `ID`.

#### Method(s)

```go
pn.DataSync.RemoveEntity().
    ID(string).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Entity identifier. |
| `IfMatchETag`Type: stringDefault: n/a | The `ETag` from a prior read. The delete succeeds only if it still matches, otherwise the server returns `412`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_removeEntityBasicUsage demonstrates deleting an entity
func Example_removeEntityBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.RemoveEntity().
		ID("product-sneaker-42").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Entity removed, status: %d\n", status.StatusCode)
}

// ==================== Relationships ====================
// These snippets are compiled but not executed as tests. They use the
// `ProductOwner` class, which must be provisioned via the Admin API; the Go SDK
// cannot create classes, and `ProductOwner` is not present on the CI DS_* keyset.
```

#### Response

```json
{
    "status": 200
}
```

## 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](https://www.pubnub.com/docs/admin-api/get-all-relationship-classes.md) you define on your keyset. 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](https://www.pubnub.com/docs/admin-api/create-a-new-relationship-class.md) to declare a class, and [Get relationship class by name and version](https://www.pubnub.com/docs/admin-api/get-relationship-class-by-name-and-version.md) 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)

```go
pn.DataSync.CreateRelationship().
    ID(string).
    EntityAID(string).
    EntityBID(string).
    RelationshipClass(string).
    RelationshipClassVersion(int).
    Status(string).
    Payload(map[string]interface{}).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID`Type: stringDefault: server-generated | Relationship identifier. Omit to let the server generate a UUID. Max 255 characters. |
| `EntityAID` *Type: stringDefault: n/a | Identifier of the first linked entity. Immutable after creation. |
| `EntityBID` *Type: stringDefault: n/a | Identifier of the second linked entity. Immutable after creation. |
| `RelationshipClass` *Type: stringDefault: n/a | Name of the relationship class this instance belongs to. Set at creation and immutable afterward. |
| `RelationshipClassVersion` *Type: intDefault: n/a | Version of the relationship class schema. |
| `Status`Type: stringDefault: n/a | Free-form lifecycle status. Max 100 characters. |
| `Payload`Type: map[string]interfaceDefault: n/a | Free-form JSON object holding your application data. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_createRelationshipBasicUsage demonstrates linking two entities
func Example_createRelationshipBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveUser().ID("seller-bob").Execute()
	pn.DataSync.CreateUser().
		ID("seller-bob").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Bob"}).
		Execute()
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	pn.DataSync.RemoveRelationship().ID("rel-bob-owns-sneaker-42").Execute()
	// snippet.show

	_, status, err := pn.DataSync.CreateRelationship().
		ID("rel-bob-owns-sneaker-42").
		EntityAID("seller-bob").
		EntityBID("product-sneaker-42").
		RelationshipClass("ProductOwner").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"since": "2026-07-13"}).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Relationship created, status: %d\n", status.StatusCode)
}
```

#### Response

```json
{
    "status": 201,
    "data": {
        "id": "rel-bob-owns-sneaker-42",
        "entityAId": "seller-bob",
        "entityBId": "product-sneaker-42",
        "relationshipClass": "ProductOwner",
        "relationshipClassVersion": 1,
        "payload": { "since": "2026-07-13" },
        "createdAt": "2026-07-13T09:20:00.000Z",
        "updatedAt": "2026-07-13T09:20:00.000Z",
        "eTag": "YzAbCdEfGhIjKl"
    }
}
```

### Get relationship

Returns a single relationship by `ID`.

#### Method(s)

```go
pn.DataSync.GetRelationship().
    ID(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Relationship identifier. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_getRelationshipBasicUsage demonstrates reading a single relationship
func Example_getRelationshipBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveUser().ID("seller-bob").Execute()
	pn.DataSync.CreateUser().
		ID("seller-bob").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Bob"}).
		Execute()
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	pn.DataSync.RemoveRelationship().ID("rel-bob-owns-sneaker-42").Execute()
	pn.DataSync.CreateRelationship().
		ID("rel-bob-owns-sneaker-42").
		EntityAID("seller-bob").
		EntityBID("product-sneaker-42").
		RelationshipClass("ProductOwner").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"since": "2026-07-13"}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.GetRelationship().
		ID("rel-bob-owns-sneaker-42").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Relationship retrieved, status: %d\n", status.StatusCode)
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "rel-bob-owns-sneaker-42",
        "entityAId": "seller-bob",
        "entityBId": "product-sneaker-42",
        "relationshipClass": "ProductOwner",
        "relationshipClassVersion": 1,
        "payload": { "since": "2026-07-13" },
        "createdAt": "2026-07-13T09:20:00.000Z",
        "updatedAt": "2026-07-13T09:20:00.000Z",
        "eTag": "YzAbCdEfGhIjKl"
    }
}
```

### 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](https://www.pubnub.com/docs/general/data-sync/data-operations.md#sorting-and-pagination).

#### Method(s)

```go
pn.DataSync.GetRelationships().
    RelationshipClass(string).
    RelationshipClassVersion(int).
    EntityAID(string).
    EntityBID(string).
    Cursor(string).
    Limit(int).
    FilterFast(string).
    Filter(string).
    Sort([]string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `RelationshipClass` *Type: stringDefault: n/a | Name of the relationship class to list. |
| `RelationshipClassVersion`Type: intDefault: all versions | Relationship class version to list. Omit to list relationships across every version of the class. |
| `EntityAID`Type: stringDefault: n/a | List only relationships whose first entity is this id. |
| `EntityBID`Type: stringDefault: n/a | List only relationships whose second entity is this id. |
| `Cursor`Type: stringDefault: n/a | Opaque pagination cursor. Omit for the first page. |
| `Limit`Type: intDefault: `20` | Maximum number of relationships per page. Max `100`. |
| `FilterFast`Type: stringDefault: 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`. |
| `Filter`Type: stringDefault: 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`. |
| `Sort`Type: []stringDefault: n/a | Order results. Each entry is a property name optionally suffixed with `:asc` or `:desc`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_getRelationshipsBasicUsage demonstrates listing relationships of a class
func Example_getRelationshipsBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveUser().ID("seller-bob").Execute()
	pn.DataSync.CreateUser().
		ID("seller-bob").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Bob"}).
		Execute()
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	pn.DataSync.RemoveRelationship().ID("rel-bob-owns-sneaker-42").Execute()
	pn.DataSync.CreateRelationship().
		ID("rel-bob-owns-sneaker-42").
		EntityAID("seller-bob").
		EntityBID("product-sneaker-42").
		RelationshipClass("ProductOwner").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"since": "2026-07-13"}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.GetRelationships().
		RelationshipClass("ProductOwner").
		Limit(20).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Relationships listed, status: %d\n", status.StatusCode)
}
```

#### Response

```json
{
    "status": 200,
    "data": [
        {
            "id": "rel-bob-owns-sneaker-42",
            "entityAId": "seller-bob",
            "entityBId": "product-sneaker-42",
            "relationshipClass": "ProductOwner",
            "relationshipClassVersion": 1,
            "payload": { "since": "2026-07-13" },
            "createdAt": "2026-07-13T09:20:00.000Z",
            "updatedAt": "2026-07-13T09:20:00.000Z",
            "eTag": "YzAbCdEfGhIjKl"
        }
    ],
    "meta": {
        "next_cursor": null,
        "has_next": false,
        "limit": 20
    }
}
```

#### Other examples

##### List an entity's incoming links with EntityBID

Pass `EntityBID` instead of `EntityAID` to list relationships where the entity is on the second side of the link.

```go
// Example_getRelationshipsByEntityBID demonstrates listing an entity's incoming links
func Example_getRelationshipsByEntityBID() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveUser().ID("seller-bob").Execute()
	pn.DataSync.CreateUser().
		ID("seller-bob").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Bob"}).
		Execute()
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	pn.DataSync.RemoveRelationship().ID("rel-bob-owns-sneaker-42").Execute()
	pn.DataSync.CreateRelationship().
		ID("rel-bob-owns-sneaker-42").
		EntityAID("seller-bob").
		EntityBID("product-sneaker-42").
		RelationshipClass("ProductOwner").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"since": "2026-07-13"}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.GetRelationships().
		RelationshipClass("ProductOwner").
		EntityBID("product-sneaker-42").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Incoming relationships listed, status: %d\n", status.StatusCode)
}
```

##### 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). Reference a declared property by its `name`, not its `path`. It shares the same expression language as `Filter`, so only one of the two can be sent per call.

```go
// Example_getRelationshipsFilterFast demonstrates filtering relationships with FilterFast
func Example_getRelationshipsFilterFast() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveUser().ID("seller-bob").Execute()
	pn.DataSync.CreateUser().
		ID("seller-bob").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Bob"}).
		Execute()
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	pn.DataSync.RemoveRelationship().ID("rel-bob-owns-sneaker-42").Execute()
	pn.DataSync.CreateRelationship().
		ID("rel-bob-owns-sneaker-42").
		EntityAID("seller-bob").
		EntityBID("product-sneaker-42").
		RelationshipClass("ProductOwner").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"since": "2026-07-13"}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.GetRelationships().
		RelationshipClass("ProductOwner").
		FilterFast("since == \"2026-07-13\"").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Filtered relationships retrieved, status: %d\n", status.StatusCode)
}
```

##### 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`. It shares the same expression language as `FilterFast`, so only one of the two can be sent per call.

```go
// Example_getRelationshipsFilter demonstrates filtering relationships with Filter
func Example_getRelationshipsFilter() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveUser().ID("seller-bob").Execute()
	pn.DataSync.CreateUser().
		ID("seller-bob").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Bob"}).
		Execute()
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	pn.DataSync.RemoveRelationship().ID("rel-bob-owns-sneaker-42").Execute()
	pn.DataSync.CreateRelationship().
		ID("rel-bob-owns-sneaker-42").
		EntityAID("seller-bob").
		EntityBID("product-sneaker-42").
		RelationshipClass("ProductOwner").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"since": "2026-07-13"}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.GetRelationships().
		RelationshipClass("ProductOwner").
		Filter("tier LIKE \"*gold*\"").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Filtered relationships retrieved, status: %d\n", status.StatusCode)
}
```

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

```go
// Example_getRelationshipsPagination demonstrates paging through the relationship list
func Example_getRelationshipsPagination() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveUser().ID("seller-bob").Execute()
	pn.DataSync.CreateUser().
		ID("seller-bob").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Bob"}).
		Execute()
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	pn.DataSync.RemoveRelationship().ID("rel-bob-owns-sneaker-42").Execute()
	pn.DataSync.CreateRelationship().
		ID("rel-bob-owns-sneaker-42").
		EntityAID("seller-bob").
		EntityBID("product-sneaker-42").
		RelationshipClass("ProductOwner").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"since": "2026-07-13"}).
		Execute()
	// snippet.show

	res, status, err := pn.DataSync.GetRelationships().
		RelationshipClass("ProductOwner").
		Limit(20).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Relationships page retrieved, status: %d\n", status.StatusCode)

	if res.Meta != nil && res.Meta.HasNext {
		_, _, err = pn.DataSync.GetRelationships().
			RelationshipClass("ProductOwner").
			Cursor(res.Meta.NextCursor).
			Limit(20).
			Execute()

		if err != nil {
			fmt.Printf("Error: %v\n", err)
		}
	}
}
```

### Set relationship

Replaces a relationship in full (PUT). Send the complete set of fields, any field you omit is cleared. `RelationshipClass` is immutable after creation and cannot be sent, and the linked entity ids are immutable too, so `SetRelationship` doesn't expose `EntityAID`/`EntityBID` at all, there's nothing to resend. Refer to [optimistic concurrency with ETags](https://www.pubnub.com/docs/general/data-sync/data-operations.md#optimistic-concurrency-with-etags) for `IfMatchETag`. For a partial update, use [Update relationship](#update-relationship).

#### Method(s)

```go
pn.DataSync.SetRelationship().
    ID(string).
    RelationshipClassVersion(int).
    Status(string).
    Payload(map[string]interface{}).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Relationship identifier. |
| `RelationshipClassVersion` *Type: intDefault: n/a | Version of the relationship class schema. |
| `Status`Type: stringDefault: n/a | Free-form lifecycle status. Max 100 characters. |
| `Payload`Type: map[string]interfaceDefault: n/a | Free-form JSON object holding your application data. |
| `IfMatchETag`Type: stringDefault: n/a | The `ETag` from a prior read. The update succeeds only if it still matches, otherwise the server returns `412`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_setRelationshipBasicUsage demonstrates replacing a relationship in full
func Example_setRelationshipBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveUser().ID("seller-bob").Execute()
	pn.DataSync.CreateUser().
		ID("seller-bob").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Bob"}).
		Execute()
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	pn.DataSync.RemoveRelationship().ID("rel-bob-owns-sneaker-42").Execute()
	pn.DataSync.CreateRelationship().
		ID("rel-bob-owns-sneaker-42").
		EntityAID("seller-bob").
		EntityBID("product-sneaker-42").
		RelationshipClass("ProductOwner").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"since": "2026-07-13"}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.SetRelationship().
		ID("rel-bob-owns-sneaker-42").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"since": "2026-07-13", "tier": "gold"}).
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Relationship replaced, status: %d\n", status.StatusCode)
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "rel-bob-owns-sneaker-42",
        "entityAId": "seller-bob",
        "entityBId": "product-sneaker-42",
        "relationshipClass": "ProductOwner",
        "relationshipClassVersion": 1,
        "payload": { "since": "2026-07-13", "tier": "gold" },
        "createdAt": "2026-07-13T09:20:00.000Z",
        "updatedAt": "2026-07-13T14:00:00.000Z",
        "eTag": "AbCdEfGhIjKlMn"
    }
}
```

### Update relationship

Applies a partial update to a relationship with raw JSON Patch operations. Refer to [partial update](https://www.pubnub.com/docs/general/data-sync/data-operations.md#partial-update) for the operation model.

#### Method(s)

```go
pn.DataSync.UpdateRelationship().
    ID(string).
    Add(string, interface{}).
    Remove(string).
    Replace(string, interface{}).
    Move(string, string).
    Copy(string, string).
    Test(string, interface{}).
    Operations([]PNJSONPatchOperation).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Relationship identifier. |
| `Operations`Type: `[]PNJSONPatchOperation`Default: n/a | At least one patch operation is required before `Execute()`. Build the list with chained `Add`, `Remove`, `Replace`, `Move`, `Copy`, and `Test` calls, or pass a complete slice through `Operations`. |
| `IfMatchETag`Type: stringDefault: 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.

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_updateRelationshipBasicUsage demonstrates applying a JSON Patch update to a relationship
func Example_updateRelationshipBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveUser().ID("seller-bob").Execute()
	pn.DataSync.CreateUser().
		ID("seller-bob").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Bob"}).
		Execute()
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	pn.DataSync.RemoveRelationship().ID("rel-bob-owns-sneaker-42").Execute()
	pn.DataSync.CreateRelationship().
		ID("rel-bob-owns-sneaker-42").
		EntityAID("seller-bob").
		EntityBID("product-sneaker-42").
		RelationshipClass("ProductOwner").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"since": "2026-07-13", "tier": "gold"}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.UpdateRelationship().
		ID("rel-bob-owns-sneaker-42").
		Replace("/payload/tier", "platinum").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Relationship updated, status: %d\n", status.StatusCode)
}
```

#### Response

```json
{
    "status": 200,
    "data": {
        "id": "rel-bob-owns-sneaker-42",
        "entityAId": "seller-bob",
        "entityBId": "product-sneaker-42",
        "relationshipClass": "ProductOwner",
        "relationshipClassVersion": 1,
        "payload": { "since": "2026-07-13", "tier": "platinum" },
        "createdAt": "2026-07-13T09:20:00.000Z",
        "updatedAt": "2026-07-13T14:05:00.000Z",
        "eTag": "CdEfGhIjKlMnOp"
    }
}
```

### Remove relationship

Deletes a relationship by `ID`.

#### Method(s)

```go
pn.DataSync.RemoveRelationship().
    ID(string).
    IfMatchETag(string).
    Execute()
```

| Parameter | Description |
| --- | --- |
| `ID` *Type: stringDefault: n/a | Relationship identifier. |
| `IfMatchETag`Type: stringDefault: n/a | The `ETag` from a prior read. The delete succeeds only if it still matches, otherwise the server returns `412`. |

#### Sample code

:::tip 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.
:::

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_removeRelationshipBasicUsage demonstrates deleting a relationship
func Example_removeRelationshipBasicUsage() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	// snippet.hide
	pn.DataSync.RemoveUser().ID("seller-bob").Execute()
	pn.DataSync.CreateUser().
		ID("seller-bob").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Bob"}).
		Execute()
	pn.DataSync.RemoveEntity().ID("product-sneaker-42").Execute()
	pn.DataSync.CreateEntity().
		ID("product-sneaker-42").
		EntityClass("product").
		EntityClassVersion(1).
		Payload(map[string]interface{}{"name": "Retro Sneaker", "price": 89.99, "stock": 12}).
		Execute()
	pn.DataSync.RemoveRelationship().ID("rel-bob-owns-sneaker-42").Execute()
	pn.DataSync.CreateRelationship().
		ID("rel-bob-owns-sneaker-42").
		EntityAID("seller-bob").
		EntityBID("product-sneaker-42").
		RelationshipClass("ProductOwner").
		RelationshipClassVersion(1).
		Payload(map[string]interface{}{"since": "2026-07-13"}).
		Execute()
	// snippet.show

	_, status, err := pn.DataSync.RemoveRelationship().
		ID("rel-bob-owns-sneaker-42").
		Execute()

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Relationship removed, status: %d\n", status.StatusCode)
}

// ==================== Real-time updates ====================
```

#### Response

```json
{
    "status": 200
}
```

## Real-time updates

DataSync objects can publish `create`, `update`, and `delete` events that you receive in real time on the `listener.DataSyncEvent` channel after attaching a `Listener` to the PubNub client with `AddListener`. Subscribe to the object's data channel with `pn.Subscribe().Channels([]string{...}).Execute()` and read from `listener.DataSyncEvent` in a select loop, as described in [Add DataSync listener](https://www.pubnub.com/docs/sdks/go/api-reference/publish-and-subscribe.md#add-datasync-listener).

```go
// Replace with your package name (usually "main")
package pubnub_samples_test

import (
	"fmt"
	"time"

	pubnub "github.com/pubnub/go/v10"
)

// Example_dataSyncEventListener demonstrates receiving DataSync events with a listener
func Example_dataSyncEventListener() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	listener := pubnub.NewListener()
	done := make(chan bool)

	go func() {
		for {
			select {
			case event := <-listener.DataSyncEvent:
				if event.Event == pubnub.PNDataSyncEventDelete {
					fmt.Printf("DataSync %s deleted: %s\n", event.Type, event.ID)
					continue
				}
				fmt.Printf("DataSync %s %s on channel %s\n", event.Type, event.Event, event.Channel)
			case <-done:
				return
			}
		}
	}()

	pn.AddListener(listener)

	pn.Subscribe().
		Channels([]string{"product-sneaker-42"}).
		Execute()

	fmt.Println("Subscribed to DataSync events")

	pn.UnsubscribeAll()
	close(done)

	// snippet.hide
	time.Sleep(100 * time.Millisecond)
	// snippet.show

	// Output:
	// Subscribed to DataSync events
}
```

Each event names the change in `Event`, identifies the object kind in `Type` (`user`, `channel`, `membership`, `entity`, or `relationship`), and carries the object state in `Entity`, `Relationship`, or `Membership`. For a `delete` event, `ID` and `DeletedAt` carry the identifier and the deletion timestamp, and the three state fields stay `nil`.

`Type` names the object kind directly, while create and update events populate one of three typed fields:

| Change to | `Type` | State arrives in |
| --- | --- | --- |
| A user | `user` | `Entity` |
| A channel | `channel` | `Entity` |
| An entity | `entity` | `Entity` |
| A membership | `membership` | `Membership` |
| A relationship | `relationship` | `Relationship` |

Dispatch on `Type` rather than on which field is populated, because `Entity` is shared by three kinds. `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 to | On `create` | On `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` |

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

### Projection channels

Each [projection](https://www.pubnub.com/docs/sdks/go/api-reference/access-manager.md#grant-token) declared on the class gets its own event, published to its own channel, and carrying only that projection's view of the payload:

| Projection | Channel | Payload |
| --- | --- | --- |
| `__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:

```go
// Example_subscribeToProjectionChannels demonstrates subscribing to both a base and a projection channel
func Example_subscribeToProjectionChannels() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"     // Replace with your subscribe key
	config.PublishKey = "demo"       // Replace with your publish key
	config.SecretKey = "demo-secret" // Replace with your secret key (required for Access Manager)

	// snippet.hide
	config = setPubnubExampleDataSyncConfigData(config)
	// snippet.show

	pn := pubnub.NewPubNub(config)

	pn.Subscribe().
		Channels([]string{"product-sneaker-42", "__admin__product-sneaker-42"}).
		Execute()

	fmt.Println("Subscribed to base and admin projection channels")

	pn.UnsubscribeAll()

	// snippet.hide
	time.Sleep(100 * time.Millisecond)
	// snippet.show

	// Output:
	// Subscribed to base and admin projection channels
}

// ==================== Access Manager ====================
```

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.

:::warning 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](https://www.pubnub.com/docs/sdks/go/api-reference/access-manager.md#grant-token).
:::

Delete events are never filtered by a projection.

Events are off by default and are enabled per class in the [Admin Portal](https://admin.pubnub.com). Refer to [enabling events](https://www.pubnub.com/docs/general/data-sync/events.md#enable-datasync-events) for how to turn them on, and [receiving events](https://www.pubnub.com/docs/general/data-sync/events.md#receive-datasync-events) for the listener flow.

Last updated at: 2026-09-22T12:01:30.000Z
