PresenceJS
Modby jcyyy_
Discord Rich Presence & Webhooks for Forge 1.20.1 with deep KubeJS customization hooks.
Type
Mod
CurseForge Downloads
2,400
CurseForge ID
1498700
Last Updated
Jul 28, 2026
Description
PresenceJS
PresenceJS is a Forge 1.20.1 client-side mod that adds Discord Rich Presence to Minecraft and exposes a broad scripting surface for KubeJS.
It uses a pure-Java Discord RPC backend designed for Minecraft mods rather than a native JNI bridge.
What it does
- Connects the Minecraft client to Discord Rich Presence
- Exposes Discord webhook REST operations, including message execution, webhook management, Slack-compatible payloads, GitHub-compatible payloads, and webhook message editing/deletion
- Ships with automatic menu / singleplayer / multiplayer presence defaults
- Exposes a mutable Rich Presence object to KubeJS every update cycle
- Lets scripts control:
- Discord application ID
- activity type
- details and state
- timestamps
- large/small images
- buttons
- Emits KubeJS events for:
- presence building
- Discord ready/disconnect lifecycle
- asynchronous webhook responses and errors
Requirements
- Minecraft 1.20.1
- Forge 47.4.0
- Java 17
- KubeJS 2001.6.5-build.16 for scripting integration
- A Discord application with Rich Presence assets configured in the Discord Developer Portal
Configuration
PresenceJS registers a client config file. The most important setting is the Discord application ID:
- clientId
- webhookEnabled
- defaultLargeImageKey
- defaultSmallImageKey
- menuDetails
- singleplayerDetails
- multiplayerDetails
You can leave clientId empty and provide it entirely from KubeJS instead. Webhook functionality is disabled by default and must be enabled explicitly through webhookEnabled before any webhook requests can be sent.
KubeJS bindings
PresenceJS adds a client-side global binding named PresenceJS and an event group named PresenceJSEvents.
Main binding methods
- PresenceJS.activity() → create a new mutable activity object
- PresenceJS.button(label, url) → create a button object
- PresenceJS.image(key, text) → create an image object
- PresenceJS.getContext() → inspect the latest client snapshot
- PresenceJS.getBaseActivity() / setBaseActivity(activity)
- PresenceJS.clearBaseActivity()
- PresenceJS.getLastSentActivity()
- PresenceJS.getCurrentDiscordUser()
- PresenceJS.getConnectionState() / getConnectionMessage()
- PresenceJS.isConnected()
- PresenceJS.isEnabled() / setEnabled(enabled)
- PresenceJS.refresh()
- PresenceJS.disconnect()
- PresenceJS.jsonObject() / jsonArray() / json(jsonString) → create or parse Gson JSON payloads for webhook requests
- PresenceJS.webhookMessage() / webhookEmbed() / webhookField(name, value) → build common webhook message payloads without hand-writing raw JSON
- PresenceJS.webhooks() → access the Discord webhook service
Client events
- PresenceJSEvents.build(event => {})
- PresenceJSEvents.ready(event => {})
- PresenceJSEvents.disconnected(event => {})
- PresenceJSEvents.webhookResponse(event => {})
- PresenceJSEvents.webhookError(event => {})
Example build handler:
PresenceJSEvents.build(event => { const presence = event.getPresence() const context = event.getContext() presence.setClientId('123456789012345678') presence.setDetails('Playing Minecraft') presence.setState(context && context.getSingleplayer() ? 'Singleplayer world' : 'In-game') presence.setLargeImage(PresenceJS.image('minecraft', 'Minecraft 1.20.1')) presence.setSmallImage(PresenceJS.image('kubejs', 'Customized with KubeJS')) presence.setButtons([ PresenceJS.button('Download Pack', 'https://example.com'), PresenceJS.button('Join Discord', 'https://discord.gg/example') ]) })Webhooks
PresenceJS exposes Discord's webhook HTTP surface through PresenceJS.webhooks().
Request factories
The webhook service includes typed request builders for the documented Discord webhook endpoints:
- createWebhook(channelId)
- getChannelWebhooks(channelId)
- getGuildWebhooks(guildId)
- getWebhook(webhookId)
- getWebhookWithToken(webhookId, webhookToken) or getWebhookWithToken(webhookUrl)
- modifyWebhook(webhookId)
- modifyWebhookWithToken(webhookId, webhookToken) or modifyWebhookWithToken(webhookUrl)
- deleteWebhook(webhookId)
- deleteWebhookWithToken(webhookId, webhookToken) or deleteWebhookWithToken(webhookUrl)
- executeWebhook(webhookId, webhookToken) or executeWebhook(webhookUrl)
- executeSlackCompatibleWebhook(webhookId, webhookToken) or executeSlackCompatibleWebhook(webhookUrl)
- executeGithubCompatibleWebhook(webhookId, webhookToken) or executeGithubCompatibleWebhook(webhookUrl)
- getWebhookMessage(webhookId, webhookToken, messageId) or getWebhookMessage(webhookUrl, messageId)
- editWebhookMessage(webhookId, webhookToken, messageId) or editWebhookMessage(webhookUrl, messageId)
- deleteWebhookMessage(webhookId, webhookToken, messageId) or deleteWebhookMessage(webhookUrl, messageId)
- request(method, pathOrUrl) for raw access to Discord webhook API paths or an official Discord webhook URL
For real Discord incoming webhooks, the URL overloads are the intended convenience surface. createWebhook(channelId) still uses a channel id because that endpoint creates the webhook before a webhook URL exists. PresenceJS rejects non-Discord hosts, inline query strings, fragments, and non-webhook API routes. Use .query(...), .waitForResponse(...), .threadId(...), and .withComponents(...) to add query parameters safely. PresenceJS also blocks webhook execution entirely unless the client config setting webhookEnabled is turned on.
Each DiscordWebhookRequest supports:
- .authorization(rawHeader)
- .botToken(token)
- .bearerToken(token)
- .auditLogReason(reason)
- .query(name, value)
- .waitForResponse(boolean)
- .threadId(threadId)
- .withComponents(boolean)
- .header(name, value)
- .json(JsonElement) or .message(DiscordWebhookMessage)
- .addFile(webhooks.file(path))
For common execute payloads, PresenceJS also exposes typed KubeJS builders for:
- message content, username, avatar_url, tts, flags, and thread_name
- embed title, description, url, color, fields, author, footer, timestamp, image, and thumbnail
Raw JSON is still supported for webhook features that are not wrapped yet.
The service supports both synchronous and asynchronous execution:
- webhooks.execute(request) returns a DiscordWebhookResponse
- webhooks.submit(request) queues the request on a background thread and returns a request id
- webhooks.cancel(requestId) cancels a queued request when possible
KubeJS examples
Execute a webhook message and wait for the created message object:
const webhooks = PresenceJS.webhooks() const webhookUrl = 'https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN' const message = PresenceJS.webhookMessage() message.setContent('Hello from PresenceJS') message.setUsername('PresenceJS') const embed = PresenceJS.webhookEmbed() embed.setTitle('Webhook builder') embed.setDescription('Built from typed KubeJS helpers') const field = PresenceJS.webhookField('Mode', 'Typed message body') field.setInline(true) embed.addField(field) message.addEmbed(embed) const response = webhooks.execute( webhooks.executeWebhook(webhookUrl) .waitForResponse(true) .message(message) ) if (response.isSuccess()) { console.log(response.getBody()) }Create or manage webhooks with an authenticated bot token:
const webhooks = PresenceJS.webhooks() const body = PresenceJS.json('{"name":"PresenceJS Hook"}') const response = webhooks.execute( webhooks.createWebhook('CHANNEL_ID') .botToken('YOUR_BOT_TOKEN') .auditLogReason('Provisioned from PresenceJS') .json(body) )Send a webhook asynchronously and handle completion in KubeJS events:
const webhooks = PresenceJS.webhooks() const webhookUrl = 'https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN' const message = PresenceJS.webhookMessage() message.setContent('Async message') const requestId = webhooks.submit( webhooks.executeWebhook(webhookUrl) .waitForResponse(true) .message(message) ) PresenceJSEvents.webhookResponse(event => { if (event.getRequestId() !== requestId) return console.log(event.getResponse().getStatusCode()) }) PresenceJSEvents.webhookError(event => { if (event.getRequestId() !== requestId) return console.error(event.getMessage()) })For payloads with files, use webhooks.file(path) or webhooks.textFile(filename, content) and add the parts to the request with .addFile(...). For unsupported webhook properties, you can still drop to raw JSON with PresenceJS.json(...) plus .json(...). JSON request bodies and response bodies use Gson types, and com.google.gson is whitelisted for client-side KubeJS scripts.
Recommended Gear
AffiliateHardware and extras that pair well with this mod.
As an Amazon Associate I earn from qualifying purchases.
Similar Mods
RFTS Additions
A simple mod adding content for the Reach For The Skies modpack
Ars Technic
addon for Ars Nouveau
Create Cosmonautics
Fulfill your childhood dream by exploring space!
XP Shop
A mod that turns the most readily available resource into a currency: XP!
Bee Angry-est
Tweaks to (angry) bees.
Beyond The End - Forgotten Tome Menu
Adds a new Inventory Menu Button which allows to teleport to The Forgotten Realm dimension