Cloud Save

Overview

Cloud Save is a service that stores arbitrary data in JSON format. With Cloud save, you can store, retrieve, update, and delete any data from your game. The game data can be stored in one of two types of records:

  • Game Records store game data such as event configurations and themes.
  • Player Records store user’s records such as saved game data.

Prerequisites

Permissions

Make sure you’re authorized to use our services and have the following permissions before you attempt to manage players’ entitlements:

UsageResourceAction
Save namespace level recordADMIN:NAMESPACE:{namespace}:CLOUDSAVE:RECORD1
Save or replace game recordADMIN:NAMESPACE:{namespace}:CLOUDSAVE:RECORD4
Purge all records under the given keyADMIN:NAMESPACE:{namespace}:CLOUDSAVE:RECORD9
Retrieve list of records key by namespaceADMIN:NAMESPACE:{namespace}:CLOUDSAVE:RECORD2
Retrieve a record value by its keyADMIN:NAMESPACE:{namespace}:CLOUDSAVE:RECORD2

Here’s the list of Action in Permission:

Action NumberDescription
1Create
2Read
3Create, Read
4Update
5Create, Update
6Read, Update
7Create, Read, Update
8Delete
9Create, Delete
10Read, Delete
11Create, Read, Delete
12Update, Delete
13Create, Update, Delete
14Read, Update, Delete
15Create, Read, Update, Delete

Tutorials

Managing Your Game with Cloud Save in the Admin Portal

The Admin Portal gives community managers and game admins an easy way to manage and integrate Cloud Save.

Create a New Game Record

  1. In the Game Management section of the Admin Portal, go the Cloud Save menu and choose Game Records.

    cloud-save

  2. On the Cloud Save page, click the Add Record button.

    cloud-save

  3. Input the required information into the form below.

    cloud-save

    • Input the Game Record Key using the appropriate format.
    • Input the JSON Configuration.
  4. When you’re done, click Add. The new record will be added to the list.

Update a Game Record

  1. In the Admin Portal, go to the Cloud Save page and choose the record you want to update.

    cloud-save

  2. In the Record Detail window, go to the JSON Configuration section and click Edit.

    cloud-save

  3. Input the updated record information and click Save.

    cloud-save

Delete a Game Record

  1. In the Admin Portal, go to the Cloud Save page and choose the record you want to delete by clicking the Delete button in the Action column of that record.

    cloud-save

  2. Confirm that you want to delete the record in the form that appears.

Search for a Player Record

  1. In the Cloud Save menu of the Admin Portal, click Player Records. In the search box above, you can search for a player record by either typing the User ID or Key and pressing Enter.

    cloud-save

  2. The related records will appear. In the example below, we’ve searched for a record by its Key.

    cloud-save

Retrieve a Game Record

  1. In the Admin Portal, go to the Cloud Save page and open the record you want to view by clicking the View button in the Action column.

    cloud-save

  2. The Record Detail page will open.

    cloud-save

Retrieve a Player Record

  1. In the Cloud Save menu of the Admin Portal, click Player Records and open the record you want to view by clicking the View button in the Action column.

    cloud-save

  2. The Record Detail page will open.

Integrating Your Game with Cloud Save Using SDK

You can use our SDK to manage and Integrate your game with Cloud Save.

Integrating Your Game with Cloud Save at the User Level

To start integrating your game with Cloud Save, make sure you have configured the AccelByte SDK, then follow the steps below. After that, you have to set the Cloud Save URL in the Assets/Resources/AccelByteSDKConfig.json file if you’re using Unity SDK, and in the DefaultEngine.ini file if you're using UE4 SDK.

//Set the Cloud Save URL in the DefaultEngine.ini file.
...
CloudSaveServerUrl="https://api.demo.accelbyte.io/cloudsave"
...
Create a New Record

This example shows you how to store user data inside the UserXyzAbGame key using the SaveUserRecord() function. Please note that if the key already exists, the new record will be appended to the existing record.

FString Key = “UserXyzAbGame”;
FJsonObject record1Test;
record1Test.SetNumberField("numRegion", 6);
record1Test.SetNumberField("oilsReserve", 125.10);
record1Test.SetStringField("islandName", "tartar friendly land");
TArray<TSharedPtr<FJsonValue>> buildings{
MakeShareable(new FJsonValueString("oilRefinery")),
MakeShareable(new FJsonValueString("oilWell")),
MakeShareable(new FJsonValueString("watchTower")),
MakeShareable(new FJsonValueString("defendsTower"))
};
record1Test.SetArrayField("buildings", buildings);
TSharedPtr<FJsonObject> resources = MakeShareable(new FJsonObject);
resources->SetNumberField("gas", 20);
resources->SetNumberField("water", 100);
resources->SetNumberField("gold", 10);
record1Test.SetObjectField("resources", resources);
bool bSaveUserRecord1Success = false;
FRegistry::CloudSave.SaveUserRecord(
Key,
record1Test,
FVoidHandler::CreateLambda([&bSaveUserRecord1Success]()
{
UE_LOG(LogAccelByteCloudSaveTest, Log, TEXT("Save user record1 success"));
bSaveUserRecord1Success = true;
}),
CloudSaveErrorHandler);
Retrieve a User Record

User data stored in a private record can be retrieved using the GetUserRecord() function.

FAccelByteModelsUserRecord GetUserRecordResult;
FRegistry::CloudSave.GetUserRecord(
Key,
THandler<FAccelByteModelsUserRecord>::CreateLambda(
[&GetUserRecordResult]
(FAccelByteModelsUserRecord UserRecord)
{
UE_LOG(LogAccelByteCloudSaveTest, Log, TEXT("Get user record success"));
GetUserRecordResult = UserRecord;
}),
CloudSaveErrorHandler);

A public user record can be retrieved by another user. To get another user’s public records, you can use the GetPublicUserRecord() function.

FAccelByteModelsUserRecord GetUserRecordResult;
FString UserId = "123456789";
FRegistry::CloudSave.GetPublicUserRecord(
Key,
UserId,
THandler<FAccelByteModelsUserRecord>::CreateLambda(
[&GetUserRecordResult]
(FAccelByteModelsUserRecord UserRecord)
{
UE_LOG(LogAccelByteCloudSaveTest, Log, TEXT("Get user record success"));
GetUserRecordResult = UserRecord;
}),
CloudSaveErrorHandler);
Replace a User Record

You can update data using the ReplaceUserRecord() function, even when the data does not exist. If the data does not exist, it will be created based on the data in the update. This data can be stored either publicly or privately.

FJsonObject newRecord1Test;
newRecord1Test.SetNumberField("numRegion", 10);
newRecord1Test.SetNumberField("oilsReserve", 100);
newRecord1Test.SetStringField("islandName", "salad friendly land");
TArray<TSharedPtr<FJsonValue>> buildings{
MakeShareable(new FJsonValueString("gasRefinery")),
MakeShareable(new FJsonValueString("gasWell")),
MakeShareable(new FJsonValueString("waterTower")),
MakeShareable(new FJsonValueString("mainTower"))
};
newRecord1Test.SetArrayField("buildings", buildings);
TSharedPtr<FJsonObject> resources = MakeShareable(new FJsonObject);
resources->SetNumberField("gas", 50);
resources->SetNumberField("water", 70);
resources->SetNumberField("gold", 30);
newRecord1Test.SetObjectField("resources", resources);
FString Key = “UserXyzAbGame”;
FRegistry::CloudSave.ReplaceUserRecord(
Key,
newRecord1Test,
FVoidHandler::CreateLambda(
[&bReplaceUserRecordSuccess]
()
{
UE_LOG(LogAccelByteCloudSaveTest, Log, TEXT("Replace user record success"));
bReplaceUserRecordSuccess = true;
}),
CloudSaveErrorHandler);
Replace User Records Concurrently

To ensure that edits to user records made concurrently aren’t overwritten, this feature will cause an error to appear if a client tries to submit changes to a user record that has already been changed at the same time by another client. There are two possible implementations of this feature: manual and automatic:

Manual

The manual implementation will return the error ErrorCode.PlayerRecordPreconditionFailed in Unity or ErrorCodes::PlayerRecordPreconditionFailedException in UE4 if the record has been changed since it was retrieved by the client. You’ll need to input the last updated time of the record, which can be found using the GetUserRecord function.

FString Key = "UserXyzAbGame";
FDateTime LatestUpdate = FDateTime::Now(); //you can get it from GetUserRecord
FJsonObject Record;
Record.SetNumberField("numRegion", 10);
Record.SetNumberField("oilsReserve", 100);
TArray<TSharedPtr<FJsonValue>> Buildings
{
MakeShareable(new FJsonValueString("oilRefinery")),
MakeShareable(new FJsonValueString("oilWell"))
};
Record.SetArrayField("buildings", Buildings);
TSharedPtr<FJsonObject> Resources = MakeShareable(new FJsonObject);
Resources->SetNumberField("gas", 20);
Resources->SetNumberField("water", 100);
Resources->SetNumberField("gold", 10);
Record.SetObjectField("resources", Resources);
FRegistry::CloudSave.ReplaceUserRecordCheckLatest(Key, LatestUpdate, Record, FVoidHandler::CreateLambda([]()
{
UE_LOG(LogTemp, Log, TEXT("Replace user record success"));
}), FErrorHandler::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Fatal, TEXT("Error code: %d\nError message:%s"), ErrorCode, *ErrorMessage);
}));

Automatic

The automatic implementation will retrieve the latest update for you. You can set it as your custom modifier, which compares records that are submitted at almost the same time. When the request is submitted again, the new custom modifier will decide which record will be submitted and which record will need to be reviewed and submitted again.

FString Key = "UserXyzAbGame";
FJsonObject Record;
Record.SetNumberField("numRegion", 10);
Record.SetNumberField("oilsReserve", 100);
TArray<TSharedPtr<FJsonValue>> Buildings
{
MakeShareable(new FJsonValueString("oilRefinery")),
MakeShareable(new FJsonValueString("oilWell"))
};
Record.SetArrayField("buildings", Buildings);
TSharedPtr<FJsonObject> Resources = MakeShareable(new FJsonObject);
Resources->SetNumberField("gas", 20);
Resources->SetNumberField("water", 100);
Resources->SetNumberField("gold", 10);
Record.SetObjectField("resources", Resources);
const int TryAttempt = 10; //How many times function should retry when always get error
TBaseDelegate<FJsonObject, FJsonObject> CustomModifier = TBaseDelegate<FJsonObject, FJsonObject>::CreateLambda([DictIndex](FJsonObject LatestRecord)
{
for(auto Item : Record.Values)
{
const TSharedPtr<FJsonObject> *Value;
if (Item.Value->TryGetObject(Value))
{
LatestRecord.SetObjectField(Item.Key, Value->ToSharedRef());
}
}
return LatestRecord; //this what will be requested to replace the old record
}
FRegistry::CloudSave.ReplaceUserRecordCheckLatest(TryAttempt, Key, Record, CustomModifier, FVoidHandler::CreateLambda([]()
{
UE_LOG(LogTemp, Log, TEXT("Replace user record %d success"), DictIndex);
}), FErrorHandler::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Fatal, TEXT("Error code: %d\nError message:%s"), ErrorCode, *ErrorMessage);
}));
Delete a User Record

Use the DeleteUserRecord() function to delete a record.

bool bDeleteUserRecordSuccess = false;
FRegistry::CloudSave.DeleteUserRecord(
Key,
FVoidHandler::CreateLambda(
[&bDeleteUserRecordSuccess]
()
{
UE_LOG(LogAccelByteCloudSaveTest, Log, TEXT("Delete user record success"));
bDeleteUserRecordSuccess = true;
}),
CloudSaveErrorHandler);

Integrating Your Game with Cloud Save at the Namespace Level

Create a New Record

To create a new record, use the SaveGameRecord() function. Please note that if the key already exists, the new record will be appended to the existing record.

FString Key = “GameAbc”;
FJsonObject record1Test;
record1Test.SetNumberField("numRegion", 6);
record1Test.SetNumberField("oilsReserve", 125.10);
record1Test.SetStringField("islandName", "tartar friendly land");
TArray<TSharedPtr<FJsonValue>> buildings{
MakeShareable(new FJsonValueString("oilRefinery")),
MakeShareable(new FJsonValueString("oilWell")),
MakeShareable(new FJsonValueString("watchTower")),
MakeShareable(new FJsonValueString("defendsTower"))
};
record1Test.SetArrayField("buildings", buildings);
TSharedPtr<FJsonObject> resources = MakeShareable(new FJsonObject);
resources->SetNumberField("gas", 20);
resources->SetNumberField("water", 100);
resources->SetNumberField("gold", 10);
record1Test.SetObjectField("resources", resources);
bool bSaveUserRecord1Success = false;
FRegistry::CloudSave.SaveGameRecord(
Key,
record1Test,
FVoidHandler::CreateLambda([&bSaveUserRecord1Success]()
{
UE_LOG(LogAccelByteCloudSaveTest, Log, TEXT("Save game record1 success"));
bSaveUserRecord1Success = true;
}),
CloudSaveErrorHandler);
Retrieve Game Data

Game data can be retrieved using the GetGameRecord() function.

FAccelByteModelsGameRecord GetGameRecordResult;
FRegistry::CloudSave.GetGameRecord(
Key,
THandler<FAccelByteModelsUserRecord>::CreateLambda(
[&GetGameRecordResult]
(FAccelByteModelsGameRecord GameRecord)
{
UE_LOG(LogAccelByteCloudSaveTest, Log, TEXT("Get game record success"));
GetGameRecordResult = GameRecord;
}),
CloudSaveErrorHandler);
Replace Game Data

You can update game data using the ReplaceGameRecord() function, even when the data does not exist. If the data does not exist, it will be created based on the data in the update.

FJsonObject newRecord1Test;
newRecord1Test.SetNumberField("numRegion", 10);
newRecord1Test.SetNumberField("oilsReserve", 100);
newRecord1Test.SetStringField("islandName", "salad friendly land");
TArray<TSharedPtr<FJsonValue>> buildings{
MakeShareable(new FJsonValueString("gasRefinery")),
MakeShareable(new FJsonValueString("gasWell")),
MakeShareable(new FJsonValueString("waterTower")),
MakeShareable(new FJsonValueString("mainTower"))
};
newRecord1Test.SetArrayField("buildings", buildings);
TSharedPtr<FJsonObject> resources = MakeShareable(new FJsonObject);
resources->SetNumberField("gas", 50);
resources->SetNumberField("water", 70);
resources->SetNumberField("gold", 30);
newRecord1Test.SetObjectField("resources", resources);
FString Key = “GameAbc”;
FRegistry::CloudSave.ReplaceGameRecord(
Key,
newRecord1Test,
FVoidHandler::CreateLambda(
[&bReplaceGameRecordSuccess]
()
{
UE_LOG(LogAccelByteCloudSaveTest, Log, TEXT("Replace game record success"));
bReplaceGameRecordSuccess = true;
}),
CloudSaveErrorHandler);
Replace Game Record Concurrently

Just like user records, you can use this function to avoid overwriting the changes made by other users in the middle of your changes. There are both manual and automatic methods to replace game records concurrently.

Manual

The manual method will be successful only if there are no other updates on record, but will return an ErrorCode.GameRecordPreconditionFailed in Unity or ErrorCodes::PlayerRecordPreconditionFailedException error for UE4 when the record has been changed since you retrieved it. You’ll need to input the timestamp of the record’s last update, which you can get from GetGameRecord.

FString Key = "UserXyzAbGame";
FDateTime LatestUpdate = FDateTime::Now(); //you can get it from GetUserRecord
FJsonObject Record;
Record.SetNumberField("numRegion", 10);
Record.SetNumberField("oilsReserve", 100);
TArray<TSharedPtr<FJsonValue>> Buildings
{
MakeShareable(new FJsonValueString("oilRefinery")),
MakeShareable(new FJsonValueString("oilWell"))
};
Record.SetArrayField("buildings", Buildings);
TSharedPtr<FJsonObject> Resources = MakeShareable(new FJsonObject);
Resources->SetNumberField("gas", 20);
Resources->SetNumberField("water", 100);
Resources->SetNumberField("gold", 10);
Record.SetObjectField("resources", Resources);
FRegistry::CloudSave.ReplaceUserRecordCheckLatest(Key, LatestUpdate, Record, FVoidHandler::CreateLambda([]()
{
UE_LOG(LogTemp, Log, TEXT("Replace user record success"));
}), FErrorHandler::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Fatal, TEXT("Error code: %d\nError message:%s"), ErrorCode, *ErrorMessage);
}));

Automatic

The automatic method will get the latest update for you, put it on your custom modifier, and then make the request again. In the custom modifier, you can compare the latest record with your local record to see what changes have been made.

FString Key = "UserXyzAbGame";
FJsonObject Record;
Record.SetNumberField("numRegion", 10);
Record.SetNumberField("oilsReserve", 100);
TArray<TSharedPtr<FJsonValue>> Buildings
{
MakeShareable(new FJsonValueString("oilRefinery")),
MakeShareable(new FJsonValueString("oilWell"))
};
Record.SetArrayField("buildings", Buildings);
TSharedPtr<FJsonObject> Resources = MakeShareable(new FJsonObject);
Resources->SetNumberField("gas", 20);
Resources->SetNumberField("water", 100);
Resources->SetNumberField("gold", 10);
Record.SetObjectField("resources", Resources);
const int TryAttempt = 10; //How many times function should retry when always get error
TBaseDelegate<FJsonObject, FJsonObject> CustomModifier = TBaseDelegate<FJsonObject, FJsonObject>::CreateLambda([DictIndex](FJsonObject LatestRecord)
{
for(auto Item : Record.Values)
{
const TSharedPtr<FJsonObject> *Value;
if (Item.Value->TryGetObject(Value))
{
LatestRecord.SetObjectField(Item.Key, Value->ToSharedRef());
}
}
return LatestRecord; //this what will be requested to replace the old record
}
FRegistry::CloudSave.ReplaceUserRecordCheckLatest(TryAttempt, Key, Record, CustomModifier, FVoidHandler::CreateLambda([]()
{
UE_LOG(LogTemp, Log, TEXT("Replace user record %d success"), DictIndex);
}), FErrorHandler::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Fatal, TEXT("Error code: %d\nError message:%s"), ErrorCode, *ErrorMessage);
}));
Delete Game Data

Use the DeleteGameRecord() function to delete game data.

bool bDeleteGameRecordSuccess = false;
FRegistry::CloudSave.DeleteGameRecord(
Key,
FVoidHandler::CreateLambda(
[&bDeleteGameRecordSuccess ]
()
{
UE_LOG(LogAccelByteCloudSaveTest, Log, TEXT("Delete game record success"));
bDeleteGameRecordSuccess = true;
}),
CloudSaveErrorHandler);

Integrating Your Game Server with Cloud Save

This API grants your game server the ability to modify users’ records. After that, you have to set the Cloud Save URL in the Assets/Resources/AccelByteSDKConfig.json file if you’re using Unity SDK.

//Not Implemented Yet
Create a User Record

Create a new record for the targeted user. If the record already exists, this action will merge the records with the following conditions:

  • If the field name already exists, the value will be replaced.
  • If the field name does not exist, it will append the field and its value.
// Not Implemented Yet
Retrieve a User Record

Retrieve the specified user’s record.

// Not Implemented Yet
Replace a User Record

This function can overwrite an existing user’s record if it exists. If the record does not exist, it will create a new record.

// Not Implemented Yet
Delete a User Record

This function can erase the specified user’s record.

// Not Implemented Yet

Managing Cloud Save Using the API

You can also use our API endpoints to manage and integrate with Cloud Save.

Create a New Game Record

You can create a game record using API. To do so, follow steps below:

  1. Use the Save Namespace Level Record: POST - /cloudsave/v1/admin/namespaces/{namespace}/records/{key} endpoint.
  2. Fill out the Namespace with Game Namespace.
  3. Fill out the Key of the record.
  4. Fill out the Request Body with the record information.

Upon successful request, the new game record will be created.

Update a Game Record

You can update or replace a game record using API. To do so, follow steps below:

  1. Use the Save or Replace Game Record: PUT - /cloudsave/v1/admin/namespaces/{namespace}/records/{key} endpoint.
  2. Fill out the Namespace with Game Namespace.
  3. Fill out the Key of the record.
  4. Fill out the Request Body with the record information.

Upon successful request, the game record will be updated or replaced.

Delete a Game Record

You can delete a game record using API. To do so, follow steps below:

  1. Use the Purge all records under the given key: DELETE - /cloudsave/v1/admin/namespaces/{namespace}/records/{key} endpoint.
  2. Fill out the Namespace with Game Namespace.
  3. Fill out the Key of the record.

Upon successful request, the game record will be deleted.

Retrieve a List of Records in a Namespace

You can retrieve records in a specific game namespace using API. To do so, follow steps below:

  1. Use the Retrieve list of records key by namespace: GET /cloudsave/v1/admin/namespaces/{namespace}/records endpoint.
  2. Fill out the Namespace with Game Namespace.
  3. Fill out the Offset and Limit if you want to paginate the results. If you want to retrieve the complete list, you can leave these fields blank.

Upon successful request, records from a specific game namespace will be retrieved.

Retrieve a Game Record

You can retrieve a game record using API. To do so, follow steps below:

  1. Use the Retrieve a record value by its key: GET /cloudsave/v1/admin/namespaces/{namespace}/records/{key} endpoint.
  2. Fill out the Namespace with Game Namespace.
  3. Fill out the Key of the record.

Upon successful request, the game record will be retrieved.

Retrieve a Player Record

You can retrieve a player record using API. To do so, follow steps below:

  1. Use the Retrieve a record value by its key: GET /cloudsave/v1/admin/namespaces/{namespace}/users/{userID}/records/{key} endpoint.
  2. Fill out the Namespace with Game Namespace.
  3. Fill out the Key of the record.

Upon successful request, the player record will be retrieved.