# Implement Friend Codes

**URL:** https://heroiclabs.com/docs/nakama/guides/concepts/friend-codes/
**Keywords:** friend, friend code, friend codes, invite, invite code, invite codes, nakama
**Categories:** nakama, guides

---


# Implement friend codes

Let players generate a code they can share with others outside the game to add each other as friends.

A friend code can work in two ways:

- As a short code a player types into the game
- As a deep link that opens the app store if the game isn't installed yet
  - If it's already installed, opens the game directly and adds the friend automatically

Using [custom RPCs](../../../server-framework/go-runtime/code-samples/#rpc), build a way to generate and claim unique friend codes to make adding friends easier.

{{< screenshot src="/images/pages/nakama/guides/concepts/friend-codes/after-claim.png" alt="Players can generate a friend code to send outside the game, or claim one to add each other as friends" caption="Players can generate a friend code to send outside the game, or claim one to add each other as friends" >}}

## Before you begin

The example project for this guide is built in Unity. To follow along, clone the [Friend Codes](https://github.com/heroiclabs/sample-projects/tree/main/guides/FriendCodes) project and open it in your Unity Editor.

### Running the server locally

The sample project includes a Docker Compose file that starts Nakama and the database. Run it to see the friend codes backend in action as you work through the guide:

1. Go to the `server/` directory.
2. From the command line, run `docker compose up --build`.
3. Access the Nakama console at `http://localhost:7351`.

## Initialize the custom RPCs

Register two custom RPCs: one to generate a unique code, and one to claim it.

{{< pretitle "Step 1" >}}

### Register the systems in InitModule

Inside `main.go`, register the custom RPCs in your `InitModule` function.

{{< code type="server" filename="main.go" hideable="false" >}}

```go
if err := initializer.RegisterRpc("generate_friend_code", RpcGenerateFriendCode); err != nil {
  return err
}
if err := initializer.RegisterRpc("claim_friend_code", RpcClaimFriendCode); err != nil {
  return err
}
```

{{< /code >}}

{{< pretitle "Step 2" >}}

### Create friendcodes.go

Create a new file `friendcodes.go` to keep `main.go` tidy. This is where you'll define the custom RPCs and their logic. Start by defining some constants at the top of the file.

{{< code type="server" filename="friendcodes.go" hideable="false">}}

```go
const (
	friendCodesCollection = "invite_codes"
	userInviteCollection  = "invite_codes_user"
	codeLength            = 6
	codeAlphabet          = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" // no O/0, I/1 to avoid ambiguity
	codeTTL               = 72 * time.Hour                     // time to live (how long the code is valid for after creation)
)
```

{{< /code >}}

{{< pretitle "Step 3" >}}

### Define structs

Next, define structs to use when interacting with the storage engine and sending or receiving RPCs.

{{< code type="server" filename="friendcodes.go" hideable="false">}}

```go
type inviteRecord struct {
	OwnerID   string `json:"owner_id"`
	ExpiresAt int64  `json:"expires_at"`
}

type userInviteRecord struct {
	Code      string `json:"code"`
	ExpiresAt int64  `json:"expires_at"`
}

type claimRequest struct {
	Code string `json:"code"`
}
```

{{< /code >}}

## GenerateFriendCode RPC

Start with the GenerateFriendCode RPC.

{{< pretitle "Step 1" >}}

### RPC body

Here's what the RPC is doing:

1. Finding which user requested to generate a friend code.
2. Checking custom storage to see whether this user already has a valid friend code that was previously generated.
3. If they do have a valid friend code already, simply return that to the caller.
4. If not, generate/mint a new code.
5. After successfully generating a new code, write it to a custom storage collection for future lookup.
    - `friendCodesCollection`: Only accessible by the server. Provides a direct O(1) lookup to find who a code belongs to when redeeming.
    - `userInviteCollection`: Readable by the owner. Used to check if they already have a valid code.
6. Return the new code to the caller.

{{< code type="server" filename="friendcodes.go" hideable="false">}}

```go
// RpcGenerateFriendCode Try to generate a new friend code for the user, first checking if a valid code already exists.
func RpcGenerateFriendCode(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
	userID, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
	if !ok || userID == "" {
		return "", runtime.NewError("no user id in context", 3)
	}

	now := time.Now()

	// Reuse an existing, valid code for this user rather than generating a new one.
	if existing, err := readUserInvite(ctx, nk, userID); err == nil && existing != nil {
		if existing.ExpiresAt > now.Unix() {
			return marshalCodeResponse(existing.Code, existing.ExpiresAt)
		}
	}

	code, err := mintUniqueCode(ctx, nk)
	if err != nil {
		logger.Error("failed to mint invite code: %v", err)
		return "", runtime.NewError("could not generate code", 13)
	}

	expiresAt := now.Add(codeTTL).Unix()

	globalRec := inviteRecord{
		OwnerID:   userID,
		ExpiresAt: expiresAt,
	}
	globalVal, _ := json.Marshal(globalRec)

	userRec := userInviteRecord{Code: code, ExpiresAt: expiresAt}
	userVal, _ := json.Marshal(userRec)

	writes := []*runtime.StorageWrite{
		{
			Collection:      friendCodesCollection,
			Key:             code,
			Value:           string(globalVal),
			PermissionRead:  0,
			PermissionWrite: 0,
		},
		{
			Collection:      userInviteCollection,
			Key:             "active",
			UserID:          userID,
			Value:           string(userVal),
			PermissionRead:  1,
			PermissionWrite: 0,
		},
	}

	if _, err := nk.StorageWrite(ctx, writes); err != nil {
		logger.Error("failed to write invite code: %v", err)
		return "", runtime.NewError("could not save code", 13)
	}

	return marshalCodeResponse(code, expiresAt)
}
```

{{< /code >}}

{{< pretitle "Step 2" >}}

### Read user invite

Check the custom storage collection to see if this user already has an existing friend code.

{{< code type="server" filename="friendcodes.go" hideable="false">}}

```go
// Try to find an existing code for the user.
func readUserInvite(ctx context.Context, nk runtime.NakamaModule, userID string) (*userInviteRecord, error) {
	objs, err := nk.StorageRead(ctx, []*runtime.StorageRead{
		{Collection: userInviteCollection, Key: "active", UserID: userID},
	})
	if err != nil || len(objs) == 0 {
		return nil, err
	}
	var rec userInviteRecord
	if err := json.Unmarshal([]byte(objs[0].Value), &rec); err != nil {
		return nil, err
	}
	return &rec, nil
}
```

{{< /code >}}

{{< pretitle "Step 3" >}}

### Mint unique code

Generates a new code for the user, checking storage after generation to make sure it's unique. If it's not unique, it generates a new code and tries again. This uses a fixed number of retries to avoid the server getting stuck in unforeseen circumstances.

{{< code type="server" filename="friendcodes.go" hideable="false">}}

```go
// Call GenerateRandomCode until the value is unique, stops after 5 attempts.
func mintUniqueCode(ctx context.Context, nk runtime.NakamaModule) (string, error) {
	for attempt := 0; attempt < 5; attempt++ {
		code, err := generateRandomCode()
		if err != nil {
			return "", err
		}
		objs, err := nk.StorageRead(ctx, []*runtime.StorageRead{
			{Collection: friendCodesCollection, Key: code},
		})
		if err == nil && len(objs) == 0 {
			return code, nil
		}
	}
	return "", runtime.NewError("could not generate a unique code, try again", 13)
}
```

{{< /code >}}

{{< pretitle "Step 4" >}}

### Generate random code

Generate a new code with `codeLength` length, using only the defined `codeAlphabet` characters.

{{< code type="server" filename="friendcodes.go" hideable="false">}}

```go
// Generate a random codeLength code only using characters in the codeAlphabet.
func generateRandomCode() (string, error) {
	bytes := make([]byte, codeLength)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	out := make([]byte, codeLength)
	for i, b := range bytes {
		out[i] = codeAlphabet[int(b)%len(codeAlphabet)]
	}
	return string(out), nil
}
```

{{< /code >}}

{{< pretitle "Step 5" >}}

### Marshal code response

Helper function to create the JSON response that the client is expecting.

{{< code type="server" filename="friendcodes.go" hideable="false">}}

```go
func marshalCodeResponse(code string, expiresAt int64) (string, error) {
	resp, err := json.Marshal(map[string]any{
		"code":       code,
		"expires_at": expiresAt,
	})
	return string(resp), err
}
```

{{< /code >}}

## ClaimFriendCode RPC

Next, build the ClaimFriendCode RPC.

{{< pretitle "Step 1" >}}

### RPC body

Here's what the RPC is doing:

1. Finding which user requested to claim a friend code.
2. Unmarshal the payload and try to read the code.
3. Make sure that the code is valid and has not expired.
4. Make sure that the user is not trying to claim their own code.
5. Finally, attempt to add the two users as friends. This is done by calling `FriendsAdd` both ways to establish the relationship.

{{< code type="server" filename="friendcodes.go" hideable="false">}}

```go
// RpcClaimFriendCode Try to claim a friend code for the calling user.
func RpcClaimFriendCode(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
	claimerID, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
	if !ok || claimerID == "" {
		return "", runtime.NewError("no user id in context", 3)
	}

	var req claimRequest
	if err := json.Unmarshal([]byte(payload), &req); err != nil || req.Code == "" {
		return "", runtime.NewError("code is required", 3)
	}

	objs, err := nk.StorageRead(ctx, []*runtime.StorageRead{
		{Collection: friendCodesCollection, Key: req.Code},
	})
	if err != nil || len(objs) == 0 {
		return "", runtime.NewError("invalid or expired code", 5)
	}

	var rec inviteRecord
	if err := json.Unmarshal([]byte(objs[0].Value), &rec); err != nil {
		return "", runtime.NewError("invalid or expired code", 5)
	}
	if time.Now().Unix() > rec.ExpiresAt {
		return "", runtime.NewError("invalid or expired code", 5)
	}
	if rec.OwnerID == claimerID {
		return "", runtime.NewError("you can't claim your own code", 3)
	}

	// Add in both directions so the request is auto-confirmed
	if err := nk.FriendsAdd(ctx, claimerID, "", []string{rec.OwnerID}, nil, nil); err != nil {
		logger.Error("friendsAdd (claimer->owner) failed: %v", err)
		return "", runtime.NewError("could not add friend", 13)
	}
	if err := nk.FriendsAdd(ctx, rec.OwnerID, "", []string{claimerID}, nil, nil); err != nil {
		logger.Error("friendsAdd (owner->claimer) failed: %v", err)
		return "", runtime.NewError("could not add friend", 13)
	}

	resp, _ := json.Marshal(map[string]any{
		"success": true,
	})
	return string(resp), nil
}
```

{{< /code >}}

## Test your friend codes

With the server running and the Unity project open, confirm each piece works end to end.

In the Unity client:

1. Select **Play** in the Unity Editor and sign in to create a player.
2. Select **Generate**. The code copies automatically to your clipboard.
{{< screenshot src="/images/pages/nakama/guides/concepts/friend-codes/after-generate.png" alt="A player generates a friend code that copies to the clipboard" caption="A player generates a friend code that copies to the clipboard" >}}
3. Use the [Account Switcher](../../../../sample-projects/unity/nakama-friends/#account-switcher) to switch to a second account.
4. Paste the friend code in the **Enter code...** field and select **Claim**.
{{< screenshot src="/images/pages/nakama/guides/concepts/friend-codes/after-claim.png" alt="A second player claims the friend code and adds the first player as a friend" caption="A second player claims the friend code and adds the first player as a friend" >}}

Switch back to the initial player to confirm the second player now appears in their friends list.

## Deep linking

Set up Unity to enable deep linking. This lets you:

1. Generate a deep link with the friend code embedded
2. Send the deep link to a friend outside the game
3. Let them open the link. If configured correctly, this takes them to the app store if they don't have the app yet, or opens the app directly
4. Read the code from the deep link when the app opens this way, and automatically call the RPC

For iOS, see [Unity's guide to enabling deep linking](https://docs.unity3d.com/Manual/deep-linking-ios.html).
For Android, see [Unity's guide to enabling deep linking](https://docs.unity3d.com/Manual/deep-linking-android.html).

### Generate a deep link

After you enable deep linking, go to your server code and adjust the `const` block to define `deepLinkScheme` (set this to the scheme you configured when enabling deep linking for your platform). Then modify the `marshalCodeResponse` function to also return a deep link to the client.

{{< note "important" "Android Studio Emulator" >}}
If testing using the Android Studio Emulator, change the host address to `10.0.2.2`, otherwise the app won't be able to reach your localhost server.
See [here](https://developer.android.com/studio/run/emulator-networking-address) for more details.
{{< / note >}}

{{< code type="server" filename="friendcodes.go" hideable="false">}}

```go
const (
	friendCodesCollection = "invite_codes"
	userInviteCollection  = "invite_codes_user"
	codeLength            = 6
	codeAlphabet          = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" // no O/0, I/1 to avoid ambiguity
	codeTTL               = 72 * time.Hour                     // time to live (how long the code is valid for after creation)
	deepLinkScheme        = "myunityapp"
)
```

{{< /code >}}

{{< code type="server" filename="friendcodes.go" hideable="false">}}

```go
func marshalCodeResponse(code string, expiresAt int64) (string, error) {
	resp, err := json.Marshal(map[string]any{
		"code":       code,
		"expires_at": expiresAt,
		"deep_link":  deepLinkScheme + "://friendcode?code=" + code,
	})
	return string(resp), err
}
```

{{< /code >}}

Now, update the Unity client's UI to display the deep link instead of just the code.

{{< code type="csharp" filename="FriendCodesController.cs" hideable="false">}}

```cs
private async Task GenerateFriendCode()
{
    try
    {
        var session = NakamaSingleton.Instance.Session;
        var result = await NakamaSingleton.Instance.Client.RpcAsync(session, "generate_friend_code");
        var response = JsonUtility.FromJson<FriendCodeData>(result.Payload);

        Debug.Log($"Code generated successfully.");

        // Display code on UI and copy to clipboard.
        friendCodeField.SetValueWithoutNotify(response.deep_link);
        GUIUtility.systemCopyBuffer = response.deep_link;
    }
    catch (Exception e)
    {
        Debug.LogWarning($"Generate friend code failed: {e.Message}");
    }
}
```

{{< /code >}}

Finally, hook into `DeepLinkManager` to call the `claim_friend_code` RPC when the app opens through a deep link, or when someone selects a deep link while the app is running.

1. Listen for the `OnInviteCodeReceived` event. This handles the case where the app is already open.

{{< code type="csharp" filename="FriendCodesController.cs" hideable="false">}}

```cs
private void OnEnable()
{
    if (DeepLinkManager.Instance != null)
    {
        DeepLinkManager.Instance.OnInviteCodeReceived += HandleInviteCodeReceived;
    }
}

private void OnDisable()
{
    if (DeepLinkManager.Instance != null)
    {
        DeepLinkManager.Instance.OnInviteCodeReceived -= HandleInviteCodeReceived;
    }
}
```

{{< /code >}}

2. Check `DeepLinkManager.Instance.PendingInviteCode` directly after initializing. This handles the case where the deep link opened the app.

{{< code type="csharp" filename="FriendCodesController.cs" hideable="false">}}

```cs
private void Start()
{
    InitializeUI();
    NakamaSingleton.Instance.ReceivedStartError += e =>
    {
        Debug.LogException(e);
        errorPopup.style.display = DisplayStyle.Flex;
        errorMessage.text = e.Message;
    };
    NakamaSingleton.Instance.Socket.ReceivedNotification += OnReceivedNotification;
    NakamaSingleton.Instance.ReceivedStartSuccess += session =>
    {
        OnInitialized?.Invoke(session, this);

        // Load friends by default.
        _ = UpdateFriendsList(FriendState.Friend);

        // If a friend code was already waiting before we were ready, try to add them.
        var pending = DeepLinkManager.Instance != null ? DeepLinkManager.Instance.PendingInviteCode : null;
        if (!string.IsNullOrEmpty(pending))
        {
            _ = ClaimFriendCode(pending);
        }
    };
}
```

{{< /code >}}

## Conclusion

You've implemented friend codes, letting players easily connect without accepting requests or typing long usernames. With deep-linking, players can simply click a link sent externally by a friend to download/open the app and then automatically add each other as friends.

## See also

- [Nakama Friends](../../../concepts/friends/)
- [Friends Sample Project](../../../../sample-projects/unity/nakama-friends/)
- [Heroic Labs forum](https://forum.heroiclabs.com/c/hiro/)