# Collections

**URL:** https://heroiclabs.com/docs/nakama/concepts/storage/collections/
**Summary:** Nakama incorporates a storage engine for project-specific data, such as user accounts. The storage engine design is optimized for object ownership, access permissions, and batch operations. Data is stored in collections with one or more objects which contain a unique key with JSON content.
**Keywords:** create collections, write objects, conditional write, read objects, list objects, remove objects
**Categories:** nakama, collections, storage

---


# Collections

Every app or game has data which is specific to the project.

This information must be stored for each user, as well as updated, retrieved, and displayed within various parts of a UI. For this purpose the server incorporates a storage engine with a design optimized for [object ownership](../permissions/#object-ownership), [access permissions](../permissions/#object-permissions), and batch operations.

Data is stored in collections with one or more objects which contain a unique key with JSON content. A collection is created without any configuration required. This creates a simple nested namespace which represents the location of a object.

This design gives great flexibility for developers to group sets of information which belong together within a game or app.

```bash {linenos=false}
Collection
+
---------------------------------------------------------------------+
|  Object                                                             |
|  +----------+------------+-------------+-----+-------------------+  |
|  | ?UserId? | Identifier | Permissions | ... |       Value       |  |
|  +---------------------------------------------------------------+  |
+---------------------------------------------------------------------+
```

The `Collection` and `Key` are used to identify the object itself. The `UserId` is used to identify the owner of an object, and to check for the needed read/write permissions when calling those operations on a object from the client.

{{< note "important" >}}
Writing custom SQL is discouraged in favor of using the built-in features of the Storage Engine. If custom SQL is needed for your use case, please [contact Heroic Labs](mailto:support@heroiclabs.com) before proceeding.
{{< / note >}}

{{< note "error" >}}
The creation of custom tables is strongly discouraged.
{{< / note >}}

## Write objects

A user can write one or more objects which will be stored in the database server. These objects will be written in a single transaction which guarantees the writes succeed together.

{{< code type="client" >}}
```bash
curl -X PUT "http://127.0.0.1:7350/v2/storage" \
  -H 'Authorization: Bearer <session token>' \
  -d '{"objects":
    [
      {
        "collection": "saves",
        "key": "savegame",
        "value": "{\"progress\": \"50\"}"
      },
      {
        "collection": "stats",
        "key": "skill",
        "value": "{\"progress\": \"24\"}"
      }
    ]
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var save_game = { "progress": 50 };
var my_stats = { "skill": 24 };

const object_ids = await client.writeStorageObjects(session, [
  {
    "collection": "saves",
    "key": "savegame",
    "value": save_game
  }, {
    "collection": "stats",
    "key": "skills",
    "value": my_stats
  }
]);

console.info("Successfully stored objects: ", object_ids);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var saveGame = "{ \"progress\": 50 }";
var myStats = "{ \"skill\": 24 }";

var writeObjects = new [] {
    new WriteStorageObject
    {
        Collection = "saves",
        Key = "savegame",
        Value = saveGame
    },
    new WriteStorageObject
    {
        Collection = "stats",
        Key = "skills",
        Value = myStats
    }
};

await client.WriteStorageObjectsAsync(session, writeObjects);
Console.WriteLine("Successfully stored objects: [{0}]", string.Join(",\n  ", objectIds));
```
{{< / code >}}

{{< code type="client" >}}
```swift
let saveGame = "{ \"progress\": 50 }"
let myStats = "{ \"skill\": 24 }"

let writeObjects = [
    WriteStorageObject(collection: "saves", key: "savegame", value: saveGame),
    WriteStorageObject(collection: "stats", key: "skills", value: myStats)
]

let acks = try await client.writeStorageObjects(session: session, objects: writeObjects)
debugPrint("Successfully stored objects:", acks)
```
{{< / code >}}

{{< code type="client" >}}
```dart
const saveGame = '{"progress": 50}';
const myStats = '{"skill": 24}';

var writeObjects = [
  WriteStorageObject(collection: 'saves', key: 'savegame', value: saveGame),
  WriteStorageObject(collection: 'stats', key: 'skills', value: myStats),
];

for (final wo in writeObjects) {
  await client.writeStorageObject(
    session: session,
    collection: wo.collection,
    key: wo.key,
    value: wo.value,
  );
}
print('Successfully stored objects: $writeObjects');
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NStorageObjectAcks& acks)
{
    std::cout << "Successfully stored objects " << acks.size() << std::endl;
};

std::vector<NStorageObjectWrite> objects;
NStorageObjectWrite savesObject, statsObject;
savesObject.collection = "saves";
savesObject.key = "savegame";
savesObject.value = "{ \"progress\": 50 }";
objects.push_back(savesObject);

statsObject.collection = "stats";
statsObject.key = "skills";
statsObject.value = "{ \"skill\": 24 }";
objects.push_back(statsObject);
client->writeStorageObjects(session, objects, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String saveGame = "{ \"progress\": 50 }";
String myStats = "{ \"skill\": 24 }";
StorageObjectWrite saveGameObject = new StorageObjectWrite("saves", "savegame", saveGame, PermissionRead.OWNER_READ, PermissionWrite.OWNER_WRITE);
StorageObjectWrite statsObject = new StorageObjectWrite("stats", "skills", myStats, PermissionRead.OWNER_READ, PermissionWrite.OWNER_WRITE);
StorageObjectAcks acks = client.writeStorageObjects(session, saveGameObject, statsObject).get();
System.out.format("Stored objects %s", acks.getAcksList());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var save_game = "{ \"progress\": 50 }"
var my_stats = "{ \"skill\": 24 }"
var can_read = 1
var can_write = 1
var version = ""

var acks : NakamaAPI.ApiStorageObjectAcks = yield(client.write_storage_objects_async(session, [
    NakamaWriteStorageObject.new("saves", "savegame", can_read, can_write, save_game, version),
    NakamaWriteStorageObject.new("stats", "skills", can_read, can_write, my_stats, version)
]), "completed")

if acks.is_exception():
    print("An error occurred: %s" % acks)
    return

print("Successfully stored objects:")

for a in acks.acks:
    print("%s" % a)
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var save_game = "{ \"progress\": 50 }"
var my_stats = "{ \"skill\": 24 }"
var can_read = 1
var can_write = 1
var version = ""

var acks : NakamaAPI.ApiStorageObjectAcks = await client.write_storage_objects_async(session, [
    NakamaWriteStorageObject.new("saves", "savegame", can_read, can_write, save_game, version),
    NakamaWriteStorageObject.new("stats", "skills", can_read, can_write, my_stats, version)
])

if acks.is_exception():
    print("An error occurred: %s" % acks)
    return

print("Successfully stored objects:")

for a in acks.acks:
    print("%s" % a)
```
{{< / code >}}

{{< code type="client" >}}
```shell
PUT /v2/storage
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "objects": [
    {
      "collection": "saves",
      "key": "key",
      "value": "{\"hello\": \"world\"}"
    },
    {
      "collection": "stats",
      "key": "skill",
      "value": "{\"progress\": \"24\"}"
    }
  ]
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local save_game = json.encode({ progress = 50 })
local my_stats = json.encode({ skill = 24 })
local can_read = 1
local can_write = 1
local version = ""

local objects = {
  {
    collection = "saves",
    key = "savegame",
    permissionRead = can_read,
    permissionWrite = can_write,
    value = save_game,
    version = version,
  },
  {
    collection = "stats",
    key = "skills",
    permissionRead = can_read,
    permissionWrite = can_write,
    value = my_stats,
    version = version,
  }
}

local result = client.write_storage_objects(objects)

if result.error then
  print(result.message)
  return
end

for _,ack in ipairs(result.acks) do
  pprint(ack)
end
```
{{< / code >}}

### Conditional writes

When objects are successfully stored a version is returned which can be used with further updates to perform concurrent modification checks with the next write. This is known as a conditional write.

A conditional write ensures a client can only update the object if they've seen the previous version of the object. The purpose is to prevent a change to the object if another client has changed the value between the first client's read and its next write.

{{< code type="client" >}}
```bash
curl -X PUT "http://127.0.0.1:7350/v2/storage" \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "objects": [
      {
        "collection": "saves",
        "key": "savegame",
        "value": "{\"progress\": \"50\"}",
        "version": "some-previous-version"
      }
    ]
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var save_game = { "progress": 50 };

const object_ids = await client.writeStorageObjects(session, [
  {
    "collection": "saves",
    "key": "savegame",
    "value": save_game,
    "version": "<version>"
  }
]);

console.info("Stored objects: %o", object_ids);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var saveGame = "{ \"progress\": 50 }";

var objectIds = await client.WriteStorageObjectsAsync(session, new [] { 
  new WriteStorageObject
  {
      Collection = "saves",
      Key = "savegame",
      Value = saveGame,
      Version = "<version>"
  }
});

Console.WriteLine("Stored objects: [{0}]", string.Join(",\n  ", objectIds));
```
{{< / code >}}

{{< code type="client" >}}
```swift
let saveGame = "{ \"progress\": 50 }"

let acks = try await client.writeStorageObjects(session: session, WriteStorageObject(
    collection: "saves",
    key: "savegame",
    value: saveGame,
    version: "<version>"
))

debugPrint("Stored objects:", objectIds)
```
{{< / code >}}

{{< code type="client" >}}
```dart
const saveGame = '{"progress": 50}';

await client.writeStorageObject(
    session: session,
    collection: 'saves',
    key: 'savegame',
    value: saveGame,
    version: '<version>',
);

print('Stored objects:');
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NStorageObjectAcks& acks)
{
    std::cout << "Successfully stored objects " << acks.size() << std::endl;
};

std::vector<NStorageObjectWrite> objects;
NStorageObjectWrite savesObject;
savesObject.collection = "saves";
savesObject.key = "savegame";
savesObject.value = "{ \"progress\": 50 }";
savesObject.version = "<version>";
objects.push_back(savesObject);
client->writeStorageObjects(session, objects, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String saveGame = "{ \"progress\": 50 }";
StorageObjectWrite object = new StorageObjectWrite("saves", "savegame", saveGame, PermissionRead.OWNER_READ, PermissionWrite.OWNER_WRITE);
object.setVersion("<version>");
StorageObjectAcks acks = client.writeStorageObjects(session, object).get();
System.out.format("Stored objects %s", acks.getAcksList());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var save_game = "{ \"progress\": 50 }"
var can_read = 1
var can_write = 1
var version = "<version>"

var acks : NakamaAPI.ApiStorageObjectAcks = yield(client.write_storage_objects_async(session, [
    NakamaWriteStorageObject.new("saves", "savegame", can_read, can_write, save_game, version)
]), "completed")

if acks.is_exception():
    print("An error occurred: %s" % acks)
    return

print("Successfully stored objects:")

for a in acks.acks:
    print("%s" % a)
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var save_game = "{ \"progress\": 50 }"
var can_read = 1
var can_write = 1
var version = "<version>"

var acks : NakamaAPI.ApiStorageObjectAcks = await client.write_storage_objects_async(session, [
    NakamaWriteStorageObject.new("saves", "savegame", can_read, can_write, save_game, version)
])

if acks.is_exception():
    print("An error occurred: %s" % acks)
    return

print("Successfully stored objects:")

for a in acks.acks:
    print("%s" % a)
```
{{< / code >}}

{{< code type="client" >}}
```shell
PUT /v2/storage
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "objects": [
    {
      "collection": "saves",
      "key": "key",
      "value": "{\"hello\": \"world\"}",
      "version": "<version>"
    }
  ]
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local save_game = json.encode({ progress = 50 })
local can_read = 1
local can_write = 1
local version = "some previous version"

local objects = {
  {
    collection = "saves",
    key = "savegame",
    permissionRead = can_read,
    permissionWrite = can_write,
    value = save_game,
    version = version,
  }
}

local result = client.write_storage_objects(objects)

if result.error then
  print(result.message)
  return
end

for _,ack in ipairs(result.acks) do
  pprint(ack)
end
```
{{< / code >}}

We support another kind of conditional write which is used to write an object only if none already exists for that object's collection and key.

{{< code type="client" >}}
```bash
curl -X PUT "http://127.0.0.1:7350/v2/storage" \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "objects": [
      {
        "collection": "saves",
        "key": "savegame",
        "value": "{\"progress\": \"50\"}",
        "version": "*"
      }
    ]
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var save_game = { "progress": 50 };
const object_ids = await client.writeStorageObjects(session, [
  {
    "collection": "saves",
    "key": "savegame",
    "value": save_game,
    "version": "*"
  }
]);
console.info("Stored objects: %o", object_ids);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var saveGame = "{ \"progress\": 50 }";

var objectIds = await client.WriteStorageObjectsAsync(session, new [] {
  new WriteStorageObject
  {
      Collection = "saves",
      Key = "savegame",
      Value = saveGame,
      Version = "*"
  }
});

Console.WriteLine("Stored objects: [{0}]", string.Join(",\n  ", objectIds));
```
{{< / code >}}

{{< code type="client" >}}
```swift
let saveGame = "{ \"progress\": 50 }"

let acks = try await client.writeStorageObjects(session: session, WriteStorageObject(
    collection: "saves",
    key: "savegame",
    value: saveGame,
    version: "*"
))

debugPrint("Stored objects:", objectIds)
```
{{< / code >}}

{{< code type="client" >}}
```dart
const saveGame = '{"progress": 50}';

await client.writeStorageObject(
    session: session,
    collection: 'saves',
    key: 'savegame',
    value: saveGame,
    version: '*',
);

print('Stored objects:');
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NStorageObjectAcks& acks)
{
    std::cout << "Successfully stored objects " << acks.size() << std::endl;
};

std::vector<NStorageObjectWrite> objects;
NStorageObjectWrite savesObject;
savesObject.collection = "saves";
savesObject.key = "savegame";
savesObject.value = "{ \"progress\": 50 }";
savesObject.version = "*";
objects.push_back(savesObject);
client->writeStorageObjects(session, objects, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String saveGame = "{ \"progress\": 50 }";
StorageObjectWrite object = new StorageObjectWrite("saves", "savegame", saveGame, PermissionRead.OWNER_READ, PermissionWrite.OWNER_WRITE);
object.setVersion("*");
StorageObjectAcks acks = client.writeStorageObjects(session, object).get();
System.out.format("Stored objects %s", acks.getAcksList());
```
{{< / code >}}

{{< code type="client" >}}
```java
// Requires Nakama 1.x

String saveGame = "{\"progress\": 1}";
String version = "*"; // represents "no version".

CollatedMessage<ResultSet<RecordId>> message = StorageWriteMessage.Builder.newBuilder()
    .record("myapp", "saves", "savegame", saveGame, version)
    .build();

Deferred<ResultSet<RecordId>> deferred = client.send(message);

deferred.addCallback(new Callback<ResultSet<RecordId>, ResultSet<RecordId>>() {
    @Override
    public ResultSet<RecordId> call(ResultSet<RecordId> list) throws Exception {
        // Cache updated version for next write.
        version = list.getResults().get(0).getVersion();
        return list;
    }
}).addErrback(new Callback<Error, Error>() {
    @Override
    public Error call(Error err) throws Exception {
        System.err.format("Error('%s', '%s')", err.getCode(), err.getMessage());
        return err;
    }
});
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var save_game = "{ \"progress\": 50 }"
var can_read = 1
var can_write = 1
var version = "*" # represents "no version".

var acks : NakamaAPI.ApiStorageObjectAcks = yield(client.write_storage_objects_async(session, [
    NakamaWriteStorageObject.new("saves", "savegame", can_read, can_write, save_game, version)
]), "completed")

if acks.is_exception():
    print("An error occurred: %s" % acks)
    return

print("Successfully stored objects:")

for a in acks.acks:
    print("%s" % a)
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var save_game = "{ \"progress\": 50 }"
var can_read = 1
var can_write = 1
var version = "*" # represents "no version".

var acks : NakamaAPI.ApiStorageObjectAcks = await client.write_storage_objects_async(session, [
    NakamaWriteStorageObject.new("saves", "savegame", can_read, can_write, save_game, version)
])

if acks.is_exception():
    print("An error occurred: %s" % acks)
    return

print("Successfully stored objects:")

for a in acks.acks:
    print("%s" % a)
```
{{< / code >}}

{{< code type="client" >}}
```shell
PUT /v2/storage
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "objects": [
    {
      "collection": "saves",
      "key": "key",
      "value": "{\"hello\": \"world\"}",
      "version": "*"
    }
  ]
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local save_game = json.encode({ progress = 50 })
local can_read = 1
local can_write = 1
local version = "*"   -- no version


local objects = {
  {
    collection = "saves",
    key = "savegame",
    permissionRead = can_read,
    permissionWrite = can_write,
    value = save_game,
    version = version,
  }
}

local result = client.write_storage_objects(objects)

if result.error then
  print(result.message)
  return
end

for _,ack in ipairs(result.acks) do
  pprint(ack)
end
```
{{< / code >}}

## Read objects

Just like with [writing objects](#write-objects) you can read one or more objects from the database server.

Each object has an owner and permissions. An object can only be read if the permissions allow it. An object which has no owner can be fetched with `"null"` and is useful for global objects which all users should be able to read.

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/storage" \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "object_ids": [
      {
        "collection": "saves",
        "key": "savegame",
        "user_id": "some-user-id"
      }
    ]
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const objects = await client.readStorageObjects(session, {
  "object_ids": [{
    "collection": "saves",
    "key": "savegame",
    "user_id": session.user_id
  }]
});
console.info("Read objects: %o", objects);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.ReadStorageObjectsAsync(session, new [] {
  new StorageObjectId {
    Collection = "saves",
    Key = "savegame",
    UserId = session.UserId
  }
});

Console.WriteLine("Read objects: [{0}]", string.Join(",\n  ", result.Objects));
```
{{< / code >}}

{{< code type="client" >}}
```swift
var result = try await client.readStorageObjects(session: session, ids: [
    StorageObjectId(collection: "saves", key: "savegame", userId: session.UserId)
  ]
)

debugPrint("Read objects:", result.objects)
```
{{< / code >}}

{{< code type="client" >}}
```dart
final result = await client.readStorageObject(
  session: session,
  collection: 'saves',
  key: 'savegame',
  userId: session.userId,
)

print('Read objects: ${result}');
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NStorageObjects& objects)
{
  for (auto& object : objects)
    {
        std::cout << "Object key: " << object.key << ", value: " << object.value << std::endl;
    }
};

std::vector<NReadStorageObjectId> objectIds;
NReadStorageObjectId objectId;
objectId.collection = "saves";
objectId.key = "savegame";
objectId.userId = session->getUserId();
objectIds.push_back(objectId);
client->readStorageObjects(session, objectIds, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
StorageObjectId objectId = new StorageObjectId("saves");
objectId.setKey("savegame");
objectId.setUserId(session.getUserId());
StorageObjects objects = client.readStorageObjects(session, objectId).get();
System.out.format("Read objects %s", objects.getObjectsList().toString());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var result : NakamaAPI.ApiStorageObjects = yield(client.read_storage_objects_async(session, [
    NakamaStorageObjectId.new("saves", "savegame", session.user_id)
]), "completed")

if result.is_exception():
    print("An error occurred: %s" % result)
    return

print("Read objects:")

for o in result.objects:
    print("%s" % o)
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var result : NakamaAPI.ApiStorageObjects = await client.read_storage_objects_async(session, [
    NakamaStorageObjectId.new("saves", "savegame", session.user_id)
])

if result.is_exception():
    print("An error occurred: %s" % result)
    return

print("Read objects:")

for o in result.objects:
    print("%s" % o)
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/storage
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "object_ids": [
    {
      "collection": "saves",
      "key": "savegame",
      "user_id": "some-user-id"
    }
  ]
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local user_id = "some user id"

local objects_ids = {
  {
    collection = "saves",
    key = "savegame",
    userId = user_id
  }
}

local result = client.read_storage_objects(objects_ids)

if result.error then
  print(result.message)
  return
end

for _,object in ipairs(result.objects) do
  pprint(object)
end
```
{{< / code >}}

## List objects

You can list objects in a collection and page through results. 

The objects returned can be filtered to those owned by a specific user, or `"null"` for all public records.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/storage/saves?user_id=some-user-id&limit=10" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const limit = 100; // default is 10.
const objects = await client.listStorageObjects(session, "saves", session.user_id, limit);
console.info("List objects: %o", objects);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const int limit = 100; // default is 10.
var result = await client.ListUsersStorageObjectsAsync(session, "saves", session.UserId, limit);
Console.WriteLine("List objects: {0}", result);
```
{{< / code >}}

{{< code type="client" >}}
```swift
let limit = 100 // default is 10.
var result = try await client.listStorageObjects(session: session, collection: "saves", limit: limit)
debugPrint("List objects:", result.objects)
```
{{< / code >}}

{{< code type="client" >}}
```dart
const limit = 100; // default is 10.
final result = await client.listStorageObjects(
  session: session,
  collection: 'saves',
  userId: session.userId,
  limit: limit,
);
print('List objects: ${result.objects}');
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NStorageObjectListPtr list)
{
    for (auto& object : list->objects)
    {
        std::cout << "Object key: " << object.key << ", value: " << object.value << std::endl;
    }
};
client->listUsersStorageObjects(session, "saves", session->getUserId(), opt::nullopt, opt::nullopt, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
StorageObjectList objects = client.listUsersStorageObjects(session, "saves", session.getUserId()).get();
System.out.format("List objects %s", objects);
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var limit = 100 # default is 10.
var objects : NakamaAPI.ApiStorageObjectList = yield(client.list_storage_objects_async(session, "saves", session.user_id, limit), "completed")

if objects.is_exception():
  print("An error occurred: %s" % objects)
  return

print("List objects: %s" % objects)
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var limit = 100 # default is 10.
var objects : NakamaAPI.ApiStorageObjectList = await client.list_storage_objects_async(session, "saves", session.user_id, limit)

if objects.is_exception():
  print("An error occurred: %s" % objects)
  return

print("List objects: %s" % objects)
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/storage/<collection>?user_id=<user_id>&limit=<limit>&cursor=<cursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local user_id = "some user id"
local limit = 10
local result = client.list_storage_objects("saves", user_id, limit)

if result.error then
  print(result.message)
  return
end

for _,object in ipairs(result.objects) do
  pprint(object)
end
```
{{< / code >}}

## Remove objects

A user can remove an object if it has the correct permissions and they own it.

{{< code type="client" >}}
```bash
curl -X PUT "http://127.0.0.1:7350/v2/storage/delete" \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "object_ids": [
      {
        "collection": "saves",
        "key": "savegame"
      }
    ]
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
await client.deleteStorageObjects(session, {
  "object_ids": [{
    "collection": "saves",
    "key": "savegame"
  }]
});
console.info("Deleted objects.");
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.DeleteStorageObjectsAsync(session, new [] {
  new StorageObjectId {
    Collection = "saves",
    Key = "savegame"
  }
});

Console.WriteLine("Deleted objects.");
```
{{< / code >}}

{{< code type="client" >}}
```swift
var result = try await client.deleteStorageObjects(session: session, ids: [StorageObjectId(collection: "saves", key: "savegame")])

debugPrint("Deleted objects.")
```
{{< / code >}}

{{< code type="client" >}}
```dart
final result = await client.deleteStorageObjects(
  session: session,
  ids: [
    StorageObjectId(
      collection: 'saves',
      key: 'savegame',
    ),
  ],
);

print('Deleted objects.');
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = []()
{
    std::cout << "Deleted objects." << std::endl;
};

std::vector<NDeleteStorageObjectId> objectIds;
NDeleteStorageObjectId objectId;
objectId.collection = "saves";
objectId.key = "savegame";
objectIds.push_back(objectId);
client->deleteStorageObjects(session, objectIds, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
StorageObjectId objectId = new StorageObjectId("saves");
objectId.setKey("savegame");
client.deleteStorageObjects(session, objectId).get();
System.out.format("Deleted objects.");
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var del : NakamaAsyncResult = yield(client.delete_storage_objects_async(session, [
  NakamaStorageObjectId.new("saves", "savegame")
]), "completed")

if del.is_exception():
  print("An error occurred: %s" % del)
  return

print("Deleted objects.")
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var del : NakamaAsyncResult = await client.delete_storage_objects_async(session, [
  NakamaStorageObjectId.new("saves", "savegame")
])

if del.is_exception():
  print("An error occurred: %s" % del)
  return

print("Deleted objects.")
```
{{< / code >}}

{{< code type="client" >}}
```shell
PUT /v2/storage/delete
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "object_ids": [
    {
      "collection": "saves",
      "key": "savegame"
    }
  ]
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local objects_ids = {
  {
    collection = "saves",
    key = "savegame"
  }
}

local result = client.delete_storage_objects(object_ids)

if result.error then
  print(result.message)
  return
end
```
{{< / code >}}

You can also conditionally remove an object if the object version matches the version sent by the client.

{{< code type="client" >}}
```bash
curl -X PUT \
  http://127.0.0.1:7350/v2/storage/delete \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "object_ids": [
      {
        "collection": "saves",
        "key": "savegame",
        "version": "<version>"
      }
    ]
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
await client.deleteStorageObjects(session, {
  "object_ids": [{
    "collection": "saves",
    "key": "savegame",
    "version": "<version>"
  }]
});
console.info("Deleted objects.");
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.DeleteStorageObjectsAsync(session, new [] {
  new StorageObjectId {
    Collection = "saves",
    Key = "savegame",
    UserId = session.UserId,
    Version = "<version>"
  }
});

Console.WriteLine("Deleted objects.");
```
{{< / code >}}

{{< code type="client" >}}
```swift
var result = try await client.deleteStorageObjects(session: session, ids: [
    StorageObjectId(collection: "saves", key: "savegame", userId: session.userId, version = "<version>")
])

debugPrint("Deleted objects.");
```
{{< / code >}}

{{< code type="client" >}}
```dart
final result = try await client.deleteStorageObjects(
  session: session,
  ids: [
    StorageObjectId(
      collection: 'saves',
      key: 'savegame',
      userId: session.userId,
      version = '<version>',
    ),
  ],
);

print('Deleted objects.');
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = []()
{
    std::cout << "Deleted objects." << std::endl;
};

std::vector<NDeleteStorageObjectId> objectIds;
NDeleteStorageObjectId objectId;
objectId.collection = "saves";
objectId.key = "savegame";
objectId.version = "<version>";
objectIds.push_back(objectId);
client->deleteStorageObjects(session, objectIds, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
StorageObjectId objectId = new StorageObjectId("saves");
objectId.setKey("savegame");
objectId.setVersion("<version>");
client.deleteStorageObjects(session, objectId).get();
System.out.format("Deleted objects.");
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var del = yield(client.delete_storage_objects_async(session, [
  NakamaStorageObjectId.new("saves", "savegame", session.user_id, "<version>")
]), "completed")

if del.is_exception():
  print("An error occurred: %s" % del)
  return

print("Deleted objects.")
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var del = await client.delete_storage_objects_async(session, [
  NakamaStorageObjectId.new("saves", "savegame", session.user_id, "<version>")
])

if del.is_exception():
  print("An error occurred: %s" % del)
  return

print("Deleted objects.")
```
{{< / code >}}

{{< code type="client" >}}
```shell
PUT /v2/storage/delete
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "object_ids": [
    {
      "collection": "saves",
      "key": "savegame",
      "version": "<version>"
    }
  ]
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local objects_ids = {
  {
    collection = "saves",
    key = "savegame",
    version = "some version"
  }
}

local result = nakama.delete_storage_objects(object_ids)

if result.error then
  print(result.message)
  return
end
```
{{< / code >}}
