Plugin Documentation

DynamoLedger
Amazon DynamoDB for Blueprints and C++

Player data, inventories, stats and leaderboards in Amazon DynamoDB, with plain JSON in and out and Amazon Cognito credentials, built on the official AWS SDK for C++.

v1.0
UE 4.27 & 5.8
Win64, Linux & Android
Category Networking
Alpha XP

Overview

DynamoLedger stores your game's player data in Amazon DynamoDB straight from Unreal Engine 4.27 and 5.8, with no web layer in between. Items go in and come out as plain JSON strings, so Blueprint structs map to items with Struct To JSON and JSON To Struct. Requests run on background worker threads with automatic retries; every result arrives on the game thread.

  • Put Item, Get Item, Update Item and Delete Item with condition expressions and a dedicated On Condition Failed pin.
  • Query and Scan with paging, filters, secondary indexes and parallel scans.
  • Batch Get and Batch Write for many items at once, with automatic retries of unprocessed entries.
  • Transact Write: all-or-nothing purchases and trades, with readable cancellation reasons.
  • Increment Counter for atomic currencies and stats, Get Top Scores for leaderboards.
  • {IdentityId} in any JSON value is replaced with the player's Cognito identity id, so each player can be limited to their own items with dynamodb:LeadingKeys.

Requirements

RequirementDetails
Unreal Engine4.27 and 5.8
PlatformsWin64, Linux and Android
ModulesDynamoLedger (Runtime), DynamoLedgerSdk (prebuilt AWS SDK for C++ 1.11.916 in Source/ThirdParty)
DependenciesNo other plugins. On Linux and Android the SDK uses the engine's libcurl, OpenSSL and certificate store.
AWSAn AWS account. DynamoDB and Cognito usage is billed by AWS (both have a free tier).

Installation

  1. Install DynamoLedger from Fab into your engine, or copy the DynamoLedger folder into your project's Plugins folder.
  2. Enable DynamoLedger in Edit > Plugins and restart the editor.
  3. Open Project Settings > Plugins > DynamoLedger and set Region, Identity Pool Id and, optionally, Table Prefix.
  4. C++ only: add PublicDependencyModuleNames.Add("DynamoLedger"); to your Build.cs.
Several AWS plugins in one projectEach Alpha XP cloud plugin carries its own copy of the SDK under a plugin-specific name, so they install side by side without conflicts.

Setting up AWS

1. Tables

Create the tables in the DynamoDB console (on-demand capacity is the simplest start). The example below keeps every player's data under their identity id and adds a leaderboard index:

Table mygame-prod-players   (on-demand capacity)
  pk  String  partition key   = the player's identity id ({IdentityId})
  sk  String  sort key        = "profile", "inventory", "item#sword-01", ...

Table mygame-prod-scores    (on-demand capacity)
  pk     String  partition key = {IdentityId}
  sk     String  sort key      = the board, for example "global" or "level-3"
  board  String                = the board again (index partition key)
  score  Number                = the player's best score (index sort key)
  name   String                = display name shown on the board

Global secondary index board-score-index on mygame-prod-scores
  partition key board (String), sort key score (Number), projection ALL

2. Identity pool

In Amazon Cognito create an identity pool. Enable guest access if players may save before signing in, and add your identity providers (for example a Cognito user pool).

3. Role policy

Attach this policy to the role the pool gives to players (authenticated and/or guest). The dynamodb:LeadingKeys condition only allows items whose partition key is the caller's own identity id, which is exactly what {IdentityId} produces. The second statement lets everyone read the leaderboard index.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "OwnItemsOnly",
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem", "dynamodb:BatchGetItem", "dynamodb:Query",
        "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem",
        "dynamodb:BatchWriteItem", "dynamodb:ConditionCheckItem"
      ],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:123456789012:table/mygame-prod-players",
        "arn:aws:dynamodb:us-east-1:123456789012:table/mygame-prod-scores"
      ],
      "Condition": {
        "ForAllValues:StringEquals": {
          "dynamodb:LeadingKeys": ["${cognito-identity.amazonaws.com:sub}"]
        }
      }
    },
    {
      "Sid": "ReadLeaderboards",
      "Effect": "Allow",
      "Action": "dynamodb:Query",
      "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/mygame-prod-scores/index/board-score-index"
    }
  ]
}
Scan and other players' itemsWith this policy players can only read and write their own partition, so Scan (and reading another player's profile) is denied. Use Scan from dedicated servers or tools with their own role, or add a separate statement for public tables.
Never ship access keysStatic keys in the settings end up in DefaultGame.ini and in packaged games. Use them only for development and dedicated servers; players should always use the identity pool. Shipping builds refuse settings keys unless Allow Static Keys In Shipping is enabled.

Quick start

With Table Prefix mygame-prod-, the nodes below use the tables from Setting up AWS:

Resolve Identity
  -> Get Item     Table "players", Key JSON {"pk": "{IdentityId}", "sk": "profile"}
       On Not Found -> Put Item  Item JSON {"pk": "{IdentityId}", "sk": "profile", "gold": 100, "level": 1}
       On Success   -> JSON To Struct (Item JSON -> FPlayerProfile)

Update Item  Key JSON {"pk": "{IdentityId}", "sk": "profile"}
             Update Expression  SET gold = gold - :cost
             Values JSON        {":cost": 50}
             Condition          gold >= :cost       -> On Condition Failed = not enough gold

Increment Counter  Key JSON {"pk": "{IdentityId}", "sk": "profile"}, Attribute "wins", Delta 1
Reserved wordsDynamoDB reserves many common names (name, level, count, status, ...). Write #n in the expression and pass Names JSON {"#n": "name"}.
Guest identitiesA guest identity lasts for the current session. For data that must survive a restart or move between devices, sign the player in with Set Login Token (Cognito user pool, Google, Apple, ...): signed-in players always get the same identity id.

JSON and types

Every item, key and value is a JSON object string. Numbers keep their exact text (DynamoDB stores up to 38 digits); integers stay integers. Strings containing {IdentityId} are replaced with the player's identity id in items, keys, expression values and start keys.

JSON you sendDynamoDB typeJSON you get back
"Ada"S (string)"Ada"
42, -3.25N (number, exact text; 1.0 is stored as 1)42, -3.25
true / falseBOOLtrue / false
nullNULLnull
[1, "a"]L (list)[1, "a"]
{"hp": 10}M (map){"hp": 10}
{"$SS": ["sword", "bow"]}SS (string set)["bow", "sword"]
{"$NS": [1, 2]}NS (number set)[1, 2]
{"$B": "AQID"}B (binary, base64)"AQID"
{"$BS": ["AQID"]}BS (binary set)["AQID"]

Expression values are a JSON object of placeholders, for example {":cost": 50, ":tag": "rare"}, converted the same way. Expression names are {"#n": "name"}.

Latent nodes

All latent nodes run in the background, can be stopped with Cancel on their return value and fire exactly one final pin on the game thread. Table names get the settings' Table Prefix.

NodeInputsOutputsDescription
Put ItemTable, Item JSON, Condition Expression; advanced: Return Old, Values JSON, Names JSONOn Success / On Condition Failed / On Failure (Old Item JSON, Error)Writes or replaces a whole item. With a condition such as attribute_not_exists(pk) the write only happens when it holds; otherwise On Condition Failed fires with the item that blocked it.
Get ItemTable, Key JSON; advanced: Projection Expression, Consistent Read, Names JSONOn Success / On Not Found / On Failure (Item JSON, Error)Reads one item by its full key. On Not Found fires when no item has that key.
Update ItemTable, Key JSON, Update Expression, Values JSON; advanced: Names JSON, Condition ExpressionOn Success (Updated JSON), On Condition Failed (Item JSON), On FailureChanges attributes in place (SET, REMOVE, ADD, DELETE) and returns the whole item after the update (ALL_NEW). Missing items are created.
Delete ItemTable, Key JSON; advanced: Condition Expression, Values JSON, Names JSONOn Success / On Condition Failed / On Failure (Old Item JSON, Error)Deletes one item and returns it. On Condition Failed carries the current item.
QueryTable, Key Condition, Values JSON; advanced: Names JSON, Index Name, Filter Expression, Limit, Scan Forward, Start Key JSONOn Success / On Failure (Items JSON, Next Start Key JSON, Count, Error)Reads the items of one partition in sort key order, one page (up to 1 MB) at a time. Pass Next Start Key JSON back as Start Key JSON for the next page; it is empty on the last page.
ScanTable, Filter Expression, Values JSON; advanced: Names JSON, Limit, Start Key JSON, Segment, Total SegmentsOn Success / On Failure (Items JSON, Next Start Key JSON, Count, Error)Reads a whole table page by page. Segment / Total Segments run a parallel scan. Meant for servers, tools and small public tables.
Batch GetTable, Keys JSON (array)On Success / On Failure (Items JSON, Error)Reads many items by key, 100 per request. Unprocessed keys are retried with exponential back-off. Items come back in the order of the keys; keys without an item are skipped, duplicates are read once.
Batch WriteTable, Put Items (array), Delete Keys (array)On Success / On Failure (Written Count, Error)Puts and deletes many items, 25 per request, retrying unprocessed entries with back-off. Not atomic: use Transact Write when everything must succeed together.
Transact WriteOperations JSON; advanced: Client TokenOn Success / On Failure (Error)Up to 100 put, update, delete and check operations that succeed or fail together. A cancelled transaction lists the reason of every failed operation in Error > Message.
Increment CounterTable, Key JSON, Attribute, DeltaOn Success / On Failure (New Value, Error)Adds Delta (negative to subtract) atomically and returns the new value. Missing items and attributes start at 0. Nested paths such as stats.kills work when the parent map exists.
Get Top ScoresTable, Index Name, Partition Key Name, Partition Value, Count, DescendingOn Success / On Failure (Items JSON, Error)Leaderboard helper over Query: the best Count items of one board, sorted by the index sort key (the score).
Resolve IdentityForce RefreshOn Success / On Failure (Identity Id, Error)Fetches the Cognito identity and temporary credentials now, for example right after login, and returns the identity id that {IdentityId} stands for.

Functions

NodeKindDescription
Set RegionCallableSwitches the AWS region for every following request.
Set Credential ModeCallableOverrides the credential mode from the settings at runtime.
Set Login TokenCallableSigns the player in to the identity pool with a provider token (Cognito user pool, Google, Apple, Facebook, OpenID).
Clear Login TokensCallableForgets all login tokens and the cached identity; the player becomes a guest.
Set Static CredentialsCallableUses the given keys from now on (Static Keys mode), for example temporary keys handed out by your backend.
Get Identity IdPureThe player's Cognito identity id (the value of {IdentityId}); empty until credentials were resolved once.
Make User Pool Provider NamePureBuilds the login key of a Cognito user pool: cognito-idp.<region>.amazonaws.com/<pool id>.
Is ConfiguredPureReturns false and a reason when the settings are incomplete.
Make Key JSONPureBuilds a key such as {"pk":"{IdentityId}","sk":"inventory"}. Leave Sort Name empty for tables without a sort key; Sort Value Is Number (advanced) writes a numeric sort key.
Struct To JSONCallableConverts any struct to a JSON object string - ready to use as Item JSON.
JSON To StructCallableFills a struct from Item JSON; missing fields keep their defaults.
Get JSON FieldPureReads one value with a path such as inventory[2].count.
CancelCallableOn a latent node's return value: none of its pins fire any more.
Is FinishedPureOn a latent node's return value: true after it finished or was cancelled.

DynamoLedger has 26 Blueprint nodes: 12 latent nodes and 14 functions.

Transactions

Transact Write takes an array of operations. Each entry has one key — put, update, delete or check — with table, item or key, expression (update only), condition, values and names. Buying a sword only if the player has enough gold and does not own it yet:

[
  {"update": {"table": "players",
              "key": {"pk": "{IdentityId}", "sk": "profile"},
              "expression": "SET gold = gold - :price",
              "condition": "gold >= :price",
              "values": {":price": 250}}},
  {"put":    {"table": "players",
              "item": {"pk": "{IdentityId}", "sk": "item#sword-01", "power": 12},
              "condition": "attribute_not_exists(sk)"}}
]

If any condition fails nothing is written and On Failure fires with Error > Code TransactionCanceledException and a message such as #0 update mygame-prod-players: ConditionalCheckFailed - The conditional request failed. Pass a Client Token (for example a purchase id) to make retries safe: the same token within 10 minutes is applied once.

Leaderboards

Each player writes their own score item (allowed by the LeadingKeys policy); the board-score-index sorts all of them by score. The condition only replaces a lower score:

Put Item    Table "scores"
            Item JSON {"pk": "{IdentityId}", "sk": "global", "board": "global", "score": 1200, "name": "Ada"}
            Condition attribute_not_exists(score) OR score < :new
            Values JSON {":new": 1200}

Get Top Scores  Table "scores", Index Name "board-score-index",
                Partition Key Name "board", Partition Value "global", Count 10, Descending

Use Descending off for boards where lower is better (race times). For competitive boards, write scores from a dedicated server so clients cannot post arbitrary values.

Paging

Query and Scan return one page (up to Limit items, at most 1 MB). When Next Start Key JSON is not empty, call the node again with it as Start Key JSON to get the next page. Count is the number of items in this page after the filter.

Credentials

ModeUse forHow
Cognito Identity PoolGame clientsGuests get unauthenticated credentials; after Set Login Token the player gets the authenticated role. Credentials refresh automatically before they expire.
Static KeysDevelopment, dedicated servers, keys from your backendSettings keys or Set Static Credentials (with an optional session token). {IdentityId} is not available.
Default Credential ChainServers and toolsEnvironment variables, the ~/.aws profile, ECS and EC2 instance roles. {IdentityId} is not available.

Signed-in players: for a Cognito user pool call Set Login Token with Provider = Make User Pool Provider Name and Token = the ID token. Other providers use their pool login keys (accounts.google.com, appleid.apple.com, graph.facebook.com).

C++ API

Every latent node is a class with a static factory function. Create it, bind its delegates and call Activate():

// Build.cs: PublicDependencyModuleNames.Add("DynamoLedger");
#include "DynamoLedgerAsyncActions.h"

UDynamoLedgerGetItemAction* Node = UDynamoLedgerGetItemAction::GetItem(this, TEXT("players"),
    TEXT("{\"pk\": \"{IdentityId}\", \"sk\": \"profile\"}"), FString(), false, FString());
Node->OnSuccess.AddDynamic(this, &AMyActor::OnProfile);     // (const FString& ItemJson, const FDynamoLedgerError& Error)
Node->OnNotFound.AddDynamic(this, &AMyActor::OnNoProfile);
Node->OnFailure.AddDynamic(this, &AMyActor::OnProfileError);
Node->Activate();                                          // runs on a worker, pins fire on the game thread

Types

TypeKindMembers
FDynamoLedgerErrorStructCode, Message, Http Status, Retryable
EDynamoLedgerCredentialModeEnumCognito Identity Pool, Static Keys, Default Credential Chain
UDynamoLedgerSettingsClassProject settings (see below)

Settings

Project Settings > Plugins > DynamoLedger (saved to DefaultGame.ini). Changes apply to the next request.

SettingDefaultDescription
Regionus-east-1Region of your tables and identity pool.
Credential ModeCognito Identity PoolCognito Identity Pool, Static Keys or Default Credential Chain.
Identity Pool Id(empty)For example us-east-1:01234567-89ab-cdef-0123-456789abcdef.
Allow Guest AccessOnUnauthenticated identities before Set Login Token. The pool must allow guest access too.
Access Key Id / Secret Access Key(empty)Static Keys mode. Saved in DefaultGame.ini - development and servers only.
Allow Static Keys In ShippingOffStatic keys from the settings are refused in Shipping builds unless enabled (dedicated servers).
Endpoint Override(empty)Custom endpoint, for example DynamoDB Local (http://localhost:8000).
Request Timeout Ms / Connect Timeout Ms30000 / 5000Time limits per request.
Max Retries3Automatic retries with exponential back-off for throttling and transient errors.
Worker Threads4Background threads that run requests.
Verify SSLOnOnly disable for a local emulator.
Log Sdk WarningsOnForwards SDK warnings and errors to LogDynamoLedger.
Table Prefix(empty)Prepended to plain table names, for example mygame-prod- so nodes can say players. Names that already start with the prefix, and table ARNs, are used unchanged.
Consistent Reads By DefaultOffStrongly consistent reads for Get Item, Batch Get and for Query / Scan on the table itself (global secondary indexes are always eventually consistent). Costs twice the read capacity.

Troubleshooting

Error codeCause and fix
NotConfiguredSet the Identity Pool Id in Project Settings > Plugins > DynamoLedger, or switch the credential mode.
NoIdentityA JSON input uses {IdentityId} but the credential mode has no Cognito identity (Static Keys / Default Chain). Write the real key value instead.
NotAuthorizedExceptionGuest access is off on the identity pool, or a login token expired. Enable guest access or call Set Login Token with a fresh token.
AccessDeniedExceptionThe identity pool role does not allow the action on that table, or the partition key is not exactly the player's identity id (dynamodb:LeadingKeys). Check the role policy and use {IdentityId} as the partition key value.
ResourceNotFoundExceptionWrong table name, prefix or region. Check Table Prefix and Region.
ValidationExceptionThe expression or key is invalid: a key attribute is missing or has the wrong type, a value placeholder is unused or missing, or a reserved word (name, level, count, ...) is used without a #placeholder in Names JSON.
ConditionalCheckFailedExceptionThe condition did not hold. Put / Update / Delete Item fire On Condition Failed with the current item instead of On Failure.
TransactionCanceledExceptionAt least one operation of Transact Write failed; Error > Message names each one, for example #1 update mygame-prod-players: ConditionalCheckFailed. Nothing was written.
ProvisionedThroughputExceededException / UnprocessedThe table is throttled. Requests are retried automatically; switch the table to on-demand capacity or raise its capacity.
InvalidJson / MissingInputAn input is not valid JSON, is not an object where one is required, or is empty. The message names the input.
UnavailableThe SDK library could not be loaded or the engine is shutting down. Check the Output Log for LogDynamoLedger errors.

Credits

© 2026 Alpha XP — alphaxp.net · Support: support@alphaxp.net. Includes the AWS SDK for C++ and the AWS Common Runtime (Apache License 2.0). Made for Unreal Engine 4.27 and 5.8.