diff --git a/docs/activities/building-an-activity.mdx b/docs/activities/building-an-activity.mdx
index 81705ce0e1..67db95c65e 100644
--- a/docs/activities/building-an-activity.mdx
+++ b/docs/activities/building-an-activity.mdx
@@ -316,7 +316,7 @@ Back in your app's settings, click on the **URL Mappings** page under **Activiti
|--------|-----------------------------------------|
| `/` | `funky-jogging-bunny.trycloudflare.com` |
-Read details about URL Mapping [in the development guide](/docs/activities/development-guides#url-mapping).
+Read details about URL Mapping [in the development guide](/docs/activities/development-guides/local-development#url-mapping).
### Enable Activities
@@ -330,7 +330,7 @@ Find the first checkbox, labeled `Enable Activities`. Turn it on 🎉
When you enable Activities for your app, a [default Entry Point command](/docs/interactions/application-commands#default-entry-point-command) called "Launch" is automatically created. This [Entry Point command](/docs/interactions/application-commands#entry-point-commands) is the primary way for users to launch your Activity in Discord.
-By default, interactions with this command will result in Discord opening your Activity for the user and posting a message in the channel where it was launched from. However, if you prefer to handle the interactions in your app, you can update the [`handler` field](/docs/interactions/application-commands#entry-point-handlers) or create your own. Additional details are in the Entry Point command [documentation](/docs/interactions/application-commands#entry-point-commands) and [development guide](/docs/activities/development-guides#setting-up-an-entry-point-command).
+By default, interactions with this command will result in Discord opening your Activity for the user and posting a message in the channel where it was launched from. However, if you prefer to handle the interactions in your app, you can update the [`handler` field](/docs/interactions/application-commands#entry-point-handlers) or create your own. Additional details are in the Entry Point command [documentation](/docs/interactions/application-commands#entry-point-commands) and [development guide](/docs/activities/development-guides/user-actions#setting-up-an-entry-point-command).
### Running your Activity in Discord
@@ -346,7 +346,7 @@ Clicking on your app will launch your locally running app from inside Discord!
:::info
**Customizing your Activity**
-If you'd like to set images for your Activity, you can learn how to do that [here](/docs/activities/development-guides#setting-up-activity-metadata).
+If you'd like to set images for your Activity, you can learn how to do that [here](/docs/activities/development-guides/assets-and-metadata#setting-up-activity-metadata).
:::
We're looking pretty good so far, but we haven't wired up any Discord functionality yet. Let's do that next.
@@ -461,7 +461,7 @@ Before we call your backend activity server, we need to be aware of the Discord
For this tutorial, we are going to prefix the API call to `/api/token/` with `/.proxy`, but you can also use the SDK's `patchUrlMappings()` method to automatically prefix calls to your external resources for the proxy.
:::
-Learn more about this topic in the guides for [Constructing a Full URL](/docs/activities/development-guides#construct-a-full-url) and [Using External Resources](/docs/activities/development-guides#using-external-resources).
+Learn more about this topic in the guides for [Constructing a Full URL](/docs/activities/development-guides/networking#construct-a-full-url) and [Using External Resources](/docs/activities/development-guides/networking#using-external-resources).
### Calling your backend server from your client
@@ -515,7 +515,7 @@ async function setupDiscordSdk() {
// Retrieve an access_token from your activity's server
// Note: We need to prefix our backend `/api/token` route with `/.proxy` to stay compliant with the CSP.
// Read more about constructing a full URL and using external resources at
- // https://discord.com/developers/docs/activities/development-guides#construct-a-full-url
+ // https://discord.com/developers/docs/activities/development-guides/networking#construct-a-full-url
const response = await fetch("/.proxy/api/token", {
method: "POST",
headers: {
diff --git a/docs/activities/development-guides.mdx b/docs/activities/development-guides.mdx
index 6c7127a91f..976b799836 100644
--- a/docs/activities/development-guides.mdx
+++ b/docs/activities/development-guides.mdx
@@ -1,1199 +1,151 @@
---
sidebar_label: Development Guides
+showTOC: true
+subpages:
+ - development-guides/local-development.mdx
+ - development-guides/user-actions.mdx
+ - development-guides/mobile.mdx
+ - development-guides/layout.mdx
+ - development-guides/networking.mdx
+ - development-guides/multiplayer-experience.mdx
+ - development-guides/growth-and-referrals.mdx
+ - development-guides/assets-and-metadata.mdx
+ - development-guides/production-readiness.mdx
---
+[Home](/docs/intro) > [Activities](/docs/activities/overview) > Development Guides
+
# Activity Development Guides
These guides include suggested development practices, SDK commands, and user flows for you to consider while building your Activity. These will help to provide your users with a consistent and clear experience while interacting with your application.
## Local Development
-
+
Get up and running with a local development application.
-
+
How to launch your app on mobile and desktop Discord clients.
-
+
Configure the Discord proxy to allow network requests to necessary external endpoints.
-
+
How to use the various levels of logging while building your application.
## User Actions
-
+
Open an external link from within your app.
-
+
Open the Application Channel Invite dialog within Discord.
-
+
Open a dialog to share media from your application to a channel, DM, or GDM.
-
+
Configure a command that allows users to open your Activity from the App Launcher.
-
+
Open a dialog to enable hardware acceleration for compute-intensive applications.
## Mobile
-
+
Update your application settings to support iOS and Android.
-
+
Ensure that your app's assets fall within mobile-safe areas.
-
+
Respond to thermal state changes surfaced from iOS and Android.
## Layout
-
+
Configure and subscribe to changes in application orientation.
-
+
Subscribe to layout mode changes to update your application's user interface.
## Networking
-
+
Working with our Activity Proxy
-
+
Generate a full URL when working with network requests.
-
+
Allow network requests to external resources from inside the Discord proxy.
-
+
Keep things safe and secure in your Activity.
## Multiplayer Experience
-
+
Managing instances to ensure users join the same instance as their friends.
-
+
Use the SDK to fetch the users currently connected to an instance.
-
+
Retrieve and render the usernames and avatars of users connected to your application.
-
+
Validating activity sessions are via a Discord client before adding them to an instance's session.
## Growth and Referrals
-
+
Encourage your users to share links to your activity by adding tracking and offering rewards for engagement.
-
+
For off-platform sharing of rewards, promotions, or limited time experiences.
-
+
An API to be able to generate ephemeral links with a customizable embed.
## Assets & Metadata
-
+
Best practices for configuring how your application shows up in Discord.
-
+
Best practices for configuring how your application shows up in Discord.
## Production Readiness
-
+
Manage asset caching in your application and the Discord Activity proxy.
-
+
Stay within rate limits to keep the fun going in your Activity.
-
+
Network routing considerations when preparing your Activity for production use.
-
+
Future-proof your application and support new commands as they become available in the SDK.
-
----
-### Run Your Application Locally
-
-It is possible to load your application via a localhost port or other unique URL. This URL must support an HTTPS connection to load on the web/desktop Discord app (HTTPS is not required for mobile). The downside to this flow is that your application's network traffic will not pass through Discord's proxy, which means any requests made by the application will need to use a full URL instead of a ["mapped"](/docs/activities/development-guides#url-mapping) URL.
-
-To run your locally hosted application, follow the instructions for [Launching your Application from the Discord Client](/docs/activities/development-guides#launch-your-application-from-the-discord-client) and set the Application URL Override to the address of your application's web server.
-
-### Running Your Application Through A Network Tunnel
-
-Although it is possible to test your application locally, we recommend developing and testing against the Discord proxy. This is helpful to make sure all URLs behave as expected before your application runs in production. One technique to enable testing locally against the proxy is to use a network tunneling tool, such as [cloudflared](https://github.com/cloudflare/cloudflared#installing-cloudflared). A typical pattern is for each developer to have their own "development-only" application. To set up a local environment to run through Discord's proxy, you will need to do the following:
-
-1. Create a new application in the Discord Developer portal.
-2. Enable Activities for your app.
-3. Set up the application's [URL mapping](/docs/activities/development-guides#url-mapping).
-4. Locally, spin up your web server.
-5. Install and run a tunnel solution, such as [cloudflared](https://github.com/cloudflare/cloudflared#installing-cloudflared). You will point it to your local web server.
-
-:::info
-Your web server can be HTTP and your network tunnel can upgrade the connection to HTTPS.
-:::
-
-If using cloudflared, you will run the following command, replace `3000` with your web server's port.
-
-```
-cloudflared tunnel --url http://localhost:3000
-```
-
-Once you run this command, you will receive your publicly accessible network tunnel address from cloudflared.
-
-```
-Your quick Tunnel has been created! Visit it at (it may take some time to be reachable):
-https://funky-jogging-bunny.trycloudflare.com
-```
-
-In the Discord Developer Portal, update the Application URL mapping for `/` url to `funky-jogging-bunny.trycloudflare.com` to match your network tunnel address and save your changes.
-
-
-
-:::warn
-If you do not own the URL that you are using to host the application (i.e. ngrok's free tier), someone else could claim that domain and host a malicious site in its place. Please be aware of these risks, and if you have to use a domain you do not own, be sure to reset your URL mapping when you are done using the tunnel.
-:::
-
-Follow the instructions for [Launching your Application from the Discord Client](/docs/activities/development-guides#launch-your-application-from-the-discord-client). Application URL Override should not be enabled.
-
-### Running Your Application In Production
-
-The flow for setting up your production application is very similar:
-
-1. If not made yet, create a new application.
-2. Enable Activities for your app.
-3. Set up the application's [URL Mapping](/docs/activities/development-guides#url-mapping). The URL for your application's html should be set to the `/` route.
-4. Follow the instructions for [Launching your Application from the Discord Client](/docs/activities/development-guides#launch-your-application-from-the-discord-client)). Application URL Override should not be enabled.
-
-This application now uses the same configuration it will use once it is fully published ✨.
-
-
-
----
-
-### Launch your application from the Discord Client
-
-You will be able to see and launch all activities owned by you or any teams you are a member of via the Developer Activity Shelf. One caveat is that the activity will not be shown on the current platform (web/ios/android) unless you have checked your platform in `Settings/Supported Platforms` on the developer portal.
-
-To see you app inside of Discord in the Activity Shelf:
-
-#### Web
-
-1. Select ⚙️User Settings > App Settings > Advanced and toggle on `Developer Mode`
-3. Close the settings window and enter a voice channel.
-4. From either the RTC Panel or the Center Control Tray, click on the "Rocket Button" to open the Activity shelf. You should now see all of the same applications that you have access to in the developer portal. Note: The shelf will only include applications which have been flagged as "Embedded".
-6. Click on an activity to launch it!
-
-#### Mobile
-
-1. From your User Profile, select Appearance, and then toggle "On" `Developer Mode`
-2. Enter a voice channel
-5. Click on an activity to launch it!
-
----
-
-### URL Mapping
-
-Activities in Discord are "sandboxed" via a Discord proxy. This is done to hide the users' IP addresses, your application's IP addresses, and to block URLs from known malicious endpoints. As an application owner, you can configure the proxy to allow network requests to external endpoints.
-
-Because your application is "sandboxed", it will be unable to make network requests to external URLs. Let's say you want request `https://some-api.com`. To enable reaching this url from inside your application, you will create a new url mapping, with the `PREFIX` set to
-`/api` and `TARGET` set to `some-api.com`. Now you can make requests to `/api` from inside of your application, which will be forwarded, via Discord's proxy to `some-api.com`.
-
-#### How to set a URL Mapping
-
-To add or modify your application's URL mappings, click on `Activities -> URL Mappings` and set the prefix and target values for each mapping as needed.
-
-
-
-#### Prefix/Target formatting rules
-
-- URL mappings can utilize any url protocol, (https, wss, ftp, etc...), which is why the URL target should not include a protocol. For example, for a URL target, do not put `https://your-url.com`, instead, omit `https://` and use `your-url.com`.
-- Parameter matching can be used to help map external domain urls. For example, if an external url has many subdomains, such as `foo.google.com`, `bar.google.com`, then you could use the following mapping:
- | PREFIX | TARGET |
- |-----------------------|--------------------------|
- | `/google/{subdomain}` | `{subdomain}.google.com` |
-- Targets must point to a directory; setting a target to a file (e.g. `example.com/index.html`) is unsupported and may lead to unexpected behavior.
-- Because of how URL globbing works, if you have multiple prefix urls with the same initial path, you must place the shortest of the prefix paths last in order for each url mapping to be reachable. For example, if you have `/foo` and `/foo/bar`, you must place the url `/foo/bar` before `/foo` or else the mapping for `/foo/bar` will never be reached.
-
-| ✅ DO | ❌ DON'T |
-|-------------------------------------------------------------|-----------------------------------------------------------------|
-| Requests mapped correctly | Requests to /foo/bar will incorrectly be sent to `foo.com` |
-|  |  |
-
-#### Exceptions
-
-The aforementioned "sandbox" is enforced by a [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). We have some notable exceptions to our CSP, meaning application clients may make requests to these URLs without hitting the proxy and therefore without establishing mappings. Notable exceptions include:
-
-- `https://discord.com/api/`
-- `https://canary.discord.com/api/`
-- `https://ptb.discord.com/api/`
-- `https://cdn.discordapp.com/attachments/`
-- `https://cdn.discordapp.com/avatars/`
-- `https://cdn.discordapp.com/icons/`
-- `https://media.discordapp.net/attachments/`
-- `https://media.discordapp.net/avatars/`
-- `https://media.discordapp.net/icons/`
-
----
-
-### Logging
-
-By default, the SDK will send any console `log`, `warn`, `error`, `info`, and `debug` events triggered by your app to the Discord application.
-
-#### Viewing Logs on Desktop
-
-Desktop logs are viewable through the console tab inside a browser's Developer Tools. See the [Troubleshooting Console Log Errors](https://support.discord.com/hc/en-us/articles/115001239472-Troubleshooting-Console-Log-Errors) support article for more information.
-
-The Public Test Build (PTB) Discord client also allows inspecting your logs from the `View -> Developer -> Toggle Developer Tools` menu. It can be downloaded at [https://discord.com/downloads](https://discord.com/downloads).
-
-#### Viewing Logs on Mobile
-
-Mobile logs are viewable via the `Debug Logs` option inside User Settings on the mobile App. It is only discoverable when you have `Developer Mode` enabled.
-
-1. On the bottom navigation, tap on your avatar and then the gear icon to open your `User Settings`.
-2. Tap `Appearance`.
-3. Slide the `Developer Mode` toggle to ON.
-4. The `Debug Logs` option will be available under the `DEV ONLY` section.
-
-#### Filtering for Application Logs
-
-Inside the Debug Logs view, you can search for your own application logs with the possible keywords:
-
-- `RpcApplicationLogger`
-- Your Application ID
-
-Each log line is formatted as: `[RpcApplicationLogger] - message`
-
-The first section of Debug Logs are not your application logs but Discord specific app startup info which is not relevant to your application.
-
-When you scroll down the page, your application logs should be visible.
-
-
-
-#### Sharing Application Logs from Mobile
-
-With `Developer Mode` enabled, you can share your application logs from within a Voice Channel.
-
-1. In the voice channel, swipe from the bottom to see the expanded voice controls. Tap on `Share Application Logs`.
-2. You'll be presented with a native share sheet where you can save the logs to a file or share it as a message.
-
-#### Disabling Logging
-
-If you do not want logs to be forwarded to the browser, you can disable it with the optional configuration object.
-
-```javascript
-import {DiscordSDK} from '@discord/embedded-app-sdk';
-const discordSdk = new DiscordSDK(clientId, {
- disableConsoleLogOverride: true,
-});
-```
-
-#### Forwarding Log Messages
-
-You can forward specific log messages via the SDK command `captureLog` as shown below.
-
-```javascript
-import {DiscordSDK} from '@discord/embedded-app-sdk';
-const discordSdk = new DiscordSDK(clientId);
-await discordSdk.ready();
-discordSdk.commands.captureLog({
- level: 'log',
- message: 'This is my log message!',
-});
-```
-
----
-
-### Open External Link
-
-Since Activities are sandboxed, your app will need to perform a command in order for users to launch any external links. Users will be prompted inside Discord whether or not they want to open the external link.
-
-#### Usage
-
-```javascript
-import {DiscordSDK} from '@discord/embedded-app-sdk';
-const discordSdk = new DiscordSDK(clientId);
-await discordSdk.ready();
-// Once the sdk has established the connection with the discord client, external
-// links can be launched
-discordSdk.commands.openExternalLink({
- url: 'https://google.com',
-});
-```
-
-#### User Experience
-
-
-
-Users will see a modal inside the Discord app notifying them whether or not they want to proceed. By clicking **_Trust this Domain_**, users will not see a modal for that specific domain again.
-
----
-
-### Open Invite Dialog
-
-Getting an Application Channel Invite, as outlined in [these docs](https://discord.com/developers/docs/resources/invite#get-invite), is not granted by any OAuth scopes. Nonetheless, the `openInviteDialog` command is available via the SDK. This command opens the Application Channel Invite UI within the discord client without requiring additional OAuth scopes.
-
-This command returns an error when called from DM (Direct Message) contexts, so should only be called in Guild Voice or Text Channels. Similarly, this command returns an error if the user has invalid permissions for the channel, so using `getChannelPermissions` (requires OAuth scope `'guilds.members.read'`) is highly recommended.
-
-#### Usage
-
-```javascript
-import {DiscordSDK, Permissions, PermissionUtils} from '@discord/embedded-app-sdk';
-const discordSdk = new DiscordSDK(clientId);
-await discordSdk.ready();
-
-try {
- const {permissions} = await discordSdk.commands.getChannelPermissions();
- if (PermissionUtils.can(Permissions.CREATE_INSTANT_INVITE, permissions)) {
- await discordSdk.commands.openInviteDialog();
- // successfully opened dialog
- } else {
- console.warn('User does not have CREATE_INSTANT_INVITE permissions');
- }
-} catch (err) {
- // failed to fetch permissions or open dialog
- console.warn(err.message);
-}
-```
-
-User Experience
-
-
-
-Users will see a modal inside the Discord app allowing them to send an invite to a channel, friend, or copy an invite link to share manually.
-
----
-
-### Open Share Moment Dialog
-
-The easiest way for an application to share media to a channel or DM is to use the `openShareMomentDialog` command. This command accepts a Discord CDN `mediaUrl` (eg `https://cdn.discordapp.com/attachments/...`) and opens a dialog on the discord client that allows the user to select channels, DMs, and GDMs to share to. This requires no additional OAuth scopes, but does require the application to be authenticated.
-
-Since `mediaUrl` must be a Discord CDN URL, it is encouraged to use the activities attachment API endpoint (`discord.com/api/applications/${applicationId}/attachment`) to create an ephemeral CDN URL. This endpoint accepts bearer tokens for any scopes, so it can be called from the application client using the authorized user's bearer token. The endpoint returns a serialized attachment, which includes a `url` attribute, which should then be passed to the DiscordSDK command as `mediaUrl`.
-
-#### Usage
-
-```javascript
-import {discordSdk} from './wherever-you-initialize-your-sdk';
-import {accessToken} from './wherever-you-store-your-access-token';
-
-// some image
-const imageURL = 'https://i.imgur.com/vaSWuKr.gif';
-
-// get image data
-const response = await fetch(imageURL);
-const blob = await response.blob();
-const mimeType = blob.type;
-
-// image data as buffer
-const buf = await blob.arrayBuffer();
-
-// image as file
-const imageFile = new File([buf], 'example.gif', {type: mimeType});
-
-const body = new FormData();
-body.append('file', imageFile);
-
-const attachmentResponse = await fetch(`${env.discordAPI}/applications/${env.applicationId}/attachment`, {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${accessToken}`,
- },
- body,
-});
-const attachmentJson = await attachmentResponse.json();
-
-// mediaUrl is an ephemeral Discord CDN URL
-const mediaUrl = attachmentJson.attachment.url;
-
-// opens dialog in Discord client
-await discordSdk.commands.openShareMomentDialog({mediaUrl});
-```
-
-User Experience
-
-
-
----
-
-### Setting Up an Entry Point Command
-
-An [Entry Point command](/docs/interactions/application-commands#entry-point-commands) is required for users to be able to launch your Activity from the [App Launcher menu](https://support.discord.com/hc/articles/21334461140375-Using-Apps-on-Discord#h_01HRQSA6C8TRHS722P1H3HW1TV) in Discord.
-
-When you enable Activities in your [app's settings](http://discord.com/developers/applications), a [default Entry Point command](/docs/interactions/application-commands#default-entry-point-command) is automatically created for your app. The default Entry Point command will use the `DISCORD_LAUNCH_ACTIVITY` (`2`) [handler type](/docs/interactions/application-commands#application-command-object-entry-point-command-handler-types), which means that Discord automatically launches your Activity for the user and posts a follow-up message into the channel where it was launched from.
-
-If you want to handle sending messages yourself, you can update the handler to be `APP_HANDLER` (`1`). Details about Entry Point command handlers is in the [Entry Point command documentation](/docs/interactions/application-commands#entry-point-handlers).
-
-#### Customizing the Default Entry Point Command
-
-Entry Point commands can be customized in the same way as other [commands](/docs/interactions/application-commands). Since Entry Point commands can only be [global](/docs/interactions/application-commands#making-a-global-command), you'll use the HTTP endpoints for global commands:
-- **Edit your existing Entry Point command's name or details** using the [Edit Global Application Command](/docs/interactions/application-commands#edit-global-application-command) endpoint. If you don't know the ID for your app's Entry Point command, use the [Get Global Application Commands](/docs/interactions/application-commands#get-global-application-commands) endpoint to retrieve it.
-- **Make a different (option-less) command your Entry Point command** by updating its [command `type`](/docs/interactions/application-commands#application-command-object-application-command-types) to `PRIMARY_ENTRY_POINT` (type `4`). Your app can only have one Entry Point command, so if your app already has one, you must first [delete](/docs/interactions/application-commands#delete-global-application-command) it or [update](/docs/interactions/application-commands#edit-global-application-command) its [command `type`](/docs/interactions/application-commands#application-command-object-application-command-types).
-
-#### Creating an Entry Point Command
-
-To create a new Entry Point command, you can call the [Create Global Application Command](/docs/interactions/application-commands#create-global-application-command) endpoint and set the [command `type`](/docs/interactions/application-commands#application-command-object-application-command-types) to `PRIMARY_ENTRY_POINT` (type `4`).
-
-Your command payload may look something like this:
-
-```json
-{
- "name": "launch",
- "description": "Launch Realms of Wumpus",
- // PRIMARY_ENTRY_POINT is type 4
- "type": 4,
- // DISCORD_LAUNCH_ACTIVITY is handler value 2
- "handler": 2,
- // integration_types and contexts define where your command can be used (see below)
- "integration_types": [0, 1],
- "contexts": [0, 1, 2]
-}
-```
-
-In addition to the `type` and `handler` values, the command payload includes `integration_types` and `contexts` which let you configure when and where your command can be used:
-- `integration_types` defines the [installation contexts](/docs/resources/application#installation-context) where your command is available (to a server, to a user's account, or both). If you don't set `integration_types` when creating a command, it will default to your app's [currently-supported installation contexts](/docs/resources/application#setting-supported-installation-contexts).
-- `contexts` defines the [interaction contexts](/docs/interactions/receiving-and-responding#interaction-object-interaction-context-types) where a command can be run in Discord (in a server, in a DM with your app, and/or in DMs and Group DMs with other users).
-
-Details about both of these fields are in the [command contexts](/docs/interactions/application-commands#contexts) documentation.
-
----
-
-### Encourage Hardware Acceleration
-
-Activities that are compute intensive may benefit from encouraging users to enable hardware acceleration. When an application invokes the `encourageHardwareAcceleration` command the current status of the setting will be returned and the user will be prompted to update the setting, if applicable.
-
-Users will see a modal inside the Discord app if Hardware Acceleration is disabled, encouraging them to change the setting. By clicking **Don't show me this again** they will not see the modal for _any application_ on this device again.
-
-#### Best Practices
-
-Switching the Hardware Acceleration setting causes the Discord client to quit and re-launch, so it is best practice to invoke this command as soon as possible, so users do not begin the experience of an application before restarting. Ideally, this is immediately after `await discordSdk.ready()`.
-
-#### Usage
-
-```javascript
-import {DiscordSDK} from '@discord/embedded-app-sdk';
-const discordSdk = new DiscordSDK(clientId);
-await discordSdk.ready();
-const {enabled} = await discordSdk.commands.encourageHardwareAcceleration();
-console.log(`Hardware Acceleration is ${enabled === true ? 'enabled' : 'disabled'}`);
-```
-
-#### User Experience
-
-
-
-
----
-
-### Supported Platforms: Web, iOS, Android
-
-By default, your Activity will be launchable on web/desktop. To enable or disable support for Web/iOS/Android, do the following:
-
-- Visit the developer portal
-- Select your application
-- Select `Activities` -> `Settings` in the left-side of the developer portal, or visit `https://discord.com/developers//embedded/settings`
-- From check the appropriate checkboxes in the developer portal, and save your changes
-
-
-
----
-
-### Mobile Safe Areas
-
-As an example, you can define your safe area insets as below in CSS:
-
-```
-:root {
- --sait: var(--discord-safe-area-inset-top, env(safe-area-inset-top));
- --saib: var(--discord-safe-area-inset-bottom, env(safe-area-inset-bottom));
- --sail: var(--discord-safe-area-inset-left, env(safe-area-inset-left));
- --sair: var(--discord-safe-area-inset-right, env(safe-area-inset-right));
-}
-```
-
-This prefers the `--discord-safe-area-inset-*` variable and will fallback to the env values for iOS + any local dev testing that is done outside of Discord.
-
-You can then reference these values:
-
-```
-body {
- padding-left: var(--sail);
- padding-right: var(--sair);
- padding-top: var(--sait);
- padding-bottom: var(--saib);
-}
-```
-
----
-
-### Mobile Thermal States
-
-You may need to respond to thermal state changes using recommendations from [thermal states surfaced by mobile devices](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/RespondToThermalStateChanges.html) to improve the user experience.
-
-Discord's Embedded App SDK provides an abstraction over [Apple's thermal state APIs](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/RespondToThermalStateChanges.html) and [Android's thermal state APIs](https://source.android.com/docs/core/power/thermal-mitigation#thermal-api).
-
-Here's how Discord's abstraction maps to Apple's thermal states and Android's thermal states.
-
-```javascript
-enum ThermalState {
- NOMINAL = 0, // maps to "nominal" on iOS and "none" on Android
- FAIR = 1, // maps to "fair" on iOS and "light" / "moderate" on Android
- SERIOUS = 2, // maps to "serious" on iOS and "severe" on Android
- CRITICAL = 3, // maps to "critical" on iOS and "critical" / "emergency" / "shutdown" on Android
-}
-```
-
-The Embedded App SDK allows developers to subscribe to these thermal state changes.
-
-```javascript
-const handleThermalStateUpdate = (update: {thermal_state: number}) => {
- switch (thermalState) {
- case Common.ThermalStateTypeObject.NOMINAL:
- ...
- case Common.ThermalStateTypeObject.FAIR:
- ...
- case Common.ThermalStateTypeObject.SERIOUS:
- ...
- case Common.ThermalStateTypeObject.CRITICAL:
- ...
- default:
- ...
- }
-}
-
-discordSdk.subscribe('THERMAL_STATE_UPDATE', handleThermalStateUpdate);
-```
-
-Discord will publish the current thermal state upon event subscription, and it will also publish any thermal state changes that happen afterward.
-
-:::info
-On Android devices, the thermal state updates will only be available on Android 10 and higher.
-:::
-
----
-
-### Application Orientation
-
-#### Locking Application Orientation
-
-This SDK provides APIs for locking the application to specific orientations. The possible lock states are `UNLOCKED`, `PORTRAIT`, and `LANDSCAPE`. `lock_state` is the default lock state, and it affects the app orientation when the application is focused. `picture_in_picture_lock_state` determines the PIP aspect ratio, and `grid_lock_state` determines the grid tile aspect ratio for the application. When `picture_in_picture_lock_state` is not set, the application PIP falls back to `lock_state` to determine the aspect ratio. When `grid_lock_state` is not set, the application grid tile falls back to `picture_in_picture_lock_state` to determine its aspect ratio, and if `picture_in_picture_lock_state`is not set, it uses `lock_state`.
-
-Calling `setOrientationLockState` with an `undefined` or omitted value for `picture_in_picture_lock_state` or `grid_lock_state` will not change the corresponding lock states for the application. Calling `setOrientationLockState` with a null value for `picture_in_picture_lock_state` or `grid_lock_state` will clear the application's corresponding lock states such that those layout modes will use the fallback lock states.
-
-```javascript
-import {DiscordSDK, Common} from '@discord/embedded-app-sdk';
-const discordSdk = new DiscordSDK(clientId);
-await discordSdk.ready();
-
-// Set a default lock state
-discordSdk.commands.setOrientationLockState({lock_state: Common.OrientationLockStateTypeObject.LANDSCAPE});
-
-// or set both a default lock state and a picture-in-picture lock state
-discordSdk.commands.setOrientationLockState({
- lock_state: Common.OrientationLockStateTypeObject.PORTRAIT,
- picture_in_picture_lock_state: Common.OrientationLockStateTypeObject.LANDSCAPE,
- grid_lock_state: Common.OrientationLockStateTypeObject.LANDSCAPE,
-});
-```
-
-#### Configuring Default Orientation Lock State Through the Developer Portal
-
-It's also possible to configure an application with a default orientation lock state via the Developer Portal. Using this method, the Discord app will apply the orientation lock when launching the application before the SDK has been initialized. This can create a smoother application launch flow where the application starts in the correct orientation rather than switching to the correct orientation after some delay after the application requests an orientation lock via the SDK. The Developer Portal supports setting a different default orientation lock states for phones versus tablets.
-
-
-
-#### Subscribing to Screen Orientation Updates
-
-To listen to the screen orientation (which is sometimes different from the physical device orientation), subscribe to the `ORIENTATION_UPDATE` event. Discord will publish the current orientation upon event subscription, and it'll also publish any orientation changes that happen afterward.
-
-```javascript
-const handleOrientationUpdate = (update: {screen_orientation: number}) => {
- switch (update.screen_orientation) {
- case Common.OrientationTypeObject.PORTRAIT:
- ...
- case Common.OrientationTypeObject.LANDSCAPE:
- ...
- default:
- ...
- }
-}
-
-discordSdk.subscribe('ORIENTATION_UPDATE', handleOrientationUpdate);
-```
-
----
-
-### Application Layout Mode
-
-There are three layout modes that an application can be in: focused, picture-in-picture (PIP), or grid mode. Activities can subscribe to the layout mode to determine when to optionally change their layouts to optimize for each layout mode. Old Discord clients only support the `ACTIVITY_PIP_MODE_UPDATE` event, while new Discord clients support both `ACTIVITY_PIP_MODE_UPDATE` and `ACTIVITY_LAYOUT_MODE_UPDATE`. Use `subscribeToLayoutModeUpdatesCompat` and `unsubscribeFromLayoutModeUpdatesCompat` to subscribe to both events with backward compatibility for old Discord clients that only support `ACTIVITY_PIP_MODE_UPDATE`. Here's an example using React:
-
-```javascript
-export default function LayoutMode() {
- const handleLayoutModeUpdate = React.useCallback((update: {layout_mode: number}) => {
- ...
- }, []);
-
- React.useEffect(() => {
- discordSdk.subscribeToLayoutModeUpdatesCompat(handleLayoutModeUpdate);
- return () => {
- discordSdk.unsubscribeFromLayoutModeUpdatesCompat(handleLayoutModeUpdate);
- };
- }, [handleLayoutModeUpdate]);
-}
-```
-
----
-
-### Activity Proxy Considerations
-
-All network traffic is routed through the Discord Proxy for various security reasons.
-
-Under the hood we utilize Cloudflare Workers, which brings some restrictions, outlined below.
-
-#### WebTransport
-
-While we currently only support websockets, we're working with our upstream providers to enable WebTransport.
-
-#### WebRTC
-
-WebRTC is not supported.
-
-Other guides are available in this [Networking](/docs/activities/development-guides#networking) section for using external network resources and constructing a full url versus relative urls.
-
----
-
-### Construct A Full URL
-
-There are scenarios where instead of using a relative url (`/path/to/my/thing`) you may want or need to reference the full url when making a network request. The URL is a combination of the following
-
-1. The protocol you wish to use
-2. Your application's client id
-3. The discord proxy domain
-4. The `/.proxy` path prefix
-5. Whatever you need to list
-
-Here's an example of how to build a full url, using the URL constructor:
-
-```javascript
-const protocol = `https`;
-const clientId = '';
-const proxyDomain = 'discordsays.com';
-const resourcePath = '/foo/bar.jpg';
-const url = new URL(`${protocol}://${clientId}.${proxyDomain}/.proxy${resourcePath}`);
-```
-
-In other words, given an application client id of `12345678`
-| Relative Path | Full Path |
-|---------------|-----------------------------------------------------|
-| /foo/bar.jpg | https://12345678.discordsays.com/.proxy/foo/bar.jpg |
-
----
-
-### Using External Resources
-
-Activities in Discord are "sandboxed" via a Discord proxy. This is done to hide the users' IP addresses as well as block URLs from known malicious endpoints. To achieve this, the Discord Developer Portal has a section for [configuring URL Mappings](/docs/activities/development-guides#url-mapping) for your application.
-
-One edge-case of URL mappings is that third-party NPM modules or other resources may reference external (non-sandboxed) urls.
-
-For example, if your application has an npm module that attempts to make an http request to https://foo.library.com, the request will fail with a `blocked:csp` error.
-
-To get around this limitation there are several options to consider:
-
-- Fork the library (to use mapped urls)
-- Utilize a post-install utility such as [patch-package](https://www.npmjs.com/package/patch-package)
-- Use our Embedded App SDK's `patchUrlMappings` API
-
-In the above scenario, we recommend using the `patchUrlMappings` API, as it will allow a smooth transition from the non-sandboxed dev environment to the production environment.
-
-This method call takes an array of "mappings" which will transform any external network requests to the mappings you've defined.
-
-See the example below:
-
-- In this example, imagine you have a third-party library which makes an HTTP request to foo.com
-- In the developer portal, create a mapping like this: `/foo` -> `foo.com`
-- Then in your code, when initializing the SDK, you will make a function call.
-
-
-```javascript
-import {patchUrlMappings} from '@discord/embedded-app-sdk';
-const isProd = process.env.NODE_ENV === 'production'; // Actual dev/prod env check may vary for you
-
-async function setupApp() {
- if (isProd) {
- patchUrlMappings([{prefix: '/foo', target: 'foo.com'}]);
- }
- // start app initialization after this....
-}
-```
-
-:::info
-Note: `patchUrlMappings` is modifying your browser's `fetch`, `WebSocket`, and `XMLHttpRequest.prototype.open` global variables. Depending on the library, you may see side effects from using this helper function. It should be used only when necessary.
-:::
-
----
-
-### Security Considerations
-
-#### Trusting Client Data
-
-Do not trust data coming from the Discord client as truth. It's fine to use this data in your application locally, but assume any data coming from the Discord Client could be falsified. That includes data about the current user, their nitro status, their current channel, etc. If you need this information in a trusted manner, contact Discord API directly from your application's server, with the user token you received from completing the OAuth2 flow.
-
-Furthermore, data coming from the Discord client is not sanitized beforehand. Things like usernames and channel names are arbitrary user input. Make sure to sanitize these strings or use `.textContent` (for example) to display them safely in your UI.
-
-#### Using Cookies
-
-To set a cookie for your activity to use in network requests through the proxy, make sure the cookie's domain matches your app's full `{clientId}.discordsays.com` domain. You will also need to explicitly set `SameSite=None Partitioned` on the cookie. `SameSite=None` is needed as browsers refuse to store or send cookies with higher restriction levels for any navigation within an iframe. `Partitioned` then limits the use of that cookie to only Discord's iframes.
-
-Rest assured: other activities will not be able to make requests with your activity's cookie, thanks to the Content Security Policy (CSP) limiting requests only to your own app's proxy.
-
----
-
-### Activity Instance Management
-
-When a user clicks "Join Application", they expect to enter the same application that their friends are participating in. Whether the application is a shared drawing canvas, board game, collaborative playlist, or first-person shooter; the two users should have access to the same shared data. In this documentation, we refer to this shared data as an **application instance**.
-
-
-
-The Embedded App SDK allows your app to talk bidirectionally with the Discord Client. The `instanceId` is necessary for your application, as well as Discord, to understand which unique instance of an application it is talking to.
-
-#### Using instanceId
-
-The `instanceId` attribute is available as soon as the SDK is constructed, and does not require the SDK to receive a `ready` payload from the Discord client.
-
-```javascript
-import {DiscordSDK} from '@discord/embedded-app-sdk';
-const discordSdk = new DiscordSDK(clientId);
-// available immediately
-const instanceId = discordSdk.instanceId;
-```
-
-The `instanceId` should be used as a key to save and load the shared data relevant to an application. This ensures that two users who are in the same application instance have access to the same shared data.
-
-##### Semantics of instanceId
-
-Instance IDs are generated when a user launches an application. Any users joining the same application will receive the same `instanceId`. When all the users of an application in a channel leave or close the application, that instance has finished its lifecycle, and will not be used again. The next time a user opens the application in that channel, a new `instanceId` will be generated.
-
----
-
-### Instance Participants
-
-Instance Participants are any Discord user actively connected to the same Application Instance. This data can be fetched or subscribed to.
-
-```javascript
-import {DiscordSDK, Events, type Types} from '@discord/embedded-app-sdk';
-
-const discordSdk = new DiscordSDK('...');
-await discordSdk.ready();
-
-// Fetch
-const participants = await discordSdk.commands.getInstanceConnectedParticipants();
-
-// Subscribe
-function updateParticipants(participants: Types.GetActivityInstanceConnectedParticipantsResponse) {
- // Do something really cool
-}
-discordSdk.subscribe(Events.ACTIVITY_INSTANCE_PARTICIPANTS_UPDATE, updateParticipants);
-// Unsubscribe
-discordSdk.unsubscribe(Events.ACTIVITY_INSTANCE_PARTICIPANTS_UPDATE, updateParticipants);
-```
-
----
-
-### Render Avatars and Names
-
-Check out detailed documentation on where and how Discord stores common image assets [here](https://discord.com/developers/docs/reference#image-formatting-cdn-endpoints).
-
-Here's a basic example for retrieving a user's avatar and username
-
-```javascript
-// We'll be referencing the user object returned from authenticate
-const {user} = await discordSdk.commands.authenticate({
- access_token: accessToken,
-});
-
-let avatarSrc = '';
-if (user.avatar) {
- avatarSrc = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=256`;
-} else {
- const defaultAvatarIndex = (BigInt(user.id) >> 22n) % 6n;
- avatarSrc = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarIndex}.png`
-}
-
-const username = user.global_name ?? `${user.username}#${user.discriminator}`;
-
-// Then in your HTML/JSX/etc...
-
-{username}
-```
-
-#### Rendering guild-specific avatars and nicknames
-
-In order to retrieve a user's guild-specific avatar and nickname, your application must request the `guilds.members.read` scope. Note, this only grants the information for that instance of the application's user. To display the guild-specific avater/nickname for all application users, any info retrieved from `guilds.members.read` scope'd API calls must be shared via your application's server.
-
-Here's an example of how to retrieve the user's guild-specific avatar and nickname:
-
-```javascript
-// We'll be referencing the user object returned from authenticate
-const {user} = await discordSdk.commands.authenticate({
- access_token: accessToken,
-});
-
-// When using the proxy, you may instead replace `https://discord.com` with `/discord`
-// or whatever url mapping you have chosen via the developer portal
-fetch(`https://discord.com/api/users/@me/guilds/${discordSdk.guildId}/member`, {
- method: 'GET',
- headers: {
- Authorization: `Bearer ${accessToken}`,
- },
-})
- .then((response) => {
- return response.json();
- })
- .then((guildsMembersRead) => {
- let guildAvatarSrc = '';
- // Retrieve the guild-specific avatar, and fallback to the user's avatar
- if (guildsMembersRead?.avatar) {
- guildAvatarSrc = `https://cdn.discordapp.com/guilds/${discordSdk.guildId}/users/${user.id}/avatars/${guildsMembersRead.avatar}.png?size=256`;
- } else if (user.avatar) {
- guildAvatarSrc = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=256`;
- } else {
- const defaultAvatarIndex = (BigInt(user.id) >> 22n) % 6n;
- avatarSrc = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarIndex}.png`;
- }
-
- // Retrieve the guild-specific nickname, and fallback to the username#discriminator
- const guildNickname = guildsMembersRead?.nick ?? (user.global_name ?? `${user.username}#${user.discriminator}`);
- });
-```
-
-This example is being done entirely on the client, however, a more common pattern is to instead, do the following:
-
-- Store the user's access token on the application server
-- Retrieve the user's guild-specific avatar and nickname via the application's server
-- Serve all of the application's avatar/nicknames via the application's server
-
----
-
-### Prompting Users to Share Incentivized Links
-
-Incentivized sharing can help grow your Activity through network effects. You can use links in several different ways such as:
-
-- **Referral links.** Users can copy referral links inside your Activity, which include their Discord user ID (`https://discord.com/activities/?referrer_id=123456789`), and they can send to their friends. If their friend accepts and starts playing your game, then you gift the referrer something inside your game.
-- **Promotions.** You can run a temporary promotion on social media, where you offer a reward if they start playing now. Share a custom link on your social media (`https://discord.com/activities/?custom_id=social012025` ). Anyone who clicks that specific link receives something inside your game.
-- **Social deep-links.** Currently, when users launch an Activity, they all land in the same place. Instead, you can start deep-linking to contextually relevant points in your game. For example, user A can copy a link inside your Activity for engaging other users (`https://discord.com/activities/?referrer_id=123456789&custom_id=visit-location`), and sends the link to their friends in a DM or channel. Then, user B who clicks the link gets taken directly to user A’s location.
-- **Turn-based deep-links.** When you send an “it’s your turn” DM to a user, you can include a link which takes them directly to the right game instance and the turn they need to take.
-- **Affiliate marketing.** You can work with affiliates (influencers, companies, etc) to advertise your game to their followings, and reward them via a custom link (`https://discord.com/activities/?custom_id=influencer1`). Then, for every user that starts playing because of said influencer, you can then pay out to the influencer.
-- **Source attribution.** You can use the `custom_id` parameter to figure out how much traffic you’re getting from different marketing sources.
-
-This guide covers implementing a referral link which will feature a reward system for users who share links and those who click them.
-
-#### Implementation Overview
-
-1. Create and track an incentivized link for a promotional campaign, then prompt users to share the link
-2. Handle incoming referrals and grant valid rewards
-
-#### Sharing Links
-
-When implementing sharing, you'll need to:
-1. Generate a unique ID for tracking the promotion
-2. Call the [`shareLink`](/docs/developer-tools/embedded-app-sdk#sharelink) command
-3. Track the share attempt
-
-```javascript
-// Generate a unique ID for this promotion
-// This could be per-campaign, per-user, or per-share depending on your needs
-const customId = await createPromotionalCustomId();
-
-try {
- const { success } = await discordSdk.commands.shareLink({
- message: 'Click this link to redeem 5 free coins!',
- custom_id: customId,
- });
-
- if (success) {
- // Track successful share for analytics/limiting
- await trackSuccessfulShare(customId);
- }
-} catch (error) {
- // Handle share failures appropriately
- console.error('Failed to share link:', error);
-}
-```
-
-#### Handling Incoming Referrals
-
-When a user clicks a shared link, your activity will launch with referral data available through the SDK:
-
-```javascript
-// Early in your activity's initialization
-async function handleReferral() {
- // Validate the referral data
- if (!discordSdk.customId || !discordSdk.referrerId) {
- return;
- }
-
- try {
- // Verify this is a valid promotion and hasn't expired
- const promotion = await validatePromotion(discordSdk.customId);
- if (!promotion) {
- console.log('Invalid or expired promotion');
- return;
- }
-
- // Prevent self-referrals
- if (discordSdk.referrerId === currentUserId) {
- console.log('Self-referrals not allowed');
- return;
- }
-
- // Grant rewards to both users
- await grantRewards({
- promotionId: discordSdk.customId,
- referrerId: discordSdk.referrerId,
- newUserId: currentUserId
- });
- } catch (error) {
- console.error('Failed to process referral:', error);
- }
-}
-```
-
-#### Link Sharing Best Practices
-
-- Generate unique, non-guessable `customId`s
-- Track and validate referrals to prevent abuse
-- Handle edge cases like expired promotions gracefully
-- Consider implementing cool-down periods between shares
-- Do not override the `referrer_id` query parameter directly. When present, `referrer_id` is expected to be a Discord snowflake-type user ID, otherwise it will be set to the message's author id.
-
----
-
-### Creating and Managing Custom Incentivized Links
-
-This guide covers creating a customizable [Incentivized Link](/docs/activities/development-guides#prompting-users-to-share-incentivized-links) through the dev portal, and then retrieving the link to be able to share it off-platform. Incentivized Links are used to customize how the embed appears to users.
-
-#### Creating a Link
-
-1. In your Application's portal, visit the Custom Links page under the Activities heading in the navigation pane.
-2. On the Custom Links page, click `Create New` to create a new link.
-3. You will need to upload an image with an aspect ratio of 43:24.
-4. Title, and description are also required.
-5. `custom_id` is an optional field, an explicit `custom_id` query parameter on the link itself will always override the set `custom_id`.
-6. Click Save.
-
-#### Editing a Link
-
-1. Click on a row to open up the modal with all of the data loaded in ready for your edits.
-2. Change the description to something else.
-3. Click Update.
-
-#### Copying a Link
-
-Once you're satisfied with your changes you can click on the copy icon on the row, it'll change colors to green indicating that it copied to your clipboard. You are now able to share this link anywhere. The link will look like: `https://discord.com/activities/?link_id=0-123456789`. Even if you've set a `custom_id`, it won't be explicitly included in the link but will be loaded once a user clicks on the link. You can then further shorten this URL if you'd like.
-
-#### Deleting a Link
-
-1. Click on the trash icon on the row of the link you're trying to delete.
-2. You'll have a confirm dialog pop up.
-
-:::warn
-Deleting is irreversible and immediate. Ensure that your link isn't in active use before deleting and/or that your activity gracefully handles any click-throughs from the link.
-:::
-
-#### Best Practices
-
-- Generate unique, non-guessable `customId`s
-- Track and validate referrals to prevent abuse
-- Gracefully handle expirations in your activity for any custom links that are limited time but still live off-platform.
-
-#### User Experience
-
-
-
-Users will see an embed with your information displayed. Clicking "Play" opens the activity and passes through the `custom_id` you've set. A `referrer_id` will be present for links shared on Discord.
-
----
-
-### Generating a Custom Link Within Your Activity
-
-This guide covers creating a customizable [Incentivized Link](/docs/activities/development-guides#prompting-users-to-share-incentivized-links) within your activity, and using the `shareLink` API to share the link.
-
-* Allows you to customize the way the link is presented to users via the embed
-* Can be generated on-demand within your activity
-* Ephemeral, 30 day TTL
-* Does not show up in the developer portal
-
-#### Generating a Link
-
-```
-// Convert an image array buffer to base64 string
-const image = base64EncodedImage;
-
-// Generate the quick activity link
-const linkIdResponse = await fetch(`${env.discordAPI}/applications/${env.applicationId}/quick-links/`, {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${accessToken}`,
- },
- body: {
- custom_id: 'user_123/game_456'
- description: 'I just beat level 10 with a perfect score',
- title: 'Check out my high score!',
- image,
- }
-});
-const {link_id} = await linkIdResponse.json();
-
-// Open the Share modal with the generated link
-const {success} = await discordSdk.commands.shareLink({
- linkId: link_id
-});
-success ? console.log('User shared link!') : console.log('User did not share link!');
-```
-
----
-
-### Preventing unwanted activity sessions
-
-Activities are surfaced through iframes in the Discord app. The activity website itself is publicly reachable at `.discordsays.com`. Activities will expect to be able to communicate with Discord's web or mobile client via the Discord SDK's RPC protocol. If a user loads the activity's website in a normal browser, the Discord RPC server will not be present, and the activity will likely fail in some way.
-
-It is theoretically possible for a malicious client to mock Discord's RPC protocol or load one activity website when launching another. Because the activity is loaded inside Discord, the RPC protocol is active, and the activity is none the wiser.
-
-To enable an activity to "lock down" activity access, we encourage utilizing the `get_activity_instance` API, found at `discord.com/api/applications//activity-instances/'`. The route requires a Bot token of the application. It returns a serialized active activity instance for the given application, if found, otherwise it returns a 404. Here are two example responses:
-
-```javascript
-curl https://discord.com/api/applications/1215413995645968394/activity-instances/i-1234567890-gc-912952092627435520-912954213460484116 -H 'Authorization: Bot '
-{"message": "404: Not Found", "code": 0}
-
-curl https://discord.com/api/applications/1215413995645968394/activity-instances/i-1276580072400224306-gc-912952092627435520-912954213460484116 -H 'Authorization: Bot '
-{"application_id":"1215413995645968394","instance_id":"i-1276580072400224306-gc-912952092627435520-912954213460484116","launch_id":"1276580072400224306","location":{"id":"gc-912952092627435520-912954213460484116","kind":"gc","channel_id":"912954213460484116","guild_id":"912952092627435520"},"users":["205519959982473217"]}
-```
-
-With this API, the activity's backend can verify that a client is in fact in an instance of that activity before allowing the client to participate in any meaningful gameplay. How an activity implements "session verification" is left to the developer's discretion. The solution can be as granular as gating specific features or as binary as not returning the activity HTML except for valid sessions.
-
-In the below flow diagram, we show how the server can deliver the activity website, only for valid users in a valid activity instance:
-
-
----
-
-### Setting Up Activity Metadata
-
-The Activity Shelf is where users can see what Activities can be played. It has various metadata and art assets that can be configured.
-
-To update your app's metadata in the Discord Developer Portal, navigate to the `Settings -> General Information` tab of your app.
-
-- **Application Name:** The publicly visible name of your app.
-- **Application Icon:** The publicly visible icon for your app.
-- **Application Description:** The application description is shown in the view of the Activity Shelf Item.
-- **Max Participants:** The max participants indicate the maximum number of players for your application.
- - Max Participants is displayed above the name in the 1-up view: `Up to X participants`.
- - Leaving this field empty defaults to `Unlimited participants`.
- - Max Participants is also displayed under the name in the 2-up view.
-
-:::info
-An app can have a different application name and avatar from the application's bot username and avatar. Both sets of metadata are public-facing and may be visible in various situations when a user interacts with your app. You can view your bot's username on the `Settings -> Bot` tab.
-:::
-
----
-
-### Setting Up Activity Art Assets
-
-The Activity Shelf is where users can see what Activities can be played. It has various metadata and art assets that can be configured.
-
-To update your app's embedded-specific art assets in the Discord Developer Portal, navigate to the `Activities -> Art Assets` tab of your app.
-
-### Embedded Background
-
-Used as a background overlay in Grid view. Artwork should be clustered around the edges of the image leaving space in the center of the image so the UI does not clash with it.
-
-#### Specifications
-- 16:9 aspect ratio
-- At least 1024 pixels wide
-
-### Cover Art
-
-Used as the main image in the Activity Shelf. It is suggested that this image contain the title and some art in the background.
-
-#### Specifications:
-- Image can be displayed at both 16:9 and 13:11 aspect ratios
-- At least 1024 pixels wide
-
-
-### App Tile
-
-There are two views of an application tile. The regular size tile (2-up tile) and the larger "featured" application tile (1-up tile).
-
-### Video Preview
-
-Hovering over the cover image should start playing a preview video of the Application. The preview videos should be no more than 10 seconds long. If no video is provided, nothing will happen as you hover over the application.
-
-#### Specifications: 640 x 360, mp4 format, under 10 seconds long, under 1 MB in size
-
----
-
-### Cache Busting
-
-All assets loaded by your application will respect [cache headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). One exception is that Discord's application proxy will remove any cache headers for assets whose `content-type` headers include `text/html`. For all non-`text/html` content that your application plans to serve, be sure your application has a cache-busting strategy. This is often built into build processes. If your application has a static filename for its javascript or css, please be sure to implement cache busting techniques, for example [webpack enables creating a content hash and manifest](https://webpack.js.org/guides/caching/) as a part of the build process.
-
-----
-
-### Handling Rate Limits
-
-Be sure network requests made by your application's client and server will be able to respect Discord API's rate limiting [as described here](https://discord.com/developers/docs/topics/rate-limits).
-
-See [this implementation in the Activity Starter project](https://github.com/discord/embedded-app-sdk-examples/blob/main/discord-activity-starter/packages/server/src/utils.ts) for an example of how to respect the `retry_after` header when you receive a 429 error.
-
----
-
-### Static IP Addresses
-
-If your application's server is utilizing a dynamically assigned IP address (this is standard for cloud functions), there is a non-zero chance that you will inherit from a previous bad actor an IP address which has been banned by Cloudflare. In this scenario any egress traffic from the IP address directed towards Discord's API will be banned for up-to an hour. The best way to mitigate this situation is to set up a static IP address for all of your application server's egress traffic to be routed through.
-
----
-
-### Backward Compatibility
-
-#### New Commands
-
-When new commands become available in the embedded-app-sdk, those commands won't be supported by all Discord app versions. The new command will typically only be supported by newer Discord app versions. When an application tries to use a new command with an old Discord app version that doesn't support the command, the Discord app will respond with error code `INVALID_COMMAND` which the application can handle like this:
-
-```javascript
-try {
- const {permissions} = await discordSdk.commands.getChannelPermissions();
-
- // check permissions
- ...
-} catch (error) {
- if (error.code == RPCErrorCodes.INVALID_COMMAND) {
- // This is an expected error. The Discord client doesn't support this command
- ...
- } else {
- // Unexpected error
- ...
- }
-}
-```
diff --git a/docs/activities/development-guides/assets-and-metadata.mdx b/docs/activities/development-guides/assets-and-metadata.mdx
new file mode 100644
index 0000000000..584ad60e2e
--- /dev/null
+++ b/docs/activities/development-guides/assets-and-metadata.mdx
@@ -0,0 +1,60 @@
+---
+sidebar_label: Assets and Metadata
+---
+
+[Home](/docs/intro) > [Activities](/docs/activities/overview) > [Development Guides](/docs/activities/development-guides) > {sidebar_label}
+
+# Assets and Metadata
+
+## Setting Up Activity Metadata
+
+The Activity Shelf is where users can see what Activities can be played. It has various metadata and art assets that can be configured.
+
+To update your app's metadata in the Discord Developer Portal, navigate to the `Settings -> General Information` tab of your app.
+
+- **Application Name:** The publicly visible name of your app.
+- **Application Icon:** The publicly visible icon for your app.
+- **Application Description:** The application description is shown in the view of the Activity Shelf Item.
+- **Max Participants:** The max participants indicate the maximum number of players for your application.
+ - Max Participants is displayed above the name in the 1-up view: `Up to X participants`.
+ - Leaving this field empty defaults to `Unlimited participants`.
+ - Max Participants is also displayed under the name in the 2-up view.
+
+:::info
+An app can have a different application name and avatar from the application's bot username and avatar. Both sets of metadata are public-facing and may be visible in various situations when a user interacts with your app. You can view your bot's username on the `Settings -> Bot` tab.
+:::
+
+---
+
+## Setting Up Activity Art Assets
+
+The Activity Shelf is where users can see what Activities can be played. It has various metadata and art assets that can be configured.
+
+To update your app's embedded-specific art assets in the Discord Developer Portal, navigate to the `Activities -> Art Assets` tab of your app.
+
+## Embedded Background
+
+Used as a background overlay in Grid view. Artwork should be clustered around the edges of the image leaving space in the center of the image so the UI does not clash with it.
+
+#### Specifications
+- 16:9 aspect ratio
+- At least 1024 pixels wide
+
+## Cover Art
+
+Used as the main image in the Activity Shelf. It is suggested that this image contain the title and some art in the background.
+
+#### Specifications:
+- Image can be displayed at both 16:9 and 13:11 aspect ratios
+- At least 1024 pixels wide
+
+
+## App Tile
+
+There are two views of an application tile. The regular size tile (2-up tile) and the larger "featured" application tile (1-up tile).
+
+## Video Preview
+
+Hovering over the cover image should start playing a preview video of the Application. The preview videos should be no more than 10 seconds long. If no video is provided, nothing will happen as you hover over the application.
+
+#### Specifications: 640 x 360, mp4 format, under 10 seconds long, under 1 MB in size
diff --git a/docs/activities/development-guides/growth-and-referrals.mdx b/docs/activities/development-guides/growth-and-referrals.mdx
new file mode 100644
index 0000000000..9d4218d7ad
--- /dev/null
+++ b/docs/activities/development-guides/growth-and-referrals.mdx
@@ -0,0 +1,184 @@
+---
+sidebar_label: Growth and Referrals
+---
+
+[Home](/docs/intro) > [Activities](/docs/activities/overview) > [Development Guides](/docs/activities/development-guides) > {sidebar_label}
+
+# Growth and Referrals
+
+## Prompting Users to Share Incentivized Links
+
+Incentivized sharing can help grow your Activity through network effects. You can use links in several different ways such as:
+
+- **Referral links.** Users can copy referral links inside your Activity, which include their Discord user ID (`https://discord.com/activities/?referrer_id=123456789`), and they can send to their friends. If their friend accepts and starts playing your game, then you gift the referrer something inside your game.
+- **Promotions.** You can run a temporary promotion on social media, where you offer a reward if they start playing now. Share a custom link on your social media (`https://discord.com/activities/?custom_id=social012025` ). Anyone who clicks that specific link receives something inside your game.
+- **Social deep-links.** Currently, when users launch an Activity, they all land in the same place. Instead, you can start deep-linking to contextually relevant points in your game. For example, user A can copy a link inside your Activity for engaging other users (`https://discord.com/activities/?referrer_id=123456789&custom_id=visit-location`), and sends the link to their friends in a DM or channel. Then, user B who clicks the link gets taken directly to user A’s location.
+- **Turn-based deep-links.** When you send an “it’s your turn” DM to a user, you can include a link which takes them directly to the right game instance and the turn they need to take.
+- **Affiliate marketing.** You can work with affiliates (influencers, companies, etc) to advertise your game to their followings, and reward them via a custom link (`https://discord.com/activities/?custom_id=influencer1`). Then, for every user that starts playing because of said influencer, you can then pay out to the influencer.
+- **Source attribution.** You can use the `custom_id` parameter to figure out how much traffic you’re getting from different marketing sources.
+
+This guide covers implementing a referral link which will feature a reward system for users who share links and those who click them.
+
+#### Implementation Overview
+
+1. Create and track an incentivized link for a promotional campaign, then prompt users to share the link
+2. Handle incoming referrals and grant valid rewards
+
+#### Sharing Links
+
+When implementing sharing, you'll need to:
+1. Generate a unique ID for tracking the promotion
+2. Call the [`shareLink`](/docs/developer-tools/embedded-app-sdk#sharelink) command
+3. Track the share attempt
+
+```javascript
+// Generate a unique ID for this promotion
+// This could be per-campaign, per-user, or per-share depending on your needs
+const customId = await createPromotionalCustomId();
+
+try {
+ const { success } = await discordSdk.commands.shareLink({
+ message: 'Click this link to redeem 5 free coins!',
+ custom_id: customId,
+ });
+
+ if (success) {
+ // Track successful share for analytics/limiting
+ await trackSuccessfulShare(customId);
+ }
+} catch (error) {
+ // Handle share failures appropriately
+ console.error('Failed to share link:', error);
+}
+```
+
+#### Handling Incoming Referrals
+
+When a user clicks a shared link, your activity will launch with referral data available through the SDK:
+
+```javascript
+// Early in your activity's initialization
+async function handleReferral() {
+ // Validate the referral data
+ if (!discordSdk.customId || !discordSdk.referrerId) {
+ return;
+ }
+
+ try {
+ // Verify this is a valid promotion and hasn't expired
+ const promotion = await validatePromotion(discordSdk.customId);
+ if (!promotion) {
+ console.log('Invalid or expired promotion');
+ return;
+ }
+
+ // Prevent self-referrals
+ if (discordSdk.referrerId === currentUserId) {
+ console.log('Self-referrals not allowed');
+ return;
+ }
+
+ // Grant rewards to both users
+ await grantRewards({
+ promotionId: discordSdk.customId,
+ referrerId: discordSdk.referrerId,
+ newUserId: currentUserId
+ });
+ } catch (error) {
+ console.error('Failed to process referral:', error);
+ }
+}
+```
+
+#### Link Sharing Best Practices
+
+- Generate unique, non-guessable `customId`s
+- Track and validate referrals to prevent abuse
+- Handle edge cases like expired promotions gracefully
+- Consider implementing cool-down periods between shares
+- Do not override the `referrer_id` query parameter directly. When present, `referrer_id` is expected to be a Discord snowflake-type user ID, otherwise it will be set to the message's author id.
+
+---
+
+## Creating and Managing Custom Incentivized Links
+
+This guide covers creating a customizable [Incentivized Link](/docs/activities/development-guides/growth-and-referrals#prompting-users-to-share-incentivized-links) through the dev portal, and then retrieving the link to be able to share it off-platform. Incentivized Links are used to customize how the embed appears to users.
+
+#### Creating a Link
+
+1. In your Application's portal, visit the Custom Links page under the Activities heading in the navigation pane.
+2. On the Custom Links page, click `Create New` to create a new link.
+3. You will need to upload an image with an aspect ratio of 43:24.
+4. Title, and description are also required.
+5. `custom_id` is an optional field, an explicit `custom_id` query parameter on the link itself will always override the set `custom_id`.
+6. Click Save.
+
+#### Editing a Link
+
+1. Click on a row to open up the modal with all of the data loaded in ready for your edits.
+2. Change the description to something else.
+3. Click Update.
+
+#### Copying a Link
+
+Once you're satisfied with your changes you can click on the copy icon on the row, it'll change colors to green indicating that it copied to your clipboard. You are now able to share this link anywhere. The link will look like: `https://discord.com/activities/?link_id=0-123456789`. Even if you've set a `custom_id`, it won't be explicitly included in the link but will be loaded once a user clicks on the link. You can then further shorten this URL if you'd like.
+
+#### Deleting a Link
+
+1. Click on the trash icon on the row of the link you're trying to delete.
+2. You'll have a confirm dialog pop up.
+
+:::warn
+Deleting is irreversible and immediate. Ensure that your link isn't in active use before deleting and/or that your activity gracefully handles any click-throughs from the link.
+:::
+
+#### Best Practices
+
+- Generate unique, non-guessable `customId`s
+- Track and validate referrals to prevent abuse
+- Gracefully handle expirations in your activity for any custom links that are limited time but still live off-platform.
+
+#### User Experience
+
+
+
+Users will see an embed with your information displayed. Clicking "Play" opens the activity and passes through the `custom_id` you've set. A `referrer_id` will be present for links shared on Discord.
+
+---
+
+## Generating a Custom Link Within Your Activity
+
+This guide covers creating a customizable [Incentivized Link](/docs/activities/development-guides/growth-and-referrals#prompting-users-to-share-incentivized-links) within your activity, and using the `shareLink` API to share the link.
+
+* Allows you to customize the way the link is presented to users via the embed
+* Can be generated on-demand within your activity
+* Ephemeral, 30 day TTL
+* Does not show up in the developer portal
+
+#### Generating a Link
+
+```javascript
+// Convert an image array buffer to base64 string
+const image = base64EncodedImage;
+
+// Generate the quick activity link
+const linkIdResponse = await fetch(`${env.discordAPI}/applications/${env.applicationId}/quick-links/`, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ body: {
+ custom_id: 'user_123/game_456'
+ description: 'I just beat level 10 with a perfect score',
+ title: 'Check out my high score!',
+ image,
+ }
+});
+const {link_id} = await linkIdResponse.json();
+
+// Open the Share modal with the generated link
+const {success} = await discordSdk.commands.shareLink({
+ linkId: link_id
+});
+success ? console.log('User shared link!') : console.log('User did not share link!');
+```
diff --git a/docs/activities/development-guides/layout.mdx b/docs/activities/development-guides/layout.mdx
new file mode 100644
index 0000000000..6793a94cbd
--- /dev/null
+++ b/docs/activities/development-guides/layout.mdx
@@ -0,0 +1,77 @@
+---
+sidebar_label: Layout
+---
+
+[Home](/docs/intro) > [Activities](/docs/activities/overview) > [Development Guides](/docs/activities/development-guides) > {sidebar_label}
+
+# Layout
+
+## Application Orientation
+
+#### Locking Application Orientation
+
+This SDK provides APIs for locking the application to specific orientations. The possible lock states are `UNLOCKED`, `PORTRAIT`, and `LANDSCAPE`. `lock_state` is the default lock state, and it affects the app orientation when the application is focused. `picture_in_picture_lock_state` determines the PIP aspect ratio, and `grid_lock_state` determines the grid tile aspect ratio for the application. When `picture_in_picture_lock_state` is not set, the application PIP falls back to `lock_state` to determine the aspect ratio. When `grid_lock_state` is not set, the application grid tile falls back to `picture_in_picture_lock_state` to determine its aspect ratio, and if `picture_in_picture_lock_state`is not set, it uses `lock_state`.
+
+Calling `setOrientationLockState` with an `undefined` or omitted value for `picture_in_picture_lock_state` or `grid_lock_state` will not change the corresponding lock states for the application. Calling `setOrientationLockState` with a null value for `picture_in_picture_lock_state` or `grid_lock_state` will clear the application's corresponding lock states such that those layout modes will use the fallback lock states.
+
+```javascript
+import {DiscordSDK, Common} from '@discord/embedded-app-sdk';
+const discordSdk = new DiscordSDK(clientId);
+await discordSdk.ready();
+
+// Set a default lock state
+discordSdk.commands.setOrientationLockState({lock_state: Common.OrientationLockStateTypeObject.LANDSCAPE});
+
+// or set both a default lock state and a picture-in-picture lock state
+discordSdk.commands.setOrientationLockState({
+ lock_state: Common.OrientationLockStateTypeObject.PORTRAIT,
+ picture_in_picture_lock_state: Common.OrientationLockStateTypeObject.LANDSCAPE,
+ grid_lock_state: Common.OrientationLockStateTypeObject.LANDSCAPE,
+});
+```
+
+#### Configuring Default Orientation Lock State Through the Developer Portal
+
+It's also possible to configure an application with a default orientation lock state via the Developer Portal. Using this method, the Discord app will apply the orientation lock when launching the application before the SDK has been initialized. This can create a smoother application launch flow where the application starts in the correct orientation rather than switching to the correct orientation after some delay after the application requests an orientation lock via the SDK. The Developer Portal supports setting a different default orientation lock states for phones versus tablets.
+
+
+
+#### Subscribing to Screen Orientation Updates
+
+To listen to the screen orientation (which is sometimes different from the physical device orientation), subscribe to the `ORIENTATION_UPDATE` event. Discord will publish the current orientation upon event subscription, and it'll also publish any orientation changes that happen afterward.
+
+```javascript
+const handleOrientationUpdate = (update: {screen_orientation: number}) => {
+ switch (update.screen_orientation) {
+ case Common.OrientationTypeObject.PORTRAIT:
+ ...
+ case Common.OrientationTypeObject.LANDSCAPE:
+ ...
+ default:
+ ...
+ }
+}
+
+discordSdk.subscribe('ORIENTATION_UPDATE', handleOrientationUpdate);
+```
+
+---
+
+## Application Layout Mode
+
+There are three layout modes that an application can be in: focused, picture-in-picture (PIP), or grid mode. Activities can subscribe to the layout mode to determine when to optionally change their layouts to optimize for each layout mode. Old Discord clients only support the `ACTIVITY_PIP_MODE_UPDATE` event, while new Discord clients support both `ACTIVITY_PIP_MODE_UPDATE` and `ACTIVITY_LAYOUT_MODE_UPDATE`. Use `subscribeToLayoutModeUpdatesCompat` and `unsubscribeFromLayoutModeUpdatesCompat` to subscribe to both events with backward compatibility for old Discord clients that only support `ACTIVITY_PIP_MODE_UPDATE`. Here's an example using React:
+
+```javascript
+export default function LayoutMode() {
+ const handleLayoutModeUpdate = React.useCallback((update: {layout_mode: number}) => {
+ ...
+ }, []);
+
+ React.useEffect(() => {
+ discordSdk.subscribeToLayoutModeUpdatesCompat(handleLayoutModeUpdate);
+ return () => {
+ discordSdk.unsubscribeFromLayoutModeUpdatesCompat(handleLayoutModeUpdate);
+ };
+ }, [handleLayoutModeUpdate]);
+}
+```
diff --git a/docs/activities/development-guides/local-development.mdx b/docs/activities/development-guides/local-development.mdx
new file mode 100644
index 0000000000..eab1571c0f
--- /dev/null
+++ b/docs/activities/development-guides/local-development.mdx
@@ -0,0 +1,196 @@
+---
+sidebar_label: Local Development
+---
+
+[Home](/docs/intro) > [Activities](/docs/activities/overview) > [Development Guides](/docs/activities/development-guides) > {sidebar_label}
+
+# Local Development
+
+## Run Your Application Locally
+
+It is possible to load your application via a localhost port or other unique URL. This URL must support an HTTPS connection to load on the web/desktop Discord app (HTTPS is not required for mobile). The downside to this flow is that your application's network traffic will not pass through Discord's proxy, which means any requests made by the application will need to use a full URL instead of a ["mapped"](/docs/activities/development-guides/local-development#url-mapping) URL.
+
+To run your locally hosted application, follow the instructions for [Launching your Application from the Discord Client](/docs/activities/development-guides/local-development#launch-your-application-from-the-discord-client) and set the Application URL Override to the address of your application's web server.
+
+### Running Your Application Through A Network Tunnel
+
+Although it is possible to test your application locally, we recommend developing and testing against the Discord proxy. This is helpful to make sure all URLs behave as expected before your application runs in production. One technique to enable testing locally against the proxy is to use a network tunneling tool, such as [cloudflared](https://github.com/cloudflare/cloudflared#installing-cloudflared). A typical pattern is for each developer to have their own "development-only" application. To set up a local environment to run through Discord's proxy, you will need to do the following:
+
+1. Create a new application in the Discord Developer portal.
+2. Enable Activities for your app.
+3. Set up the application's [URL mapping](/docs/activities/development-guides/local-development#url-mapping).
+4. Locally, spin up your web server.
+5. Install and run a tunnel solution, such as [cloudflared](https://github.com/cloudflare/cloudflared#installing-cloudflared). You will point it to your local web server.
+
+:::info
+Your web server can be HTTP and your network tunnel can upgrade the connection to HTTPS.
+:::
+
+If using cloudflared, you will run the following command, replace `3000` with your web server's port.
+
+```
+cloudflared tunnel --url http://localhost:3000
+```
+
+Once you run this command, you will receive your publicly accessible network tunnel address from cloudflared.
+
+```
+Your quick Tunnel has been created! Visit it at (it may take some time to be reachable):
+https://funky-jogging-bunny.trycloudflare.com
+```
+
+In the Discord Developer Portal, update the Application URL mapping for `/` url to `funky-jogging-bunny.trycloudflare.com` to match your network tunnel address and save your changes.
+
+
+
+:::warn
+If you do not own the URL that you are using to host the application (i.e. ngrok's free tier), someone else could claim that domain and host a malicious site in its place. Please be aware of these risks, and if you have to use a domain you do not own, be sure to reset your URL mapping when you are done using the tunnel.
+:::
+
+Follow the instructions for [Launching your Application from the Discord Client](/docs/activities/development-guides/local-development#launch-your-application-from-the-discord-client). Application URL Override should not be enabled.
+
+### Running Your Application In Production
+
+The flow for setting up your production application is very similar:
+
+1. If not made yet, create a new application.
+2. Enable Activities for your app.
+3. Set up the application's [URL Mapping](/docs/activities/development-guides/local-development#url-mapping). The URL for your application's html should be set to the `/` route.
+4. Follow the instructions for [Launching your Application from the Discord Client](/docs/activities/development-guides/local-development#launch-your-application-from-the-discord-client)). Application URL Override should not be enabled.
+
+This application now uses the same configuration it will use once it is fully published ✨.
+
+
+
+---
+
+### Launch your application from the Discord Client
+
+You will be able to see and launch all activities owned by you or any teams you are a member of via the Developer Activity Shelf. One caveat is that the activity will not be shown on the current platform (web/ios/android) unless you have checked your platform in `Settings/Supported Platforms` on the developer portal.
+
+To see you app inside of Discord in the Activity Shelf:
+
+#### Web
+
+1. Select ⚙️User Settings > App Settings > Advanced and toggle on `Developer Mode`
+3. Close the settings window and enter a voice channel.
+4. From either the RTC Panel or the Center Control Tray, click on the "Rocket Button" to open the Activity shelf. You should now see all of the same applications that you have access to in the developer portal. Note: The shelf will only include applications which have been flagged as "Embedded".
+6. Click on an activity to launch it!
+
+#### Mobile
+
+1. From your User Profile, select Appearance, and then toggle "On" `Developer Mode`
+2. Enter a voice channel
+5. Click on an activity to launch it!
+
+---
+
+### URL Mapping
+
+Activities in Discord are "sandboxed" via a Discord proxy. This is done to hide the users' IP addresses, your application's IP addresses, and to block URLs from known malicious endpoints. As an application owner, you can configure the proxy to allow network requests to external endpoints.
+
+Because your application is "sandboxed", it will be unable to make network requests to external URLs. Let's say you want request `https://some-api.com`. To enable reaching this url from inside your application, you will create a new url mapping, with the `PREFIX` set to
+`/api` and `TARGET` set to `some-api.com`. Now you can make requests to `/api` from inside of your application, which will be forwarded, via Discord's proxy to `some-api.com`.
+
+#### How to set a URL Mapping
+
+To add or modify your application's URL mappings, click on `Activities -> URL Mappings` and set the prefix and target values for each mapping as needed.
+
+
+
+#### Prefix/Target formatting rules
+
+- URL mappings can utilize any url protocol, (https, wss, ftp, etc...), which is why the URL target should not include a protocol. For example, for a URL target, do not put `https://your-url.com`, instead, omit `https://` and use `your-url.com`.
+- Parameter matching can be used to help map external domain urls. For example, if an external url has many subdomains, such as `foo.google.com`, `bar.google.com`, then you could use the following mapping:
+ | PREFIX | TARGET |
+ |-----------------------|--------------------------|
+ | `/google/{subdomain}` | `{subdomain}.google.com` |
+- Targets must point to a directory; setting a target to a file (e.g. `example.com/index.html`) is unsupported and may lead to unexpected behavior.
+- Because of how URL globbing works, if you have multiple prefix urls with the same initial path, you must place the shortest of the prefix paths last in order for each url mapping to be reachable. For example, if you have `/foo` and `/foo/bar`, you must place the url `/foo/bar` before `/foo` or else the mapping for `/foo/bar` will never be reached.
+
+| ✅ DO | ❌ DON'T |
+|-------------------------------------------------------------|-----------------------------------------------------------------|
+| Requests mapped correctly | Requests to /foo/bar will incorrectly be sent to `foo.com` |
+|  |  |
+
+#### Exceptions
+
+The aforementioned "sandbox" is enforced by a [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). We have some notable exceptions to our CSP, meaning application clients may make requests to these URLs without hitting the proxy and therefore without establishing mappings. Notable exceptions include:
+
+- `https://discord.com/api/`
+- `https://canary.discord.com/api/`
+- `https://ptb.discord.com/api/`
+- `https://cdn.discordapp.com/attachments/`
+- `https://cdn.discordapp.com/avatars/`
+- `https://cdn.discordapp.com/icons/`
+- `https://media.discordapp.net/attachments/`
+- `https://media.discordapp.net/avatars/`
+- `https://media.discordapp.net/icons/`
+
+---
+
+### Logging
+
+By default, the SDK will send any console `log`, `warn`, `error`, `info`, and `debug` events triggered by your app to the Discord application.
+
+#### Viewing Logs on Desktop
+
+Desktop logs are viewable through the console tab inside a browser's Developer Tools. See the [Troubleshooting Console Log Errors](https://support.discord.com/hc/en-us/articles/115001239472-Troubleshooting-Console-Log-Errors) support article for more information.
+
+The Public Test Build (PTB) Discord client also allows inspecting your logs from the `View -> Developer -> Toggle Developer Tools` menu. It can be downloaded at [https://discord.com/downloads](https://discord.com/downloads).
+
+#### Viewing Logs on Mobile
+
+Mobile logs are viewable via the `Debug Logs` option inside User Settings on the mobile App. It is only discoverable when you have `Developer Mode` enabled.
+
+1. On the bottom navigation, tap on your avatar and then the gear icon to open your `User Settings`.
+2. Tap `Appearance`.
+3. Slide the `Developer Mode` toggle to ON.
+4. The `Debug Logs` option will be available under the `DEV ONLY` section.
+
+#### Filtering for Application Logs
+
+Inside the Debug Logs view, you can search for your own application logs with the possible keywords:
+
+- `RpcApplicationLogger`
+- Your Application ID
+
+Each log line is formatted as: `[RpcApplicationLogger] - message`
+
+The first section of Debug Logs are not your application logs but Discord specific app startup info which is not relevant to your application.
+
+When you scroll down the page, your application logs should be visible.
+
+
+
+#### Sharing Application Logs from Mobile
+
+With `Developer Mode` enabled, you can share your application logs from within a Voice Channel.
+
+1. In the voice channel, swipe from the bottom to see the expanded voice controls. Tap on `Share Application Logs`.
+2. You'll be presented with a native share sheet where you can save the logs to a file or share it as a message.
+
+#### Disabling Logging
+
+If you do not want logs to be forwarded to the browser, you can disable it with the optional configuration object.
+
+```javascript
+import {DiscordSDK} from '@discord/embedded-app-sdk';
+const discordSdk = new DiscordSDK(clientId, {
+ disableConsoleLogOverride: true,
+});
+```
+
+#### Forwarding Log Messages
+
+You can forward specific log messages via the SDK command `captureLog` as shown below.
+
+```javascript
+import {DiscordSDK} from '@discord/embedded-app-sdk';
+const discordSdk = new DiscordSDK(clientId);
+await discordSdk.ready();
+discordSdk.commands.captureLog({
+ level: 'log',
+ message: 'This is my log message!',
+});
+```
diff --git a/docs/activities/development-guides/mobile.mdx b/docs/activities/development-guides/mobile.mdx
new file mode 100644
index 0000000000..753f4ed8ae
--- /dev/null
+++ b/docs/activities/development-guides/mobile.mdx
@@ -0,0 +1,92 @@
+---
+sidebar_label: Mobile
+---
+
+[Home](/docs/intro) > [Activities](/docs/activities/overview) > [Development Guides](/docs/activities/development-guides) > {sidebar_label}
+
+# Mobile
+
+## Supported Platforms: Web, iOS, Android
+
+By default, your Activity will be launchable on web/desktop. To enable or disable support for Web/iOS/Android, do the following:
+
+- Visit the developer portal
+- Select your application
+- Select `Activities` -> `Settings` in the left-side of the developer portal, or visit `https://discord.com/developers//embedded/settings`
+- From check the appropriate checkboxes in the developer portal, and save your changes
+
+
+
+---
+
+## Mobile Safe Areas
+
+As an example, you can define your safe area insets as below in CSS:
+
+```
+:root {
+ --sait: var(--discord-safe-area-inset-top, env(safe-area-inset-top));
+ --saib: var(--discord-safe-area-inset-bottom, env(safe-area-inset-bottom));
+ --sail: var(--discord-safe-area-inset-left, env(safe-area-inset-left));
+ --sair: var(--discord-safe-area-inset-right, env(safe-area-inset-right));
+}
+```
+
+This prefers the `--discord-safe-area-inset-*` variable and will fallback to the env values for iOS + any local dev testing that is done outside of Discord.
+
+You can then reference these values:
+
+```
+body {
+ padding-left: var(--sail);
+ padding-right: var(--sair);
+ padding-top: var(--sait);
+ padding-bottom: var(--saib);
+}
+```
+
+---
+
+## Mobile Thermal States
+
+You may need to respond to thermal state changes using recommendations from [thermal states surfaced by mobile devices](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/RespondToThermalStateChanges.html) to improve the user experience.
+
+Discord's Embedded App SDK provides an abstraction over [Apple's thermal state APIs](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/RespondToThermalStateChanges.html) and [Android's thermal state APIs](https://source.android.com/docs/core/power/thermal-mitigation#thermal-api).
+
+Here's how Discord's abstraction maps to Apple's thermal states and Android's thermal states.
+
+```javascript
+enum ThermalState {
+ NOMINAL = 0, // maps to "nominal" on iOS and "none" on Android
+ FAIR = 1, // maps to "fair" on iOS and "light" / "moderate" on Android
+ SERIOUS = 2, // maps to "serious" on iOS and "severe" on Android
+ CRITICAL = 3, // maps to "critical" on iOS and "critical" / "emergency" / "shutdown" on Android
+}
+```
+
+The Embedded App SDK allows developers to subscribe to these thermal state changes.
+
+```javascript
+const handleThermalStateUpdate = (update: {thermal_state: number}) => {
+ switch (thermalState) {
+ case Common.ThermalStateTypeObject.NOMINAL:
+ ...
+ case Common.ThermalStateTypeObject.FAIR:
+ ...
+ case Common.ThermalStateTypeObject.SERIOUS:
+ ...
+ case Common.ThermalStateTypeObject.CRITICAL:
+ ...
+ default:
+ ...
+ }
+}
+
+discordSdk.subscribe('THERMAL_STATE_UPDATE', handleThermalStateUpdate);
+```
+
+Discord will publish the current thermal state upon event subscription, and it will also publish any thermal state changes that happen afterward.
+
+:::info
+On Android devices, the thermal state updates will only be available on Android 10 and higher.
+:::
diff --git a/docs/activities/development-guides/multiplayer-experience.mdx b/docs/activities/development-guides/multiplayer-experience.mdx
new file mode 100644
index 0000000000..5034ad5bae
--- /dev/null
+++ b/docs/activities/development-guides/multiplayer-experience.mdx
@@ -0,0 +1,154 @@
+---
+sidebar_label: Multiplayer Experience
+---
+
+[Home](/docs/intro) > [Activities](/docs/activities/overview) > [Development Guides](/docs/activities/development-guides) > {sidebar_label}
+
+# Multiplayer Experience
+
+## Activity Instance Management
+
+When a user clicks "Join Application", they expect to enter the same application that their friends are participating in. Whether the application is a shared drawing canvas, board game, collaborative playlist, or first-person shooter; the two users should have access to the same shared data. In this documentation, we refer to this shared data as an **application instance**.
+
+
+
+The Embedded App SDK allows your app to talk bidirectionally with the Discord Client. The `instanceId` is necessary for your application, as well as Discord, to understand which unique instance of an application it is talking to.
+
+#### Using instanceId
+
+The `instanceId` attribute is available as soon as the SDK is constructed, and does not require the SDK to receive a `ready` payload from the Discord client.
+
+```javascript
+import {DiscordSDK} from '@discord/embedded-app-sdk';
+const discordSdk = new DiscordSDK(clientId);
+// available immediately
+const instanceId = discordSdk.instanceId;
+```
+
+The `instanceId` should be used as a key to save and load the shared data relevant to an application. This ensures that two users who are in the same application instance have access to the same shared data.
+
+##### Semantics of instanceId
+
+Instance IDs are generated when a user launches an application. Any users joining the same application will receive the same `instanceId`. When all the users of an application in a channel leave or close the application, that instance has finished its lifecycle, and will not be used again. The next time a user opens the application in that channel, a new `instanceId` will be generated.
+
+---
+
+## Instance Participants
+
+Instance Participants are any Discord user actively connected to the same Application Instance. This data can be fetched or subscribed to.
+
+```javascript
+import {DiscordSDK, Events, type Types} from '@discord/embedded-app-sdk';
+
+const discordSdk = new DiscordSDK('...');
+await discordSdk.ready();
+
+// Fetch
+const participants = await discordSdk.commands.getInstanceConnectedParticipants();
+
+// Subscribe
+function updateParticipants(participants: Types.GetActivityInstanceConnectedParticipantsResponse) {
+ // Do something really cool
+}
+discordSdk.subscribe(Events.ACTIVITY_INSTANCE_PARTICIPANTS_UPDATE, updateParticipants);
+// Unsubscribe
+discordSdk.unsubscribe(Events.ACTIVITY_INSTANCE_PARTICIPANTS_UPDATE, updateParticipants);
+```
+
+---
+
+## Render Avatars and Names
+
+Check out detailed documentation on where and how Discord stores common image assets [here](https://discord.com/developers/docs/reference#image-formatting-cdn-endpoints).
+
+Here's a basic example for retrieving a user's avatar and username
+
+```javascript
+// We'll be referencing the user object returned from authenticate
+const {user} = await discordSdk.commands.authenticate({
+ access_token: accessToken,
+});
+
+let avatarSrc = '';
+if (user.avatar) {
+ avatarSrc = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=256`;
+} else {
+ const defaultAvatarIndex = (BigInt(user.id) >> 22n) % 6n;
+ avatarSrc = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarIndex}.png`
+}
+
+const username = user.global_name ?? `${user.username}#${user.discriminator}`;
+
+// Then in your HTML/JSX/etc...
+
+{username}
+```
+
+#### Rendering guild-specific avatars and nicknames
+
+In order to retrieve a user's guild-specific avatar and nickname, your application must request the `guilds.members.read` scope. Note, this only grants the information for that instance of the application's user. To display the guild-specific avater/nickname for all application users, any info retrieved from `guilds.members.read` scope'd API calls must be shared via your application's server.
+
+Here's an example of how to retrieve the user's guild-specific avatar and nickname:
+
+```javascript
+// We'll be referencing the user object returned from authenticate
+const {user} = await discordSdk.commands.authenticate({
+ access_token: accessToken,
+});
+
+// When using the proxy, you may instead replace `https://discord.com` with `/discord`
+// or whatever url mapping you have chosen via the developer portal
+fetch(`https://discord.com/api/users/@me/guilds/${discordSdk.guildId}/member`, {
+ method: 'GET',
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+})
+ .then((response) => {
+ return response.json();
+ })
+ .then((guildsMembersRead) => {
+ let guildAvatarSrc = '';
+ // Retrieve the guild-specific avatar, and fallback to the user's avatar
+ if (guildsMembersRead?.avatar) {
+ guildAvatarSrc = `https://cdn.discordapp.com/guilds/${discordSdk.guildId}/users/${user.id}/avatars/${guildsMembersRead.avatar}.png?size=256`;
+ } else if (user.avatar) {
+ guildAvatarSrc = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=256`;
+ } else {
+ const defaultAvatarIndex = (BigInt(user.id) >> 22n) % 6n;
+ avatarSrc = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarIndex}.png`;
+ }
+
+ // Retrieve the guild-specific nickname, and fallback to the username#discriminator
+ const guildNickname = guildsMembersRead?.nick ?? (user.global_name ?? `${user.username}#${user.discriminator}`);
+ });
+```
+
+This example is being done entirely on the client, however, a more common pattern is to instead, do the following:
+
+- Store the user's access token on the application server
+- Retrieve the user's guild-specific avatar and nickname via the application's server
+- Serve all of the application's avatar/nicknames via the application's server
+
+---
+
+## Preventing unwanted activity sessions
+
+Activities are surfaced through iframes in the Discord app. The activity website itself is publicly reachable at `.discordsays.com`. Activities will expect to be able to communicate with Discord's web or mobile client via the Discord SDK's RPC protocol. If a user loads the activity's website in a normal browser, the Discord RPC server will not be present, and the activity will likely fail in some way.
+
+It is theoretically possible for a malicious client to mock Discord's RPC protocol or load one activity website when launching another. Because the activity is loaded inside Discord, the RPC protocol is active, and the activity is none the wiser.
+
+To enable an activity to "lock down" activity access, we encourage utilizing the `get_activity_instance` API, found at `discord.com/api/applications//activity-instances/'`. The route requires a Bot token of the application. It returns a serialized active activity instance for the given application, if found, otherwise it returns a 404. Here are two example responses:
+
+```javascript
+curl https://discord.com/api/applications/1215413995645968394/activity-instances/i-1234567890-gc-912952092627435520-912954213460484116 -H 'Authorization: Bot '
+{"message": "404: Not Found", "code": 0}
+
+curl https://discord.com/api/applications/1215413995645968394/activity-instances/i-1276580072400224306-gc-912952092627435520-912954213460484116 -H 'Authorization: Bot '
+{"application_id":"1215413995645968394","instance_id":"i-1276580072400224306-gc-912952092627435520-912954213460484116","launch_id":"1276580072400224306","location":{"id":"gc-912952092627435520-912954213460484116","kind":"gc","channel_id":"912954213460484116","guild_id":"912952092627435520"},"users":["205519959982473217"]}
+```
+
+With this API, the activity's backend can verify that a client is in fact in an instance of that activity before allowing the client to participate in any meaningful gameplay. How an activity implements "session verification" is left to the developer's discretion. The solution can be as granular as gating specific features or as binary as not returning the activity HTML except for valid sessions.
+
+In the below flow diagram, we show how the server can deliver the activity website, only for valid users in a valid activity instance:
+
diff --git a/docs/activities/development-guides/networking.mdx b/docs/activities/development-guides/networking.mdx
new file mode 100644
index 0000000000..c70fcee9ac
--- /dev/null
+++ b/docs/activities/development-guides/networking.mdx
@@ -0,0 +1,107 @@
+---
+sidebar_label: Networking
+---
+
+[Home](/docs/intro) > [Activities](/docs/activities/overview) > [Development Guides](/docs/activities/development-guides) > {sidebar_label}
+
+# Networking
+
+## Activity Proxy Considerations
+
+All network traffic is routed through the Discord Proxy for various security reasons.
+
+Under the hood we utilize Cloudflare Workers, which brings some restrictions, outlined below.
+
+#### WebTransport
+
+While we currently only support websockets, we're working with our upstream providers to enable WebTransport.
+
+#### WebRTC
+
+WebRTC is not supported.
+
+---
+
+## Construct A Full URL
+
+There are scenarios where instead of using a relative url (`/path/to/my/thing`) you may want or need to reference the full url when making a network request. The URL is a combination of the following
+
+1. The protocol you wish to use
+2. Your application's client id
+3. The discord proxy domain
+4. The `/.proxy` path prefix
+5. Whatever you need to list
+
+Here's an example of how to build a full url, using the URL constructor:
+
+```javascript
+const protocol = `https`;
+const clientId = '';
+const proxyDomain = 'discordsays.com';
+const resourcePath = '/foo/bar.jpg';
+const url = new URL(`${protocol}://${clientId}.${proxyDomain}/.proxy${resourcePath}`);
+```
+
+In other words, given an application client id of `12345678`
+| Relative Path | Full Path |
+|---------------|-----------------------------------------------------|
+| /foo/bar.jpg | https://12345678.discordsays.com/.proxy/foo/bar.jpg |
+
+---
+
+## Using External Resources
+
+Activities in Discord are "sandboxed" via a Discord proxy. This is done to hide the users' IP addresses as well as block URLs from known malicious endpoints. To achieve this, the Discord Developer Portal has a section for [configuring URL Mappings](/docs/activities/development-guides/local-development#url-mapping) for your application.
+
+One edge-case of URL mappings is that third-party NPM modules or other resources may reference external (non-sandboxed) urls.
+
+For example, if your application has an npm module that attempts to make an http request to https://foo.library.com, the request will fail with a `blocked:csp` error.
+
+To get around this limitation there are several options to consider:
+
+- Fork the library (to use mapped urls)
+- Utilize a post-install utility such as [patch-package](https://www.npmjs.com/package/patch-package)
+- Use our Embedded App SDK's `patchUrlMappings` API
+
+In the above scenario, we recommend using the `patchUrlMappings` API, as it will allow a smooth transition from the non-sandboxed dev environment to the production environment.
+
+This method call takes an array of "mappings" which will transform any external network requests to the mappings you've defined.
+
+See the example below:
+
+- In this example, imagine you have a third-party library which makes an HTTP request to foo.com
+- In the developer portal, create a mapping like this: `/foo` -> `foo.com`
+- Then in your code, when initializing the SDK, you will make a function call.
+
+
+```javascript
+import {patchUrlMappings} from '@discord/embedded-app-sdk';
+const isProd = process.env.NODE_ENV === 'production'; // Actual dev/prod env check may vary for you
+
+async function setupApp() {
+ if (isProd) {
+ patchUrlMappings([{prefix: '/foo', target: 'foo.com'}]);
+ }
+ // start app initialization after this....
+}
+```
+
+:::info
+Note: `patchUrlMappings` is modifying your browser's `fetch`, `WebSocket`, and `XMLHttpRequest.prototype.open` global variables. Depending on the library, you may see side effects from using this helper function. It should be used only when necessary.
+:::
+
+---
+
+## Security Considerations
+
+#### Trusting Client Data
+
+Do not trust data coming from the Discord client as truth. It's fine to use this data in your application locally, but assume any data coming from the Discord Client could be falsified. That includes data about the current user, their nitro status, their current channel, etc. If you need this information in a trusted manner, contact Discord API directly from your application's server, with the user token you received from completing the OAuth2 flow.
+
+Furthermore, data coming from the Discord client is not sanitized beforehand. Things like usernames and channel names are arbitrary user input. Make sure to sanitize these strings or use `.textContent` (for example) to display them safely in your UI.
+
+#### Using Cookies
+
+To set a cookie for your activity to use in network requests through the proxy, make sure the cookie's domain matches your app's full `{clientId}.discordsays.com` domain. You will also need to explicitly set `SameSite=None Partitioned` on the cookie. `SameSite=None` is needed as browsers refuse to store or send cookies with higher restriction levels for any navigation within an iframe. `Partitioned` then limits the use of that cookie to only Discord's iframes.
+
+Rest assured: other activities will not be able to make requests with your activity's cookie, thanks to the Content Security Policy (CSP) limiting requests only to your own app's proxy.
diff --git a/docs/activities/development-guides/production-readiness.mdx b/docs/activities/development-guides/production-readiness.mdx
new file mode 100644
index 0000000000..843d1e7511
--- /dev/null
+++ b/docs/activities/development-guides/production-readiness.mdx
@@ -0,0 +1,50 @@
+---
+sidebar_label: Production Readiness
+---
+
+[Home](/docs/intro) > [Activities](/docs/activities/overview) > [Development Guides](/docs/activities/development-guides) > {sidebar_label}
+
+# Production Readiness
+
+## Cache Busting
+
+All assets loaded by your application will respect [cache headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). One exception is that Discord's application proxy will remove any cache headers for assets whose `content-type` headers include `text/html`. For all non-`text/html` content that your application plans to serve, be sure your application has a cache-busting strategy. This is often built into build processes. If your application has a static filename for its javascript or css, please be sure to implement cache busting techniques, for example [webpack enables creating a content hash and manifest](https://webpack.js.org/guides/caching/) as a part of the build process.
+
+----
+
+## Handling Rate Limits
+
+Be sure network requests made by your application's client and server will be able to respect Discord API's rate limiting [as described here](https://discord.com/developers/docs/topics/rate-limits).
+
+See [this implementation in the Activity Starter project](https://github.com/discord/embedded-app-sdk-examples/blob/main/discord-activity-starter/packages/server/src/utils.ts) for an example of how to respect the `retry_after` header when you receive a 429 error.
+
+---
+
+## Static IP Addresses
+
+If your application's server is utilizing a dynamically assigned IP address (this is standard for cloud functions), there is a non-zero chance that you will inherit from a previous bad actor an IP address which has been banned by Cloudflare. In this scenario any egress traffic from the IP address directed towards Discord's API will be banned for up-to an hour. The best way to mitigate this situation is to set up a static IP address for all of your application server's egress traffic to be routed through.
+
+---
+
+## Backward Compatibility
+
+#### New Commands
+
+When new commands become available in the embedded-app-sdk, those commands won't be supported by all Discord app versions. The new command will typically only be supported by newer Discord app versions. When an application tries to use a new command with an old Discord app version that doesn't support the command, the Discord app will respond with error code `INVALID_COMMAND` which the application can handle like this:
+
+```javascript
+try {
+ const {permissions} = await discordSdk.commands.getChannelPermissions();
+
+ // check permissions
+ ...
+} catch (error) {
+ if (error.code == RPCErrorCodes.INVALID_COMMAND) {
+ // This is an expected error. The Discord client doesn't support this command
+ ...
+ } else {
+ // Unexpected error
+ ...
+ }
+}
+```
diff --git a/docs/activities/development-guides/user-actions.mdx b/docs/activities/development-guides/user-actions.mdx
new file mode 100644
index 0000000000..3c43c74f35
--- /dev/null
+++ b/docs/activities/development-guides/user-actions.mdx
@@ -0,0 +1,184 @@
+---
+sidebar_label: User Actions
+---
+
+[Home](/docs/intro) > [Activities](/docs/activities/overview) > [Development Guides](/docs/activities/development-guides) > {sidebar_label}
+
+# User Actions
+
+## Open External Link
+
+Since Activities are sandboxed, your app will need to perform a command in order for users to launch any external links. Users will be prompted inside Discord whether or not they want to open the external link.
+
+#### Usage
+
+```javascript
+import {DiscordSDK} from '@discord/embedded-app-sdk';
+const discordSdk = new DiscordSDK(clientId);
+await discordSdk.ready();
+// Once the sdk has established the connection with the discord client, external
+// links can be launched
+discordSdk.commands.openExternalLink({
+ url: 'https://google.com',
+});
+```
+
+#### User Experience
+
+
+
+Users will see a modal inside the Discord app notifying them whether or not they want to proceed. By clicking **_Trust this Domain_**, users will not see a modal for that specific domain again.
+
+---
+
+## Open Invite Dialog
+
+Getting an Application Channel Invite, as outlined in [these docs](https://discord.com/developers/docs/resources/invite#get-invite), is not granted by any OAuth scopes. Nonetheless, the `openInviteDialog` command is available via the SDK. This command opens the Application Channel Invite UI within the discord client without requiring additional OAuth scopes.
+
+This command returns an error when called from DM (Direct Message) contexts, so should only be called in Guild Voice or Text Channels. Similarly, this command returns an error if the user has invalid permissions for the channel, so using `getChannelPermissions` (requires OAuth scope `'guilds.members.read'`) is highly recommended.
+
+#### Usage
+
+```javascript
+import {DiscordSDK, Permissions, PermissionUtils} from '@discord/embedded-app-sdk';
+const discordSdk = new DiscordSDK(clientId);
+await discordSdk.ready();
+
+try {
+ const {permissions} = await discordSdk.commands.getChannelPermissions();
+ if (PermissionUtils.can(Permissions.CREATE_INSTANT_INVITE, permissions)) {
+ await discordSdk.commands.openInviteDialog();
+ // successfully opened dialog
+ } else {
+ console.warn('User does not have CREATE_INSTANT_INVITE permissions');
+ }
+} catch (err) {
+ // failed to fetch permissions or open dialog
+ console.warn(err.message);
+}
+```
+
+User Experience
+
+
+
+Users will see a modal inside the Discord app allowing them to send an invite to a channel, friend, or copy an invite link to share manually.
+
+---
+
+## Open Share Moment Dialog
+
+The easiest way for an application to share media to a channel or DM is to use the `openShareMomentDialog` command. This command accepts a Discord CDN `mediaUrl` (eg `https://cdn.discordapp.com/attachments/...`) and opens a dialog on the discord client that allows the user to select channels, DMs, and GDMs to share to. This requires no additional OAuth scopes, but does require the application to be authenticated.
+
+Since `mediaUrl` must be a Discord CDN URL, it is encouraged to use the activities attachment API endpoint (`discord.com/api/applications/${applicationId}/attachment`) to create an ephemeral CDN URL. This endpoint accepts bearer tokens for any scopes, so it can be called from the application client using the authorized user's bearer token. The endpoint returns a serialized attachment, which includes a `url` attribute, which should then be passed to the DiscordSDK command as `mediaUrl`.
+
+#### Usage
+
+```javascript
+import {discordSdk} from './wherever-you-initialize-your-sdk';
+import {accessToken} from './wherever-you-store-your-access-token';
+
+// some image
+const imageURL = 'https://i.imgur.com/vaSWuKr.gif';
+
+// get image data
+const response = await fetch(imageURL);
+const blob = await response.blob();
+const mimeType = blob.type;
+
+// image data as buffer
+const buf = await blob.arrayBuffer();
+
+// image as file
+const imageFile = new File([buf], 'example.gif', {type: mimeType});
+
+const body = new FormData();
+body.append('file', imageFile);
+
+const attachmentResponse = await fetch(`${env.discordAPI}/applications/${env.applicationId}/attachment`, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ body,
+});
+const attachmentJson = await attachmentResponse.json();
+
+// mediaUrl is an ephemeral Discord CDN URL
+const mediaUrl = attachmentJson.attachment.url;
+
+// opens dialog in Discord client
+await discordSdk.commands.openShareMomentDialog({mediaUrl});
+```
+
+User Experience
+
+
+
+---
+
+## Setting Up an Entry Point Command
+
+An [Entry Point command](/docs/interactions/application-commands#entry-point-commands) is required for users to be able to launch your Activity from the [App Launcher menu](https://support.discord.com/hc/articles/21334461140375-Using-Apps-on-Discord#h_01HRQSA6C8TRHS722P1H3HW1TV) in Discord.
+
+When you enable Activities in your [app's settings](http://discord.com/developers/applications), a [default Entry Point command](/docs/interactions/application-commands#default-entry-point-command) is automatically created for your app. The default Entry Point command will use the `DISCORD_LAUNCH_ACTIVITY` (`2`) [handler type](/docs/interactions/application-commands#application-command-object-entry-point-command-handler-types), which means that Discord automatically launches your Activity for the user and posts a follow-up message into the channel where it was launched from.
+
+If you want to handle sending messages yourself, you can update the handler to be `APP_HANDLER` (`1`). Details about Entry Point command handlers is in the [Entry Point command documentation](/docs/interactions/application-commands#entry-point-handlers).
+
+#### Customizing the Default Entry Point Command
+
+Entry Point commands can be customized in the same way as other [commands](/docs/interactions/application-commands). Since Entry Point commands can only be [global](/docs/interactions/application-commands#making-a-global-command), you'll use the HTTP endpoints for global commands:
+- **Edit your existing Entry Point command's name or details** using the [Edit Global Application Command](/docs/interactions/application-commands#edit-global-application-command) endpoint. If you don't know the ID for your app's Entry Point command, use the [Get Global Application Commands](/docs/interactions/application-commands#get-global-application-commands) endpoint to retrieve it.
+- **Make a different (option-less) command your Entry Point command** by updating its [command `type`](/docs/interactions/application-commands#application-command-object-application-command-types) to `PRIMARY_ENTRY_POINT` (type `4`). Your app can only have one Entry Point command, so if your app already has one, you must first [delete](/docs/interactions/application-commands#delete-global-application-command) it or [update](/docs/interactions/application-commands#edit-global-application-command) its [command `type`](/docs/interactions/application-commands#application-command-object-application-command-types).
+
+#### Creating an Entry Point Command
+
+To create a new Entry Point command, you can call the [Create Global Application Command](/docs/interactions/application-commands#create-global-application-command) endpoint and set the [command `type`](/docs/interactions/application-commands#application-command-object-application-command-types) to `PRIMARY_ENTRY_POINT` (type `4`).
+
+Your command payload may look something like this:
+
+```json
+{
+ "name": "launch",
+ "description": "Launch Realms of Wumpus",
+ // PRIMARY_ENTRY_POINT is type 4
+ "type": 4,
+ // DISCORD_LAUNCH_ACTIVITY is handler value 2
+ "handler": 2,
+ // integration_types and contexts define where your command can be used (see below)
+ "integration_types": [0, 1],
+ "contexts": [0, 1, 2]
+}
+```
+
+In addition to the `type` and `handler` values, the command payload includes `integration_types` and `contexts` which let you configure when and where your command can be used:
+- `integration_types` defines the [installation contexts](/docs/resources/application#installation-context) where your command is available (to a server, to a user's account, or both). If you don't set `integration_types` when creating a command, it will default to your app's [currently-supported installation contexts](/docs/resources/application#setting-supported-installation-contexts).
+- `contexts` defines the [interaction contexts](/docs/interactions/receiving-and-responding#interaction-object-interaction-context-types) where a command can be run in Discord (in a server, in a DM with your app, and/or in DMs and Group DMs with other users).
+
+Details about both of these fields are in the [command contexts](/docs/interactions/application-commands#contexts) documentation.
+
+---
+
+## Encourage Hardware Acceleration
+
+Activities that are compute intensive may benefit from encouraging users to enable hardware acceleration. When an application invokes the `encourageHardwareAcceleration` command the current status of the setting will be returned and the user will be prompted to update the setting, if applicable.
+
+Users will see a modal inside the Discord app if Hardware Acceleration is disabled, encouraging them to change the setting. By clicking **Don't show me this again** they will not see the modal for _any application_ on this device again.
+
+#### Best Practices
+
+Switching the Hardware Acceleration setting causes the Discord client to quit and re-launch, so it is best practice to invoke this command as soon as possible, so users do not begin the experience of an application before restarting. Ideally, this is immediately after `await discordSdk.ready()`.
+
+#### Usage
+
+```javascript
+import {DiscordSDK} from '@discord/embedded-app-sdk';
+const discordSdk = new DiscordSDK(clientId);
+await discordSdk.ready();
+const {enabled} = await discordSdk.commands.encourageHardwareAcceleration();
+console.log(`Hardware Acceleration is ${enabled === true ? 'enabled' : 'disabled'}`);
+```
+
+#### User Experience
+
+
diff --git a/docs/activities/overview.mdx b/docs/activities/overview.mdx
index 5ebce3096e..2cc4ce49d2 100644
--- a/docs/activities/overview.mdx
+++ b/docs/activities/overview.mdx
@@ -38,7 +38,7 @@ Activities are primarily opened when users invoke your app's [Entry Point comman
When you enable Activities for your app, a [default Entry Point command](/docs/interactions/application-commands#default-entry-point-command) called "Launch" is created for you. By default, Discord automatically handles opening your Activity when your Entry Point command is run by a user.
-Read more about setting up Entry Point commands in the [development guide](/docs/activities/development-guides#setting-up-an-entry-point-command).
+Read more about setting up Entry Point commands in the [development guide](/docs/activities/development-guides/user-actions#setting-up-an-entry-point-command).
### Interaction Response
@@ -54,7 +54,7 @@ The sections below provide an overview of the Embedded App SDK, but technical de
### Embedded App SDK
-The [Embedded App SDK](/docs/developer-tools/embedded-app-sdk) handles all of the communication between Discord and your app, making it easier to do common tasks like managing connected users, supporting mobile clients, and debugging your Activity.
+The [Embedded App SDK](/docs/developer-tools/embedded-app-sdk) handles all of the communication between Discord and your app, making it easier to do common tasks like managing connected users, supporting mobile clients, and debugging your Activity.
The Embedded App SDK offers different events and commands to handle the communication between Discord and your Activity, which are covered more below.
diff --git a/docs/change-log/2024-07-17-activities-proxy-csp-update.md b/docs/change-log/2024-07-17-activities-proxy-csp-update.md
index 55604e2390..473931e1fc 100644
--- a/docs/change-log/2024-07-17-activities-proxy-csp-update.md
+++ b/docs/change-log/2024-07-17-activities-proxy-csp-update.md
@@ -15,6 +15,6 @@ our CSP will be updated as follows:
* Only allowed paths on `cdn.discordapp.com` and `media.discordapp.net` will be permitted such as `/attachments/`, `/icons/`, and `/avatars/`.
* nested child iframes must also mount paths prepended by `/.proxy/`
-As of [embedded-app-sdk v1.4.0](https://github.com/discord/embedded-app-sdk/releases/tag/v1.4.0) we have updated `patchUrlMappings` to automatically route requests through `/.proxy/`, so updating your SDK version calling `patchUrlMappings` is a good first step. If you are unfamiliar with `patchUrlMappings`, please consult the [documentation](/docs/activities/development-guides#using-external-resources).
+As of [embedded-app-sdk v1.4.0](https://github.com/discord/embedded-app-sdk/releases/tag/v1.4.0) we have updated `patchUrlMappings` to automatically route requests through `/.proxy/`, so updating your SDK version calling `patchUrlMappings` is a good first step. If you are unfamiliar with `patchUrlMappings`, please consult the [documentation](/docs/activities/development-guides/networking#using-external-resources).
All Application IDs created after `07/17/2024 12:00:00` UTC (applicationID greater than `1263102905548800000`) will also automatically have the new CSP applied. Testing your production code on a new application created after this date is a suggested way for developers to test compliance with this new CSP.
diff --git a/docs/change-log/2024-08-26-entry-point-commands.md b/docs/change-log/2024-08-26-entry-point-commands.md
index 99bf9f853a..6f868a962e 100644
--- a/docs/change-log/2024-08-26-entry-point-commands.md
+++ b/docs/change-log/2024-08-26-entry-point-commands.md
@@ -12,8 +12,8 @@ When creating or updating an Entry Point command, an [Entry Point handler](/docs
- If the value is `DISCORD_LAUNCH_ACTIVITY` (`2`), Discord will automatically handle the interaction and send a follow-up message to the channel where the Entry Point command was invoked from.
- If the value is `APP_HANDLER` (`1`), your app will receive an interaction token and will be responsible for responding to the interaction. In this case, you can launch your Activity using the `LAUNCH_ACTIVITY` (type `12`) [interaction callback](/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type).
-More details about Entry Point commands can be found in the [Application Commands documentation](/docs/interactions/application-commands#entry-point-commands) and in the [Activity development guide](/docs/activities/development-guides#setting-up-an-entry-point-command).
+More details about Entry Point commands can be found in the [Application Commands documentation](/docs/interactions/application-commands#entry-point-commands) and in the [Activity development guide](/docs/activities/development-guides/user-actions#setting-up-an-entry-point-command).
### Default Entry Point Commands
-Starting today, when you enable Activities in your [app's settings](http://discord.com/developers/applications), a [default Entry Point command](/docs/interactions/application-commands#default-entry-point-command) called "Launch" will automatically be created for your app. This can be customized or deleted like other commands if you want to update the name or handler type.
\ No newline at end of file
+Starting today, when you enable Activities in your [app's settings](http://discord.com/developers/applications), a [default Entry Point command](/docs/interactions/application-commands#default-entry-point-command) called "Launch" will automatically be created for your app. This can be customized or deleted like other commands if you want to update the name or handler type.
diff --git a/docs/change-log/2024-09-26-activities-general-availability.md b/docs/change-log/2024-09-26-activities-general-availability.md
index b74444e6ad..b4488cb0e8 100644
--- a/docs/change-log/2024-09-26-activities-general-availability.md
+++ b/docs/change-log/2024-09-26-activities-general-availability.md
@@ -14,11 +14,11 @@ Following up on [the rollout of the App Launcher](https://discord.com/blog/disco
Since the developer preview was announced, there have been a few important API updates:
- Activities can now enable and implement monetization features, and [`getEntitlements`](/docs/developer-tools/embedded-app-sdk#getentitlements),[`getSkus`](/docs/developer-tools/embedded-app-sdk#getskus), and [`startPurchase`](/docs/developer-tools/embedded-app-sdk#startpurchase) are generally available in the Embedded App SDK.
-- New [Get Application Activity Instance](/docs/resources/application#get-application-activity-instance) endpoint to make [managing Activity instances](/docs/activities/development-guides#activity-instance-management) easier.
-- Apps with Activities can create an [Entry Point command (type `4`)](/docs/interactions/application-commands#entry-point-commands), which are the primary entry point for Activities in the App Launcher. When new apps enable Activities, a [default Entry Point command](/docs/interactions/application-commands#default-entry-point-command) will be created for the app. Read the [original change log](/docs/change-log#entry-point-commands) and the [Entry Point command guide](/docs/activities/development-guides#setting-up-an-entry-point-command) for details.
+- New [Get Application Activity Instance](/docs/resources/application#get-application-activity-instance) endpoint to make [managing Activity instances](/docs/activities/development-guides/multiplayer-experience#activity-instance-management) easier.
+- Apps with Activities can create an [Entry Point command (type `4`)](/docs/interactions/application-commands#entry-point-commands), which are the primary entry point for Activities in the App Launcher. When new apps enable Activities, a [default Entry Point command](/docs/interactions/application-commands#default-entry-point-command) will be created for the app. Read the [original change log](/docs/change-log#entry-point-commands) and the [Entry Point command guide](/docs/activities/development-guides/user-actions#setting-up-an-entry-point-command) for details.
- Activities can now be launched in response to interactions using the `LAUNCH_ACTIVITY` (type `12`) [interaction callback type](/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type) for `APPLICATION_COMMAND`, `MESSAGE_COMPONENT`, and `MODAL_SUBMIT` [interaction types](/docs/interactions/receiving-and-responding#interaction-object-interaction-type).
- Apps can now be installed to users (in addition to servers). After [setting up your installation contexts](/docs/resources/application#setting-supported-installation-contexts), make sure to request the `application.commands` scope when authorizing with users to make sure your Activity is available for them across their Discord servers, DMs, and Group DMs.
-- In August, there were updates to the Content Security Policy (CSP) for Activities that limits how you can make requests to external resources when building Activities. Read [the change log](/docs/change-log#activities-proxy-csp-update) and the guide on [using external resources](/docs/activities/development-guides#using-external-resources) for details.
+- In August, there were updates to the Content Security Policy (CSP) for Activities that limits how you can make requests to external resources when building Activities. Read [the change log](/docs/change-log#activities-proxy-csp-update) and the guide on [using external resources](/docs/activities/development-guides/networking#using-external-resources) for details.
### Documentation Updates
@@ -26,7 +26,7 @@ We’ve also added and improved the documentation for Activities and the Embedde
- New reference documentation for [Monetization](/docs/monetization/overview) SDK commands: [`getEntitlements`](/docs/developer-tools/embedded-app-sdk#getentitlements),[`getSkus`](/docs/developer-tools/embedded-app-sdk#getskus), and [`startPurchase`](/docs/developer-tools/embedded-app-sdk#startpurchase)
- Updated [Embedded App SDK Reference](/docs/developer-tools/embedded-app-sdk) documentation that adds signatures and arguments
-- Updated development guides for [Activity Instance Management](/docs/activities/development-guides#activity-instance-management) and [Activity Proxy Considerations](/docs/activities/development-guides#activity-proxy-considerations) when using external resources
+- Updated development guides for [Activity Instance Management](/docs/activities/development-guides/multiplayer-experience#activity-instance-management) and [Activity Proxy Considerations](/docs/activities/development-guides/networking#activity-proxy-considerations) when using external resources
- New guide on implementing [In-App Purchases (IAP) for Activities](/docs/monetization/implementing-iap-for-activities)
- New guides for [Verification and Discovery Surfaces](/docs/discovery/overview)
-- New guide on [Using Rich Presence with the Embedded App SDK](/docs/rich-presence/using-with-the-embedded-app-sdk)
\ No newline at end of file
+- New guide on [Using Rich Presence with the Embedded App SDK](/docs/rich-presence/using-with-the-embedded-app-sdk)
diff --git a/docs/interactions/application-commands.mdx b/docs/interactions/application-commands.mdx
index fa81d64484..f3003e9eb2 100644
--- a/docs/interactions/application-commands.mdx
+++ b/docs/interactions/application-commands.mdx
@@ -155,7 +155,7 @@ An app can have the following number of commands:
- 5 global `MESSAGE` commands
- 1 global `PRIMARY_ENTRY_POINT` command
-For all command types except `PRIMARY_ENTRY_POINT`, you can have the same amount of guild-specific commands per guild.
+For all command types except `PRIMARY_ENTRY_POINT`, you can have the same amount of guild-specific commands per guild.
:::danger
There is a global rate limit of 200 application command creates per day, per guild
@@ -1019,7 +1019,7 @@ When a user invokes an app's Entry Point command, the value of [`handler`](/docs
When you enable Activities, an Entry Point command (named "Launch") is automatically created for your app with `DISCORD_LAUNCH_ACTIVITY` (`2`) set as the [Entry Point handler](/docs/interactions/application-commands#entry-point-handlers). You can retrieve details for the automatically-created command, like its ID, by calling the [Get Global Application Commands](/docs/interactions/application-commands##get-global-application-commands) endpoint and looking for the "Launch" command.
-Details about updating or replacing the default Entry Point command is in the [Setting Up an Entry Point Command guide](/docs/activities/development-guides#setting-up-an-entry-point-command).
+Details about updating or replacing the default Entry Point command is in the [Setting Up an Entry Point Command guide](/docs/activities/development-guides/user-actions#setting-up-an-entry-point-command).
## Autocomplete
diff --git a/docs/resources/application.mdx b/docs/resources/application.mdx
index a795254107..c0b8509981 100644
--- a/docs/resources/application.mdx
+++ b/docs/resources/application.mdx
@@ -265,7 +265,7 @@ All parameters to this endpoint are optional
## Get Application Activity Instance
/applications/[\{application.id\}](/docs/resources/application#application-object)/activity-instances/[\{instance_id\}](/docs/resources/application#get-application-activity-instance-activity-instance-object)
-Returns a serialized activity instance, if it exists. Useful for [preventing unwanted activity sessions](/docs/activities/development-guides#preventing-unwanted-activity-sessions).
+Returns a serialized activity instance, if it exists. Useful for [preventing unwanted activity sessions](/docs/activities/development-guides/multiplayer-experience#preventing-unwanted-activity-sessions).
###### Example Activity Instance
@@ -286,13 +286,13 @@ Returns a serialized activity instance, if it exists. Useful for [preventing unw
###### Activity Instance Object
-| Field | Type | Description |
-|----------------|-------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|
-| application_id | snowflake | [Application](/docs/resources/application#application-object) ID |
-| instance_id | string | Activity [Instance](/docs/activities/development-guides#activity-instance-management) ID |
-| launch_id | snowflake | Unique identifier for the launch |
-| location | [Activity Location](/docs/resources/application#get-application-activity-instance-activity-location-object) | Location the instance is running in |
-| users | array of snowflakes, [user](/docs/resources/user#user-object) IDs | IDs of the Users currently connected to the instance |
+| Field | Type | Description |
+|----------------|-------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
+| application_id | snowflake | [Application](/docs/resources/application#application-object) ID |
+| instance_id | string | Activity [Instance](/docs/activities/development-guides/multiplayer-experience#activity-instance-management) ID |
+| launch_id | snowflake | Unique identifier for the launch |
+| location | [Activity Location](/docs/resources/application#get-application-activity-instance-activity-location-object) | Location the instance is running in |
+| users | array of snowflakes, [user](/docs/resources/user#user-object) IDs | IDs of the Users currently connected to the instance |
###### Activity Location Object
diff --git a/docs/rich-presence/using-with-the-embedded-app-sdk.mdx b/docs/rich-presence/using-with-the-embedded-app-sdk.mdx
index c215b87469..d04a93cf81 100644
--- a/docs/rich-presence/using-with-the-embedded-app-sdk.mdx
+++ b/docs/rich-presence/using-with-the-embedded-app-sdk.mdx
@@ -110,7 +110,7 @@ The following example only focuses on using `setActivity()`. Follow the [Buildin
:::
```js
await discordSdk.commands.setActivity({
- activity: {
+ activity: {
type: 0,
details: 'Traveling with a group',
state: 'In Mainframe',
@@ -133,7 +133,7 @@ await discordSdk.commands.setActivity({
## Using External Custom Assets
-Typically when building an Activity, you need to be aware of the proxy and [how to use external resources](/docs/activities/development-guides#using-external-resources). However, lucikly for you (and the writer of this guide), image URLs in fields for features like Rich Presence don't need to jump through any extra hoops.
+Typically when building an Activity, you need to be aware of the proxy and [how to use external resources](/docs/activities/development-guides/networking#using-external-resources). However, lucikly for you (and the writer of this guide), image URLs in fields for features like Rich Presence don't need to jump through any extra hoops.
As mentioned in the [Rich Presence overview](/docs/rich-presence/overview#assets), you have more than 300 custom assets or if you want to use your stored images from somewhere else, you can specify an external URL for `large_image` or `small_image` within the `assets` object.
@@ -144,7 +144,7 @@ The following example only focuses on using `setActivity()`. Follow the [Buildin
:::
```js
await discordSdk.commands.setActivity({
- activity: {
+ activity: {
type: 2,
state: 'Broken Hearts and Code (club edit)',
details: 'DJ Wump',