Presence Class
The Presence class is the main class for creating activities. It provides methods for setting the activity data, handling events, and interacting with the PreMiD extension.
Creating a Presence Instance
To create a new Presence instance, you need to import the Presence class and create a new instance with a client ID:
const presence = new Presence({
clientId: 'your_client_id'
})The client ID is not automatically generated. You must obtain your own client ID from Discord's Developer Portal and enter it yourself.
The UpdateData Event
The UpdateData event is the most important event for a Presence. It is fired regularly by the PreMiD extension, and it's where you should update your activity data.
presence.on('UpdateData', async () => {
// Update your activity data here
})Setting Activity Data
To set the activity data, you need to create a PresenceData object and pass it to the setActivity method:
const browsingTimestamp = Math.floor(Date.now() / 1000)
enum ActivityAssets {
Logo = 'https://example.com/logo.png',
}
presence.on('UpdateData', async () => {
const presenceData: PresenceData = {
largeImageKey: ActivityAssets.Logo,
details: 'Browsing Example.com',
state: 'Homepage',
startTimestamp: browsingTimestamp
}
presence.setActivity(presenceData)
})PresenceData Properties
The PresenceData object can have the following properties:
| Property | Type | Description |
|---|---|---|
name | string | The name of the activity |
type | ActivityType | The type of activity |
stateDisplayType | StatusDisplayType | Controls which field is displayed in the user's status text in the member list |
details | string | The first line of the activity |
detailsUrl | string | The URL the user will be redirected to when clicking the details text |
state | string | The second line of the activity |
stateUrl | string | The URL the user will be redirected to when clicking the state text |
startTimestamp | number | The time when the activity started (Unix timestamp in milliseconds) |
endTimestamp | number | The time when the activity will end (Unix timestamp in milliseconds) |
largeImageKey | string | The key of the large image. Preferably a direct URL to an image (e.g., https://example.com/logo.png) |
largeImageUrl | string | The URL the user will be redirected to when clicking the large image |
largeImageText | string | The text that appears when hovering over the large image |
smallImageKey | string | The key of the small image. Preferably a direct URL to an image (e.g., https://example.com/icon.png) |
smallImageUrl | string | The URL the user will be redirected to when clicking the small image |
smallImageText | string | The text that appears when hovering over the small image |
buttons | ButtonData[] | An array of buttons (max 2) |
party | PartyData | A party size shown on the activity, e.g. "2 of 5". Only honored on ActivityType.Playing |
Activity Types
You can set the type of activity using the type property:
presenceData.type = ActivityType.WatchingThe available activity types are:
| Type | Description |
|---|---|
ActivityType.Playing | Shows as "Playing [name]" |
ActivityType.Listening | Shows as "Listening to [name]" |
ActivityType.Watching | Shows as "Watching [name]" |
ActivityType.Competing | Shows as "Competing in [name]" |
Special Season and Episode Formatting
When using ActivityType.Watching, you can display a special season and episode indicator by setting the largeImageText property to follow the pattern "word digit, digit". For example: "Season 2, Episode 5". Discord will automatically detect this pattern and display it as "S2E5" in the activity.
The pattern must follow the exact structure: word, space, digit, comma, space, digit. Discord will automatically convert this to "SxEy" format regardless of what word you use.
See the PresenceData documentation for more details.
Buttons
You can add up to two buttons to your activity:
presenceData.buttons = [
{
label: 'Visit Website',
url: 'https://example.com'
},
{
label: 'View Page',
url: document.location.href
}
]Each button has a label and a url property. The label is the text that appears on the button, and the url is the link that opens when the button is clicked.
Button Guidelines
Buttons must follow our Guidelines. Specifically:
- Redirects to main pages are prohibited
- Promoting websites is prohibited
- They can't display information you couldn't fit in other fields
- Redirecting directly to audio/video streams is prohibited
Timestamps
You can add timestamps to show how long the user has been doing an activity or how much time is left:
// Show elapsed time
presenceData.startTimestamp = Date.now()
// Show remaining time
presenceData.endTimestamp = Date.now() + 60000 // 1 minute from nowYou can also use the getTimestampsFromMedia utility function to calculate timestamps for media:
import { getTimestampsFromMedia } from 'premid'
const video = document.querySelector('video')
[presenceData.startTimestamp, presenceData.endTimestamp] = getTimestampsFromMedia(video)Party Size
For activities with a "Playing" type, you can show a party size — rendered by Discord as e.g. "2 of 5":
presenceData.type = ActivityType.Playing
presenceData.party = {
partySize: 2,
maxPartySize: 5
}partySize and maxPartySize are required; partyId is optional and generated automatically when omitted. The party is only honored on ActivityType.Playing — it is ignored for other activity types.
Clearing Activity
If you want to clear the activity, you can use the clearActivity method:
presence.clearActivity()Getting Settings
If your activity has settings, you can get their values using the getSetting method:
const showButtons = await presence.getSetting<boolean>('showButtons')
const displayFormat = await presence.getSetting<number>('displayFormat')Reading Page Data and Requests
Some sites don't expose everything you need in the DOM. Two methods let you reach into the page itself:
execInPage— run a function in the page's own realm and get its return value (call the site's own functions, read its globals).onRequest— read the data the page fetches from its own API, instead of scraping the DOM.
Both were added in extension 2.14. Activities run on every installed version, so always guard them with the bundled supports helper — on older extensions the method is missing, and feature-detecting lets your activity degrade gracefully instead of throwing.
import { supports } from 'premid'
// Run code inside the page and get a serializable result back
if (supports(presence, 'execInPage')) {
const track = await presence.execInPage(() => {
const state = window.spotifyPlayer.getCurrentState()
return { title: state.track.name, paused: state.paused }
})
}
// Read data straight from the site's own API responses
if (supports(presence, 'onRequest')) {
presence.onRequest({ url: '/api/now-playing', method: 'GET' }, (request) => {
if (!request.responseBody)
return
const data = JSON.parse(request.responseBody)
// use data...
})
}TIP
The eslint plugin will flag an unguarded execInPage/onRequest call and can autofix it by wrapping it in a supports() check. See the API reference for the full options (declarative execInPage specs, request filters, the InterceptedRequest shape, and unsubscribing).
Complete Example
Here's a complete example of a Presence class implementation:
const presence = new Presence({
clientId: 'your_client_id'
})
const browsingTimestamp = Math.floor(Date.now() / 1000)
enum ActivityAssets {
Logo = 'https://example.com/logo.png',
}
presence.on('UpdateData', async () => {
// Get settings
const showButtons = await presence.getSetting<boolean>('showButtons')
const showTimestamp = await presence.getSetting<boolean>('showTimestamp')
// Get page information
const { pathname } = document.location
// Create the base presence data
const presenceData: PresenceData = {
largeImageKey: ActivityAssets.Logo,
}
// Only show activity on public pages, not private/sensitive ones
if (pathname === '/') {
presenceData.details = 'Browsing Example.com'
presenceData.state = 'Homepage'
}
else if (pathname.includes('/about')) {
presenceData.details = 'Reading about Example.com'
presenceData.state = 'About page'
}
else if (pathname.includes('/contact')) {
presenceData.details = 'Contacting Example.com'
presenceData.state = 'Contact page'
}
else if (pathname.includes('/login') || pathname.includes('/signup') || pathname.includes('/account')) {
// Don't show activity on private/login pages for privacy
// details will remain undefined
}
else {
presenceData.details = 'Browsing Example.com'
presenceData.state = 'Exploring'
}
// Add timestamp if enabled
if (showTimestamp) {
presenceData.startTimestamp = browsingTimestamp
}
// Add buttons if enabled
if (showButtons) {
presenceData.buttons = [
{
label: 'Visit Website',
url: 'https://example.com'
},
{
label: 'View Page',
url: document.location.href
}
]
}
// Set the activity
if (presenceData.details) {
presence.setActivity(presenceData)
}
else {
presence.clearActivity()
}
})Code Guidelines
When implementing your Presence class, make sure to follow our Guidelines for code requirements. These include:
- Using native functions when available
- Supporting the primary language of the website
- Using smallImageKey and smallImageText appropriately
- Properly handling cookies and undefined values
Next Steps
Now that you understand how to use the Presence class, you can learn more about:
- Settings: Learn how to add customizable settings to your activity.
- iFrames: Learn how to gather information from iframes.
- Slideshows: Learn how to create a slideshow that alternates between different presence data.
