---
source_url: https://www.pubnub.com/docs/sdks/unreal/migration-guides/unreal-v2-migration-guide
title: Unreal SDK 2.0.0 Migration Guide
updated_at: 2026-06-29T11:50:19.893Z
sdk_name: PubNub Unreal SDK
sdk_version: 2.1.0
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Unreal SDK 2.0.0 Migration Guide

PubNub Unreal SDK, use the latest version: 2.1.0

This guide summarizes the differences between versions 1.x.x and 2.0.0 and shows how to migrate to Unreal SDK 2.0.0.

Unreal SDK version 2.0.0 introduces `UPubnubClient` as the primary API entry point, enabling multiple independent PubNub contexts. This release also adds synchronous method variants, an advanced logging system, structured result types, and dedicated input structs for App Context operations.

:::warning No Unreal SDK 1.x.x support
If your application uses Unreal SDK 1.x.x, it continues to work. We recommend migrating to Unreal 2.0.0 to access new features and improvements. Version 1.x.x receives only critical security fixes.
:::

## What has changed

See the major differences between versions:

| Feature/Method | Unreal SDK 1.x.x | Unreal SDK 2.0.0 |
| --- | --- | --- |
| [Entry point](#upubnubclient-replaces-upubnubsubsystem-as-the-primary-entry-point) | `UPubnubSubsystem` (single context) | `UPubnubClient` (multiple contexts) |
| [Method pattern](#synchronous-and-asynchronous-method-variants) | Async-only (callback required) | Sync + Async variants (`Method()` and `MethodAsync()`) |
| [Operation results](#new-result-structs-with-fpubnuboperationresult) | Returned via callback params | `FPubnubOperationResult` in result structs and callbacks |
| [Delegate names](#delegate-renames) | `FOn...Response` (e.g., `FOnPublishMessageResponse`) | `FOnPubnub...Response` (e.g., `FOnPubnubPublishMessageResponse`) |
| [Pagination](#pagination-with-fpubnubpage) | Separate `PageNext`/`PagePrev` strings | `FPubnubPage` struct with `Next` and `Prev` |
| [App Context inputs](#new-input-structs-for-app-context) | `FPubnubUserData` for set operations | `FPubnubUserInputData` with `ForceSet*` fields |
| [Paginated total count](#totalcount-in-paginated-responses) | Not available | `TotalCount` field in result structs and `IncludeTotalCount` in include structs |
| [Logging](#advanced-logger-system) | UE_LOG only | Configurable `IPubnubLoggerInterface` with levels and sources |
| [Entities](#entity-and-subscription-changes) | Reference `UPubnubSubsystem` | Reference `UPubnubClient` |

## Breaking changes

### UPubnubClient replaces UPubnubSubsystem as the primary entry point

Unreal SDK 2.0.0 introduces `UPubnubClient` as the main class for all PubNub operations. `UPubnubSubsystem` now serves as a factory that creates and manages `UPubnubClient` instances. This lets you run multiple independent PubNub contexts simultaneously, each with its own keys, user ID, and subscriptions.

#### Before (v1.x.x)

```cpp
// Get the subsystem and call methods directly
UPubnubSubsystem* PubnubSubsystem = GetGameInstance()
    ->GetSubsystem<UPubnubSubsystem>();

PubnubSubsystem->InitPubnub();
PubnubSubsystem->SetUserID("my-user");
PubnubSubsystem->PublishMessage(
    "my-channel",
    "Hello!",
    FOnPublishMessageResponse()
);
```

#### After (v2.x.x)

```cpp
// Get the subsystem, create a client, then call methods on the client
UPubnubSubsystem* PubnubSubsystem = GetGameInstance()
    ->GetSubsystem<UPubnubSubsystem>();

FPubnubConfig PubnubConfig = FPubnubConfig({.PublishKey = "demo", .SubscribeKey = "demo", .UserID = "Player_001"});
UPubnubClient* PubnubClient = PubnubSubsystem->CreatePubnubClient(PubnubConfig);

PubnubClient->PublishMessageAsync(
    "my-channel",
    "Hello!",
    FOnPubnubPublishMessageResponse()
);
```

`FPubnubConfig` now includes a `LoggerConfig` field of type `FPubnubLoggerConfig` for configuring the [advanced logger](#advanced-logger-system).

### Synchronous and asynchronous method variants

In v1.x.x, all methods were asynchronous and required a callback delegate. In v2.0.0, every method has both a synchronous (blocking) and asynchronous variant:

* Synchronous `MethodName()`, which blocks until complete and returns a result struct
* Asynchronous `MethodNameAsync()`, which is non-blocking with a callback delegate

#### Before (v1.x.x)

```cpp
// Async-only, callback was required for Blueprint-exposed version
FOnPublishMessageResponseNative PublishMessageResponse;
PublishMessageResponse.BindLambda([](FPubnubMessageData Msg)
{
    // Handle result
});
PubnubSubsystem->PublishMessage(
    "my-channel",
    "Hello!",
    PublishMessageResponse);
```

#### After (v2.x.x)

```cpp
// Synchronous - blocks and returns result
FPubnubPublishMessageResult SyncResult = PubnubClient->PublishMessage(
    "my-channel",
    "Hello!"
);

if (!SyncResult.Result.Error)
{
    // Success - use SyncResult.PublishedMessage
}

// Asynchronous - use native callback with BindLambda (do not use CreateLambda directly in the call)
FOnPubnubPublishMessageResponseNative PublishMessageResponse;
PublishMessageResponse.BindLambda([](FPubnubOperationResult Result, FPubnubMessageData Msg)
{
    // Handle result
});
PubnubClient->PublishMessageAsync(
    "my-channel",
    "Hello!",
    PublishMessageResponse);
```

This pattern applies to all methods.

### New result structs with FPubnubOperationResult

Unreal SDK 2.0.0 introduces `FPubnubOperationResult` as a standardized error-reporting struct that is included in all result types.

```cpp
USTRUCT(BlueprintType)
struct FPubnubOperationResult
{
    // HTTP status code. 200 if the operation succeeded.
    int Status = 0;
    // Whether the operation encountered an error.
    bool Error = false;
    // Description of the error, if any.
    FString ErrorMessage = "";
};
```

Each operation now has a dedicated result struct that wraps `FPubnubOperationResult` with operation-specific data:

| Result struct | Fields (besides `Result`) |
| --- | --- |
| `FPubnubPublishMessageResult` | `PublishedMessage` |
| `FPubnubSignalResult` | `SignalMessage` |
| `FPubnubListChannelsFromGroupResult` | `Channels` |
| `FPubnubListUsersFromChannelResult` | `Data` |
| `FPubnubListUsersSubscribedChannelsResult` | `Channels` |
| `FPubnubGetStateResult` | `StateResponse` |
| `FPubnubGrantTokenResult` | `Token` |
| `FPubnubFetchHistoryResult` | `Messages` |
| `FPubnubMessageCountsResult` | `MessageCounts` |
| `FPubnubMessageCountsMultipleResult` | `MessageCountsPerChannel` |
| `FPubnubGetAllUserMetadataResult` | `UsersData`, `Page`, `TotalCount` |
| `FPubnubGetAllChannelMetadataResult` | `ChannelsData`, `Page`, `TotalCount` |
| `FPubnubUserMetadataResult` | `UserData` |
| `FPubnubChannelMetadataResult` | `ChannelData` |
| `FPubnubMembershipsResult` | `MembershipsData`, `Page`, `TotalCount` |
| `FPubnubChannelMembersResult` | `MembersData`, `Page`, `TotalCount` |
| `FPubnubGetMessageActionsResult` | `MessageActions` |
| `FPubnubAddMessageActionResult` | `MessageActionData` |

These structs are returned by synchronous methods and are also used as the first parameter in async callback delegates.

### Delegate renames

Callback delegates on `UPubnubClient` are prefixed with `FOnPubnub` to avoid naming collisions and improve clarity. The `UPubnubSubsystem` retains the old delegate names for backward compatibility.

| v1.x.x delegate | v2.0.0 client delegate |
| --- | --- |
| `FOnPublishMessageResponse` | `FOnPubnubPublishMessageResponse` |
| `FOnSignalResponse` | `FOnPubnubSignalResponse` |
| `FOnSubscribeOperationResponse` | `FOnPubnubSubscribeOperationResponse` |
| `FOnAddChannelToGroupResponse` | `FOnPubnubAddChannelToGroupResponse` |
| `FOnRemoveChannelFromGroupResponse` | `FOnPubnubRemoveChannelFromGroupResponse` |
| `FOnListChannelsFromGroupResponse` | `FOnPubnubListChannelsFromGroupResponse` |
| `FOnRemoveChannelGroupResponse` | `FOnPubnubRemoveChannelGroupResponse` |
| `FOnListUsersSubscribedChannelsResponse` | `FOnPubnubListUsersSubscribedChannelsResponse` |
| `FOnListUsersFromChannelResponse` | `FOnPubnubListUsersFromChannelResponse` |
| `FOnSetStateResponse` | `FOnPubnubSetStateResponse` |
| `FOnGetStateResponse` | `FOnPubnubGetStateResponse` |
| `FOnGrantTokenResponse` | `FOnPubnubGrantTokenResponse` |
| `FOnRevokeTokenResponse` | `FOnPubnubRevokeTokenResponse` |
| `FOnFetchHistoryResponse` | `FOnPubnubFetchHistoryResponse` |
| `FOnMessageCountsResponse` | `FOnPubnubMessageCountsResponse` |
| `FOnDeleteMessagesResponse` | `FOnPubnubDeleteMessagesResponse` |
| `FOnGetAllUserMetadataResponse` | `FOnPubnubGetAllUserMetadataResponse` |
| `FOnGetUserMetadataResponse` | `FOnPubnubGetUserMetadataResponse` |
| `FOnSetUserMetadataResponse` | `FOnPubnubSetUserMetadataResponse` |
| `FOnRemoveUserMetadataResponse` | `FOnPubnubRemoveUserMetadataResponse` |
| `FOnGetAllChannelMetadataResponse` | `FOnPubnubGetAllChannelMetadataResponse` |
| `FOnGetChannelMetadataResponse` | `FOnPubnubGetChannelMetadataResponse` |
| `FOnSetChannelMetadataResponse` | `FOnPubnubSetChannelMetadataResponse` |
| `FOnRemoveChannelMetadataResponse` | `FOnPubnubRemoveChannelMetadataResponse` |
| `FOnGetMembershipsResponse` | `FOnPubnubGetMembershipsResponse` |
| `FOnSetMembershipsResponse` | `FOnPubnubSetMembershipsResponse` |
| `FOnRemoveMembershipsResponse` | `FOnPubnubRemoveMembershipsResponse` |
| `FOnGetChannelMembersResponse` | `FOnPubnubGetChannelMembersResponse` |
| `FOnSetChannelMembersResponse` | `FOnPubnubSetChannelMembersResponse` |
| `FOnRemoveChannelMembersResponse` | `FOnPubnubRemoveChannelMembersResponse` |
| `FOnGetMessageActionsResponse` | `FOnPubnubGetMessageActionsResponse` |
| `FOnAddMessageActionResponse` | `FOnPubnubAddMessageActionResponse` |
| `FOnRemoveMessageActionResponse` | `FOnPubnubRemoveMessageActionResponse` |

Multicast delegates on the client also changed:

| v1.x.x (on subsystem) | v2.0.0 (on client) |
| --- | --- |
| `FOnMessageReceived` | `FOnPubnubMessageReceived` |
| `FOnSubscriptionStatusChanged` | `FOnPubnubSubscriptionStatusChanged` |

### Pagination with FPubnubPage

Paginated operations now use a `FPubnubPage` struct instead of separate `PageNext` and `PagePrev` string parameters.

#### Before (v1.x.x)

```cpp
// Separate string parameters - use BindLambda for async callback (not CreateLambda in call)
FOnGetAllUserMetadataResponseNative GetAllUserMetadataResponse;
GetAllUserMetadataResponse.BindLambda([](FPubnubOperationResult Result,
    const TArray<FPubnubUserData>& Users,
    FString PageNext,
    FString PagePrev)
{
    // Use PageNext for next page
});
PubnubSubsystem->GetAllUserMetadata(GetAllUserMetadataResponse);

// Passing page tokens as separate strings
PubnubSubsystem->GetAllUserMetadata(
    MyCallback,
    FPubnubGetAllInclude(),
    100,    // Limit
    "",     // Filter
    FPubnubGetAllSort(),
    PageNext,  // FString
    PagePrev   // FString
);
```

#### After (v2.x.x)

```cpp
// FPubnubPage struct in result
FPubnubGetAllUserMetadataResult SyncResult =
    PubnubClient->GetAllUserMetadata();

FString NextPage = SyncResult.Page.Next;

// Pass FPubnubPage for next request
FPubnubPage Page;
Page.Next = NextPage;

FPubnubGetAllUserMetadataResult NextResult =
    PubnubClient->GetAllUserMetadata(
        FPubnubGetAllInclude(),
        100,    // Limit
        "",     // Filter
        FPubnubGetAllSort(),
        Page    // FPubnubPage
    );
```

### New input structs for App Context

Set operations for App Context now use dedicated input structs instead of the full data structs. These input structs exclude server-generated fields (`UserID`/`ChannelID`, `Updated`, `ETag`) and include `ForceSet*` booleans to explicitly set empty fields to `null` on the server.

| v1.x.x input type | v2.0.0 input type |
| --- | --- |
| `FPubnubUserData` | `FPubnubUserInputData` |
| `FPubnubChannelData` | `FPubnubChannelInputData` |
| `FPubnubMembershipInputData` | `FPubnubMembershipInputData` (with `ForceSet*` fields) |
| `FPubnubChannelMemberInputData` | `FPubnubChannelMemberInputData` (with `ForceSet*` fields) |

#### Before (v1.x.x)

```cpp
// Used FPubnubUserData directly (UserID field ignored)
FPubnubUserData UserData;
UserData.UserName = "John Doe";
UserData.Email = "john@example.com";

PubnubSubsystem->SetUserMetadata(
    "user-123",
    UserData,
    FOnSetUserMetadataResponse()
);
```

#### After (v2.x.x)

```cpp
// Use FPubnubUserInputData (no UserID/Updated/ETag)
FPubnubUserInputData UserInput;
UserInput.UserName = "John Doe";
UserInput.Email = "john@example.com";

// Synchronous
FPubnubUserMetadataResult Result =
    PubnubClient->SetUserMetadata("user-123", UserInput);

// To clear a field on the server, use ForceSet
FPubnubUserInputData ClearInput;
ClearInput.ForceSetEmail = true;  // Email="" sent as null
ClearInput.UserName = "John Doe";

// Or force-set all fields at once
ClearInput.ForceSetAllFields();
```

Each input struct provides a static converter from the corresponding data struct:

```cpp
// Convert from server response data to input data
FPubnubUserInputData Input =
    FPubnubUserInputData::FromPubnubUserData(existingUserData);
```

### TotalCount in paginated responses

Paginated responses now include a `TotalCount` field. To request it, set `IncludeTotalCount = true` in the relevant include struct (`FPubnubGetAllInclude`, `FPubnubMembershipInclude`, or `FPubnubMemberInclude`).

#### Before (v1.x.x)

```cpp
// No TotalCount available - use BindLambda for async callback (not CreateLambda in call)
FOnGetAllUserMetadataResponseNative GetAllUserMetadataResponse;
GetAllUserMetadataResponse.BindLambda([](FPubnubOperationResult Result,
    const TArray<FPubnubUserData>& Users,
    FString PageNext,
    FString PagePrev)
{
    // No way to know total count
});
PubnubSubsystem->GetAllUserMetadata(GetAllUserMetadataResponse);
```

#### After (v2.x.x)

```cpp
// Request TotalCount
FPubnubGetAllInclude Include;
Include.IncludeTotalCount = true;

FPubnubGetAllUserMetadataResult Result =
    PubnubClient->GetAllUserMetadata(Include);

int Total = Result.TotalCount;
```

### Advanced logger system

Unreal SDK 2.0.0 replaces basic `UE_LOG` logging with a configurable logger system. You can control log levels, filter by source (UE SDK or C-Core), and register custom loggers.

Enable and configure the default logger through `FPubnubLoggerConfig` in `FPubnubConfig`:

```cpp
// ACTION REQUIRED: Replace ASample_Configuration with name of your Actor class
void ASample_Configuration::SetLogLevelInConfigurationSample()
{
	//Get PubnubSubsystem from GameInstance
	UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this);
	UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem<UPubnubSubsystem>();

	//Set logger levels directly in client configuration before creating PubnubClient
	FPubnubConfig PubnubConfig;
	PubnubConfig.PublishKey = TEXT("demo");   //replace with your Publish Key from Admin Portal
	PubnubConfig.SubscribeKey = TEXT("demo"); //replace with your Subscribe Key from Admin Portal
	PubnubConfig.UserID = TEXT("Player_001");
	PubnubConfig.LoggerConfig.DefaultLoggerMinLevel = EPubnubLogLevel::PLL_Debug;
	PubnubConfig.LoggerConfig.DefaultLoggerMinCCoreLevel = EPubnubLogLevel::PLL_Warning;

	UPubnubClient* PubnubClient = PubnubSubsystem->CreatePubnubClient(PubnubConfig);

	//Use PubnubClient as needed
	PubnubClient->PublishMessageAsync(TEXT("test-channel"), TEXT("Hello, world!"));
}
```

For details on log levels, log sources, and creating custom loggers, refer to the [Unreal SDK Logging documentation](https://www.pubnub.com/docs/sdks/unreal/logging).

### Entity and subscription changes

Entities and subscriptions now reference `UPubnubClient` instead of `UPubnubSubsystem`. Entity methods follow the same sync/async pattern introduced in 2.0.0. For async entity methods, use `BindLambda` on a native callback (do not use `CreateLambda` directly in the call).

#### Before (v1.x.x)

```cpp
// Entities were created by the subsystem
UPubnubChannelEntity* Channel =
    PubnubSubsystem->CreateChannelEntity("my-channel");

// All methods were async-only - use BindLambda for callback
FOnPublishMessageResponseNative PublishMessageResponse;
PublishMessageResponse.BindLambda([](FPubnubMessageData Msg)
{
    // Handle result
});
Channel->PublishMessage("Hello!", PublishMessageResponse);

// Subscription referenced subsystem internally
UPubnubSubscription* Sub = Channel->CreateSubscription();
Sub->Subscribe();
```

#### After (v2.x.x)

```cpp
// Entities are created by the client
UPubnubChannelEntity* Channel =
    PubnubClient->CreateChannelEntity("my-channel");

// Sync variant
FPubnubPublishMessageResult Result =
    Channel->PublishMessage("Hello!");

// Async variant - use BindLambda for callback (not CreateLambda in call)
FOnPubnubPublishMessageResponseNative PublishMessageResponse;
PublishMessageResponse.BindLambda([](FPubnubOperationResult Result, FPubnubMessageData Msg)
{
    // Handle result
});
Channel->PublishMessageAsync("Hello!", PublishMessageResponse);

// Subscription references client internally
UPubnubSubscription* Sub = Channel->CreateSubscription();
FPubnubOperationResult SubResult = Sub->Subscribe();

// Or async - use BindLambda for callback
FOnPubnubSubscribeOperationResponseNative SubscribeResponse;
SubscribeResponse.BindLambda([](FPubnubOperationResult Result)
{
    // Handle result
});
Sub->SubscribeAsync(SubscribeResponse);
```

## Migration steps

To migrate from Unreal SDK 1.x.x to 2.0.0:

1. Update your PubNub plugin to version 2.0.0.
2. Replace direct UPubnubSubsystem method calls with UPubnubClient: ActionDescriptionCreate a UPubnubClient using UPubnubSubsystem::CreatePubnubClient()All PubNub operations are now called on UPubnubClient instead of UPubnubSubsystem.Pass FPubnubConfig to CreatePubnubClient()Configuration is now passed when creating a client, including the new LoggerConfig field.
3. Update method names to the new sync/async pattern: ActionDescriptionRename async method calls from MethodName() to MethodNameAsync()For example, PublishMessage(Channel, Message, Callback) becomes PublishMessageAsync(Channel, Message, Callback).Use sync methods where callbacks are not neededFor example, FPubnubPublishMessageResult Result = Client->PublishMessage(Channel, Message).
4. Update delegate type names: ActionDescriptionAdd Pubnub prefix to all delegate typesFor example, FOnPublishMessageResponse becomes FOnPubnubPublishMessageResponse.
5. Update pagination parameters: ActionDescriptionReplace PageNext/PagePrev string params with FPubnubPageCreate a FPubnubPage struct and set its Next/Prev fields.Update callbacks to use FPubnubPage instead of separate stringsPaginated callbacks now pass FPubnubPage Page and int TotalCount instead of FString PageNext, FString PagePrev.
6. Update App Context set operations: ActionDescriptionReplace FPubnubUserData with FPubnubUserInputData in SetUserMetadataThe new struct excludes server-generated fields and adds ForceSet* booleans.Replace FPubnubChannelData with FPubnubChannelInputData in SetChannelMetadataSame pattern as user metadata.Use FromPubnubUserData() / FromPubnubChannelData() converters if neededStatic methods on input structs convert from response data types.
7. Configure logging: ActionDescriptionSet LoggerConfig in FPubnubConfig if you need custom log levelsThe default logger is enabled by default with Warning level.Implement IPubnubLoggerInterface for custom loggingRegister custom loggers via Config.LoggerConfig.InitialLoggers.
8. Update entity usage: ActionDescriptionCreate entities from UPubnubClient instead of UPubnubSubsystemFor example, Client->CreateChannelEntity() instead of Subsystem->CreateChannelEntity().Update entity method calls to the sync/async patternEntity methods follow the same naming convention as client methods.
9. Test your application. Pay special attention to initialization flow, publish/subscribe operations, App Context calls, and entity-based subscriptions.

## Additional resources

For API details, see the [Unreal SDK documentation](https://www.pubnub.com/docs/sdks/unreal). For questions or issues, contact [PubNub support](https://support.pubnub.com/).