# User accounts

**URL:** https://heroiclabs.com/docs/nakama/concepts/user-accounts/
**Summary:** A user represents an identity within the server. Every user is registered and has a profile for other users to find and become friends with or join groups and chat.A user can own records, share public information with other users, and authenticate via a variety of social providers.
**Keywords:** fetch user accounts, virtual wallet, online indicator, fetch users, update account, metadata
**Categories:** nakama, user-accounts, concepts

---


# User accounts

A user represents an identity within the server. Every user is registered and has a profile for other users to find and become [friends](../friends/) with or join [groups](../groups/) and [chat](../chat/).

A user can own [records](../storage/permissions/), share public information with other users, and authenticate via a variety of different social providers.

{{< note "important" >}}
The system owner identity is represented by a user account with a nil UUID (`00000000-0000-0000-0000-000000000000`).
{{< / note >}}

## Fetch account

When a user has a session you can retrieve their account. The profile contains a variety of information which includes various "linked" social providers.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/account" \
  -H 'authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const account = await client.getAccount(session);
const user = account.user;
console.info("User id '%o' and username '%o'.", user.id, user.username);
console.info("User's wallet:", account.wallet);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var account = await client.GetAccountAsync(session);
var user = account.User;
System.Console.WriteLine("User id '{0}' username '{1}'", user.Id, user.Username);
System.Console.WriteLine("User wallet: '{0}'", account.Wallet);
```
{{< / code >}}

{{< code type="client" >}}
```swift
let account = await client.getAccount(session: session)
let user = account.user
debugPrint("User id:", user.id, "username:", user.username)
debugPrint("User wallet:", account.wallet)
```
{{< / code >}}

{{< code type="client" >}}
```dart
final account = await client.getAccount(session: session);
final user = account.user;
print('User id: ${user.id}, username: ${user.username}');
print('User wallet: ${account.wallet}');
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NAccount& account)
{
    std::cout << "User's wallet: " << account.wallet.c_str() << std::endl;
};

client->getAccount(session, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
Account account = client.getAccount(session);
User user = account.getUser();
System.out.format("User id %s username %s", user.getId(), user.getUsername());
System.out.format("User wallet %s", account.getWallet());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var account : NakamaAPI.ApiAccount = yield(client.get_account_async(session), "completed")

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

var user = account.user
print("User id '%s' and username '%s'." % [user.id, user.username])
print("User's wallet: %s." % account.wallet)
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var account : NakamaAPI.ApiAccount = await client.get_account_async(session)

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

var user = account.user
print("User id '%s' and username '%s'." % [user.id, user.username])
print("User's wallet: %s." % account.wallet)
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/account
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 result = client.get_account()

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

local user = result.user
print(("User id '%s' and username '%s'."):format(user.id, user.username))
```
{{< / code >}}

Some information like wallet, device IDs and custom ID are private but part of the profile is visible to other users.

{{< table name="nakama.concepts.user-accounts.public-fields" >}}

{{< table name="nakama.concepts.user-accounts.private-fields" >}}

### User metadata

You can store additional fields for a user in `user.metadata` which is useful to share data you want to be public to other users. We recommend using user metadata to store very common fields which other users will need to see. For example, use metadata to enable users to show biographical details if desired, or to display their character name, level, and game statistics.

For all other information you can store records with [public read permissions](../storage/collections/) which other users can find.

Metadata is limited to 16KB per user. This can be set only via the [script runtime](../../server-framework/), similar to the `wallet`.

The following example showcases using user metadata to store a VIP status which is then used as part of a before join tournament hook which only allows VIP members to join.

{{< code type="server" >}}
```typescript
// Assuming a user metadata structure as follows
const metadata = {
  vip: true
};

// Add a before hook to only allow VIP users to join the vip_only tournament
let BeforeJoinTournament: nkruntime.BeforeHookFunction<JoinTournamentRequest> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: nkruntime.JoinTournamentRequest): nkruntime.JoinTournamentRequest | void {
  const account = nk.accountGetId(ctx.userId)

  // Only do the following checks if the tournament id is `vip_only`
  if (data.tournamentId != "vip_only") {
    return data;
  }

  // Only continue with the Join Tournament if the actioning user is a vip
  if (account.user.metadata["vip"]) {
    return data;
  }

  logger.warn("you must be a vip to join this tournament")
  return null;
};

// Register inside InitModule
initializer.registerBeforeJoinTournament(BeforeJoinTournament);
```
{{< / code >}}

{{< code type="server" >}}
```go
// Assuming a user metadata structure as follows
type UserMetadata struct {
	Vip bool `json:"vip"`
}

metadata := &UserMetadata {
  Vip: true,
}

// Add a before hook to only allow VIP users to join the vip_only tournament
if  err := initializer.RegisterBeforeJoinTournament(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.JoinTournamentRequest) (*api.JoinTournamentRequest, error) {
  userId, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
  if !ok {
    logger.Error("invalid user")
    return nil, runtime.NewError("invalid user", 13)
  }

  // Get the user's metadata
  account, err := nk.AccountGetId(ctx, userId)
  if err != nil {
    logger.Error("error getting user account")
    return nil, runtime.NewError("error getting user account", 13)
  }

  // Only do the following checks if the tournament id is `vip_only`
  if in.TournamentId != "vip_only" {
    return in, nil
  }

  // Only continue with the Join Tournament if the actioning user is a vip
  var metadata UserMetadata
  if err := json.Unmarshal([]byte(account.User.GetMetadata()), &metadata); err != nil {
    logger.Error("error deserializing metadata")
    return nil, runtime.NewError("error deserializing metadata", 13)
  }

  if metadata.Vip {
    return in, nil
  }

  return nil, runtime.NewError("you must be a vip user to join this tournament", 7)
}); err != nil {
  logger.Error("unable to register before join tournament hook: %v", err)
  return err
}
```
{{< / code >}}

{{< code type="server" >}}
```lua
-- Assuming a user metadata structure as follows
local metadata = {
    ["vip"] = true
}

-- Add a before hook to only allow VIP users to join the vip_only tournament
local function before_join_tournament(context, payload)
    local account = nk.account_get_id(context.user_id)

    -- Only do the following checks if the tournament id is `vip_only`
    if payload.tournament_id ~= "vip_only" then
        return payload
    end

    -- Only continue with the Join Tournament request if the actioning user is a vip
    if account.user.metadata["vip"] then
        return payload
    end

    nk.logger_error("you must be a vip user to join this tournament")
    return nil
end

nk.register_req_before(before_join_tournament, "JoinTournament")
```
{{< / code >}}

### Virtual wallet

Nakama has the concept of a virtual wallet and transaction ledger. Nakama allows developers to create, update and list changes to the user's wallet. This operation has transactional guarantees and is only achievable with the [script runtime](../../server-framework/).

With server-side code it's possible to update the user's wallet.

{{< code type="server" >}}
```lua
local nk = require("nakama")
local user_id = "8f4d52c7-bf28-4fcf-8af2-1d4fcf685592"

local changeset = {
  coins = 10, -- Add 10 coins to the user's wallet.
  gems = -5   -- Remove 5 gems from the user's wallet.
}

local metadata = {
  game_result = "won"
}

local updated, previous = nk.wallet_update(user_id, changeset, metadata, true)
```
{{< / code >}}

{{< code type="server" >}}
```go
userID := "8f4d52c7-bf28-4fcf-8af2-1d4fcf685592"
changeset := map[string]interface{}{
    "coins": 10, // Add 10 coins to the user's wallet.
    "gems":  -5, // Remove 5 gems from the user's wallet.
}
metadata := map[string]interface{}{
    "game_result": "won",
}
updated, previous, err := nk.WalletUpdate(ctx, userID, changeset, metadata, true)
if err != nil {
    logger.WithField("err", err).Error("Wallet update error.")
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
let user_id = '8f4d52c7-bf28-4fcf-8af2-1d4fcf685592';

let changeset = {
  coins: 10, // Add 10 coins to the user's wallet.
  gems: -5,   // Remove 5 gems from the user's wallet.
};

let metadata = {
  gameResult: 'won'
};

let result: nkruntime.WalletUpdateResult;

try {
    result = nk.walletUpdate(user_id, changeset, metadata, true);
} catch (error) {
    // Handle error
}
```
{{< / code >}}

The wallet is private to a user and cannot be seen by other users. You can fetch wallet information for a user via [Fetch Account](../user-accounts/#fetch-account) operation.

### Online indicator

Nakama can report back user online indicators in two ways:

1. [Fetch user](#fetch-users) information to see if they are online. The user presence will be detected if they have an active [socket connection](../sockets/#establishing-a-socket-connection) to the server and did not set `appearOnline=false`.
2. Publish and subscribe to user [status presence](../status/) updates. This will give you updates when the online status of the user changes (along side a custom message).

## Fetch users

You can fetch one or more users by their IDs or handles. This is useful for displaying public profiles with other users.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/user?ids=userid1&ids=userid2&usernames=username1&usernames=username2&facebook_ids=facebookid1" \
  -H 'authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const users = await client.getUsers(session, ["user_id1"], ["username1"], ["facebookid1"]);

users.foreach(user => {
  console.info("User id '%o' and username '%o'.", user.id, user.username);
});
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var ids = new[] {"userid1", "userid2"};
var usernames = new[] {"username1", "username2"};
var facebookIds = new[] {"facebookid1"};
var result = await client.GetUsersAsync(session, ids, usernames, facebookIds);

foreach (var u in result.Users)
{
    System.Console.WriteLine("User id '{0}' username '{1}'", u.Id, u.Username);
}
```
{{< / code >}}

{{< code type="client" >}}
```swift
let ids = ["userid1", "userid2"]
let usernames = ["username1", "username2"]
let facebookIds = ["facebookid1"]
let result = await client.getUsers(session: session, ids: ids, usernames: usernames, facebookIds: facebookIds)

for u in result.users {
    debugPrint("User id:", u.id, "username:", u.username)
}
```
{{< / code >}}

{{< code type="client" >}}
```dart
final ids = ["userid1", "userid2"];
final usernames = ["username1", "username2"];
final facebookIds = ["facebookid1"];
final users = await client.getUsers(
  session: session,
  ids: ids,
  usernames: usernames,
  facebookIds: facebookIds,
);

for (final user in users) {
  print('User id: ${user.id}, username: ${user.username}');
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NUsers& users)
{
    for (auto& user : users.users)
    {
        std::cout << "User id '" << user.id << "' username " << user.username << std::endl;
    }
};
client->getUsers(session, { "user_id1" }, { "username1" }, { "facebookid1" }, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
List<String> ids = Arrays.asList("userid1", "userid2");
List<String> usernames = Arrays.asList("username1", "username1");
String[] facebookIds = new String[] {"facebookid1"};
Users users = client.getUsers(session, ids, usernames, facebookIds).get();

for (User user : users.getUsersList()) {
  System.out.format("User id %s username %s", user.getId(), user.getUsername());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var ids = ["userid1", "userid2"]
var usernames = ["username1", "username2"]
var facebook_ids = ["facebookid1"]
var result : NakamaAPI.ApiUsers = yield(client.get_users_async(session, ids, usernames, facebook_ids), "completed")

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

for u in result.users:
    print("User id '%s' username '%s'" % [u.id, u.username])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var ids = ["userid1", "userid2"]
var usernames = ["username1", "username2"]
var facebook_ids = ["facebookid1"]
var result : NakamaAPI.ApiUsers = await client.get_users_async(session, ids, usernames, facebook_ids)

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

for u in result.users:
    print("User id '%s' username '%s'" % [u.id, u.username])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/user?ids=userid1&ids=userid2&usernames=username1&usernames=username2&facebook_ids=facebookid1
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 ids = { "userid1", "userid2" }
local usernames = { "username1", "username2" }
local facebook_ids = { "facebookid1" }
local result = client.get_users(ids, usernames, facebook_ids)

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

local users = result.users

for _,user in ipairs(users) do
  print(("User id '%s' and username '%s'."):format(user.id, user.username))
end
```
{{< / code >}}

You can also fetch one or more users in server-side code.

{{< code type="server" >}}
```lua
local nk = require("nakama")

local user_ids = {
  "3ea5608a-43c3-11e7-90f9-7b9397165f34",
  "447524be-43c3-11e7-af09-3f7172f05936"
}

local users = nk.users_get_id(user_ids)

for _, u in ipairs(users) do
  local message = ("username: %q, displayname: %q"):format(u.username, u.display_name)
  nk.logger_info(message)
end
```
{{< / code >}}

{{< code type="server" >}}
```go
if users, err := nk.UsersGetId(ctx, []string{
    "3ea5608a-43c3-11e7-90f9-7b9397165f34",
    "447524be-43c3-11e7-af09-3f7172f05936",
}); err != nil {
    // Handle error.
} else {
    for _, u := range users {
      logger.Info("username: %s, displayname: %s", u.Username, u.DisplayName)
    }
}
```
{{< / code >}}

## Update account

When a user is registered most of their profile is setup with default values. A user can update their own profile to change fields but cannot change any other user's profile.

{{< code type="client" >}}
```bash
curl -X PUT "http://127.0.0.1:7350/v2/account" \
  -H 'authorization: Bearer <session token>' \
  --data '{
    "display_name": "My new name",
    "avatar_url": "http://graph.facebook.com/avatar_url",
    "location": "San Francisco"
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
await client.updateAccount(session, {
  display_name: "My new name",
  avatar_url: "http://graph.facebook.com/avatar_url",
  location: "San Francisco"
});
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string displayName = "My new name";
const string avatarUrl = "http://graph.facebook.com/avatar_url";
const string location = "San Francisco";
await client.UpdateAccountAsync(session, null, displayName, avatarUrl, null, location);
```
{{< / code >}}

{{< code type="client" >}}
```swift
let displayName = "My new name"
let avatarUrl = "http://graph.facebook.com/avatar_url"
let location = "San Francisco"
await client.updateAccount(session: session, username: nil, displayName: displayName, avatarUrl: avatarUrl, langTag: nil, location: location)
```
{{< / code >}}

{{< code type="client" >}}
```dart
final displayName = 'My new name';
final avatarUrl = 'http://graph.facebook.com/avatar_url';
final location = 'San Francisco';
await client.updateAccount(
  session: session,
  username: null,
  displayName: displayName,
  avatarUrl: avatarUrl,
  langTag: null,
  location: location,
);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
client->updateAccount(session, opt::nullopt, "My new name", "http://graph.facebook.com/avatar_url", opt::nullopt, "San Francisco");
```
{{< / code >}}

{{< code type="client" >}}
```java
String displayName = "My new name";
String avatarUrl = "http://graph.facebook.com/avatar_url";
String location = "San Francisco";
client.updateAccount(session, null, displayName, avatarUrl, null, location);
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var display_name = "My new name";
var avatar_url = "http://graph.facebook.com/avatar_url";
var location = "San Francisco";
var update : NakamaAsyncResult = yield(client.update_account_async(session, null, display_name, avatar_url, null, location), "completed")

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

print("Account updated")
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var display_name = "My new name";
var avatar_url = "http://graph.facebook.com/avatar_url";
var location = "San Francisco";
var update : NakamaAsyncResult = await client.update_account_async(session, null, display_name, avatar_url, null, location)

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

print("Account updated")
```
{{< / code >}}

{{< code type="client" >}}
```shell
PUT /v2/account HTTP/1.1
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "display_name": "My new name",
  "avatar_url": "http://graph.facebook.com/avatar_url",
  "location": "San Francisco"
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local display_name = "Björn"
local avatar_url = "http://graph.facebook.com/avatar_url"
local lang_tag = ""
local timezone = ""
local username = ""
local location = "Stockholm"

local result = client.update_account(client, avatar_url, display_name, lang_tag, location, timezone, username)

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

print("Account updated")
```
{{< / code >}}

With server-side code it's possible to update any user's profile.

{{< code type="server" >}}
```lua
local nk = require("nakama")

local user_id = "4ec4f126-3f9d-11e7-84ef-b7c182b36521" -- some user's id.
local metadata = {}
local username = "my-new-username"
local display_name = "My new Name"
local timezone = nil
local location = "San Francisco"
local lang_tag = nil
local avatar_url = "http://graph.facebook.com/avatar_url"

local status, err = pcall(nk.account_update_id, user_id, metadata, username, display_name, timezone, location, lang_tag, avatar_url)

if (not status) then
  nk.logger_info(("Account update error: %q"):format(err))
end
```
{{< / code >}}

{{< code type="server" >}}
```go
userID := "4ec4f126-3f9d-11e7-84ef-b7c182b36521" // some user's id.
username := "my-new-username" // must be unique
metadata := make(map[string]interface{})
displayName := "My new name"
timezone := ""
location := "San Francisco"
langTag := ""
avatarUrl := "http://graph.facebook.com/avatar_url"

if err := nk.AccountUpdateId(ctx, userID, username, metadata, displayName, timezone, location, langTag, avatarUrl); err != nil {
    // Handle error.
    logger.Error("Account update error: %s", err.Error())
}
```
{{< / code >}}

## Delete account

You can delete a user's account on the server. This will remove all data associated with the user.

{{< code type="client" >}}
```bash
curl -X DELETE "http://127.0.0.1:7350/v2/account" \
  -H 'authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var account : NakamaAsyncResult = yield(client.delete_account_async(session), "completed")
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var account : NakamaAsyncResult = await client.delete_account_async(session)
```
{{< / code >}}

{{< code type="client" >}}
```csharp
await client.DeleteAccountAsync(session);
```
{{< / code >}}

{{< code type="client" >}}
```swift
await client.deleteAccount(session: session)
```
{{< / code >}}

{{< code type="client" >}}
```dart
await client.deleteAccount(session: session);
```
{{< / code >}}

{{< code type="client" >}}
```shell
DELETE /v2/account
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" >}}
```javascript
await client.deleteAccount(session);
```
{{< / code >}}

{{< missing type="client" lang="java" />}}
{{< missing type="client" lang="cpp" />}}
{{< missing type="client" framework="defold" lang="lua" />}}
