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 withdynamodb:LeadingKeys.
Requirements
| Requirement | Details |
|---|---|
| Unreal Engine | 4.27 and 5.8 |
| Platforms | Win64, Linux and Android |
| Modules | DynamoLedger (Runtime), DynamoLedgerSdk (prebuilt AWS SDK for C++ 1.11.916 in Source/ThirdParty) |
| Dependencies | No other plugins. On Linux and Android the SDK uses the engine's libcurl, OpenSSL and certificate store. |
| AWS | An AWS account. DynamoDB and Cognito usage is billed by AWS (both have a free tier). |
Installation
- Install DynamoLedger from Fab into your engine, or copy the
DynamoLedgerfolder into your project'sPluginsfolder. - Enable DynamoLedger in Edit > Plugins and restart the editor.
- Open Project Settings > Plugins > DynamoLedger and set Region, Identity Pool Id and, optionally, Table Prefix.
- C++ only: add
PublicDependencyModuleNames.Add("DynamoLedger");to yourBuild.cs.
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"
}
]
}
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
name, level, count, status, ...). Write #n in the expression and pass Names JSON {"#n": "name"}.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 send | DynamoDB type | JSON you get back |
|---|---|---|
"Ada" | S (string) | "Ada" |
42, -3.25 | N (number, exact text; 1.0 is stored as 1) | 42, -3.25 |
true / false | BOOL | true / false |
null | NULL | null |
[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.
| Node | Inputs | Outputs | Description |
|---|---|---|---|
| Put Item | Table, Item JSON, Condition Expression; advanced: Return Old, Values JSON, Names JSON | On 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 Item | Table, Key JSON; advanced: Projection Expression, Consistent Read, Names JSON | On 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 Item | Table, Key JSON, Update Expression, Values JSON; advanced: Names JSON, Condition Expression | On Success (Updated JSON), On Condition Failed (Item JSON), On Failure | Changes attributes in place (SET, REMOVE, ADD, DELETE) and returns the whole item after the update (ALL_NEW). Missing items are created. |
| Delete Item | Table, Key JSON; advanced: Condition Expression, Values JSON, Names JSON | On Success / On Condition Failed / On Failure (Old Item JSON, Error) | Deletes one item and returns it. On Condition Failed carries the current item. |
| Query | Table, Key Condition, Values JSON; advanced: Names JSON, Index Name, Filter Expression, Limit, Scan Forward, Start Key JSON | On 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. |
| Scan | Table, Filter Expression, Values JSON; advanced: Names JSON, Limit, Start Key JSON, Segment, Total Segments | On 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 Get | Table, 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 Write | Table, 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 Write | Operations JSON; advanced: Client Token | On 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 Counter | Table, Key JSON, Attribute, Delta | On 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 Scores | Table, Index Name, Partition Key Name, Partition Value, Count, Descending | On 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 Identity | Force Refresh | On 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
| Node | Kind | Description |
|---|---|---|
| Set Region | Callable | Switches the AWS region for every following request. |
| Set Credential Mode | Callable | Overrides the credential mode from the settings at runtime. |
| Set Login Token | Callable | Signs the player in to the identity pool with a provider token (Cognito user pool, Google, Apple, Facebook, OpenID). |
| Clear Login Tokens | Callable | Forgets all login tokens and the cached identity; the player becomes a guest. |
| Set Static Credentials | Callable | Uses the given keys from now on (Static Keys mode), for example temporary keys handed out by your backend. |
| Get Identity Id | Pure | The player's Cognito identity id (the value of {IdentityId}); empty until credentials were resolved once. |
| Make User Pool Provider Name | Pure | Builds the login key of a Cognito user pool: cognito-idp.<region>.amazonaws.com/<pool id>. |
| Is Configured | Pure | Returns false and a reason when the settings are incomplete. |
| Make Key JSON | Pure | Builds 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 JSON | Callable | Converts any struct to a JSON object string - ready to use as Item JSON. |
| JSON To Struct | Callable | Fills a struct from Item JSON; missing fields keep their defaults. |
| Get JSON Field | Pure | Reads one value with a path such as inventory[2].count. |
| Cancel | Callable | On a latent node's return value: none of its pins fire any more. |
| Is Finished | Pure | On 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
| Mode | Use for | How |
|---|---|---|
| Cognito Identity Pool | Game clients | Guests get unauthenticated credentials; after Set Login Token the player gets the authenticated role. Credentials refresh automatically before they expire. |
| Static Keys | Development, dedicated servers, keys from your backend | Settings keys or Set Static Credentials (with an optional session token). {IdentityId} is not available. |
| Default Credential Chain | Servers and tools | Environment 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
| Type | Kind | Members |
|---|---|---|
| FDynamoLedgerError | Struct | Code, Message, Http Status, Retryable |
| EDynamoLedgerCredentialMode | Enum | Cognito Identity Pool, Static Keys, Default Credential Chain |
| UDynamoLedgerSettings | Class | Project settings (see below) |
Settings
Project Settings > Plugins > DynamoLedger (saved to DefaultGame.ini). Changes apply to the next request.
| Setting | Default | Description |
|---|---|---|
| Region | us-east-1 | Region of your tables and identity pool. |
| Credential Mode | Cognito Identity Pool | Cognito Identity Pool, Static Keys or Default Credential Chain. |
| Identity Pool Id | (empty) | For example us-east-1:01234567-89ab-cdef-0123-456789abcdef. |
| Allow Guest Access | On | Unauthenticated 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 Shipping | Off | Static 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 Ms | 30000 / 5000 | Time limits per request. |
| Max Retries | 3 | Automatic retries with exponential back-off for throttling and transient errors. |
| Worker Threads | 4 | Background threads that run requests. |
| Verify SSL | On | Only disable for a local emulator. |
| Log Sdk Warnings | On | Forwards 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 Default | Off | Strongly 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 code | Cause and fix |
|---|---|
NotConfigured | Set the Identity Pool Id in Project Settings > Plugins > DynamoLedger, or switch the credential mode. |
NoIdentity | A JSON input uses {IdentityId} but the credential mode has no Cognito identity (Static Keys / Default Chain). Write the real key value instead. |
NotAuthorizedException | Guest access is off on the identity pool, or a login token expired. Enable guest access or call Set Login Token with a fresh token. |
AccessDeniedException | The 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. |
ResourceNotFoundException | Wrong table name, prefix or region. Check Table Prefix and Region. |
ValidationException | The 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. |
ConditionalCheckFailedException | The condition did not hold. Put / Update / Delete Item fire On Condition Failed with the current item instead of On Failure. |
TransactionCanceledException | At least one operation of Transact Write failed; Error > Message names each one, for example #1 update mygame-prod-players: ConditionalCheckFailed. Nothing was written. |
ProvisionedThroughputExceededException / Unprocessed | The table is throttled. Requests are retried automatically; switch the table to on-demand capacity or raise its capacity. |
InvalidJson / MissingInput | An input is not valid JSON, is not an object where one is required, or is empty. The message names the input. |
Unavailable | The 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.