Reference
Developer API
Integrate your own plugin: check account links, listen for events, and send messages to Discord.
DiscordSynthesis exposes an Event API for reacting to chat, account linking, and config reloads, and an Action API for sending messages and looking up players. The API classes live in me.osayed.discordsynthesis.core.api and ship inside the normal plugin JAR.
This is a Java API for plugins running on Spigot/Paper, BungeeCord, or Velocity. It does not provide an HTTP endpoint for an external website or service; that needs an integration plugin of your own.
Setup
Which JAR do I need?
Use the same DiscordSynthesis JAR your server runs as a compile-only dependency. There is no separate API JAR or published Maven artifact. The me.osayed.discordsynthesis package is not relocated. Do not shade or bundle DiscordSynthesis into your plugin.
The examples below use version 13.4.10. Replace the version and filename with the release installed on your server.
mvn install:install-file -Dfile=DiscordSynthesis-13.4.10.jar -DgroupId=me.osayed -DartifactId=DiscordSynthesis -Dversion=13.4.10 -Dpackaging=jar<dependency>
<groupId>me.osayed</groupId>
<artifactId>DiscordSynthesis</artifactId>
<version>13.4.10</version>
<scope>provided</scope>
</dependency>dependencies {
compileOnly files('libs/DiscordSynthesis-13.4.10.jar')
}JDA dependencies
JDA is not bundled in the DiscordSynthesis JAR. On Spigot/Paper, DiscordSynthesis loads it through libraries: in plugin.yml. If you use JDA types such as EmbedBuilder, Message, Member, or TextChannel, compile against the same JDA version as your DiscordSynthesis release (6.1.2 for these examples) and make it available to your plugin at runtime too.
libraries:
- net.dv8tion:JDA:6.1.2The event system, IDSPlayer, and ChannelType need no extra dependency unless your code references JDA types.
Load order
DiscordSynthesis must load before your plugin calls it. Use a hard dependency if your integration requires it, or a soft dependency if your plugin can work without it.
depend: [DiscordSynthesis]
# For an optional integration, use instead:
# softdepend: [DiscordSynthesis]@Plugin(id = "myplugin", dependencies =
@Dependency(id = "discordsynthesis", optional = true))Getting the public API
Use DiscordSynthesisAPI.get() after DiscordSynthesis initializes. This entry point is the same on Spigot/Paper, BungeeCord, and Velocity. Integrations do not need to access the plugin's core, database, or event manager directly. Handle the checked Exception if the API is unavailable.
import me.osayed.discordsynthesis.core.api.DiscordSynthesisAPI;
DiscordSynthesisAPI api = DiscordSynthesisAPI.get();Requires DiscordSynthesis 13.4.10 or newer
The isLinked(UUID), getDiscordId(UUID), subscribe(Object), and unsubscribe(Object) methods below are available in 13.4.10 and newer. They are not available in older plugin JARs. On older releases, use the existing PlaceholderAPI link check.
Checking account linking
Call api.isLinked(UUID) to check whether a Minecraft account is linked to Discord. To retrieve the linked user ID, call api.getDiscordId(UUID), which returns java.util.Optional<String> and is empty for unlinked accounts. Both methods work for online and offline accounts and read the local cache without contacting Discord.
DiscordSynthesisAPI api = DiscordSynthesisAPI.get();
if (!api.isLinked(player.getUniqueId())) {
player.sendMessage("Link your Discord account with /connect!");
}
// Retrieve the linked ID when your integration needs it:
api.getDiscordId(player.getUniqueId()).ifPresent(discordId -> {
// Use the linked Discord user ID here.
});A null UUID is rejected with NullPointerException. If the linking database is unavailable, both methods throw IllegalStateException; skip the reminder when the lookup fails instead of treating that player as unlinked.
If your custom system supports PlaceholderAPI, %discordsynthesis_linked% already returns true or false. See Placeholders. To react to changes, subscribe to AccountLinkedEvent and AccountUnlinkedEvent below.
Linked and online are different checks
A linked account may be offline. IDSPlayer.isOnline() checks Minecraft connection status, not Discord presence. On a network, use the shared database configuration and allow for the cache refresh interval when a link changes on another node. See Database.
Event API
Register your listener with api.subscribe(listener) and remove it with api.unsubscribe(listener) when your plugin disables. These are DiscordSynthesis events; register them through the public API.
import me.osayed.discordsynthesis.core.api.DiscordSynthesisAPI;
import me.osayed.discordsynthesis.core.api.Subscribe;
import me.osayed.discordsynthesis.core.api.events.AccountLinkedEvent;
import me.osayed.discordsynthesis.core.api.events.AccountUnlinkedEvent;
import org.bukkit.plugin.java.JavaPlugin;
public class MyPlugin extends JavaPlugin {
private DiscordSynthesisAPI api;
private final LinkListener listener = new LinkListener();
@Override
public void onEnable() {
try {
api = DiscordSynthesisAPI.get();
api.subscribe(listener);
} catch (Exception e) {
getLogger().warning("DiscordSynthesis API is unavailable: " + e.getMessage());
getServer().getPluginManager().disablePlugin(this);
}
}
@Override
public void onDisable() {
if (api != null) api.unsubscribe(listener);
}
public class LinkListener {
@Subscribe
public void onLink(AccountLinkedEvent event) {
getLogger().info(event.getMinecraftName()
+ " linked to " + event.getDiscordId());
}
@Subscribe
public void onUnlink(AccountUnlinkedEvent event) {
getLogger().info(event.getMinecraftName() + " unlinked");
}
}
}Handler signatures and registration
- Use a public method returning
void, annotated withme.osayed.discordsynthesis.core.api.Subscribe, with exactly one concrete event parameter. - Declare handler methods directly on the listener class. Inherited handlers are not registered.
- Dispatch matches the exact event class. Handlers for base types
Event,GameEvent, andDiscordEventnever fire. subscribe()throwsIllegalArgumentExceptionfor a null or duplicate listener, no annotated handlers, or invalid event parameters.- Use
api.unsubscribe(listener)to remove only your own listener's handlers. Listener registration throwsIllegalStateExceptionif the event system is unavailable.
Priorities and cancellation
Set @Subscribe(priority = ListenerPriority.HIGH) using me.osayed.discordsynthesis.core.api.ListenerPriority. Handlers run in this order: LOWEST, LOW, NORMAL (default), HIGH, HIGHEST, MONITOR. Use MONITOR to observe the result without changing it.
Cancel or modify events at NORMAL priority or earlier. Cancelled events still reach later handlers, so check isCancelled() if you do not want to process an event another plugin cancelled.
import me.osayed.discordsynthesis.core.api.Subscribe;
import me.osayed.discordsynthesis.core.api.events.GameChatMessagePreProcessEvent;
@Subscribe
public void onChat(GameChatMessagePreProcessEvent event) {
if (event.isCancelled()) return;
if (event.getMessage().contains("spam")) {
event.setCancelled(true);
return;
}
event.setMessage(event.getMessage().replace("oldword", "newword"));
}Available events
Event types live in me.osayed.discordsynthesis.core.api.events. Every event exposes getTimestamp() (milliseconds) and getEventName(). Exceptions thrown by handlers are caught and logged without stopping the remaining listeners.
| Event | Cancellable | When it fires |
|---|---|---|
GameChatMessagePreProcessEvent | yes | Before Minecraft chat is filtered and sent to Discord. |
GameChatMessagePostProcessEvent | no | After a Minecraft chat message is sent to Discord. |
DiscordMessageReceivedEvent | yes | Before a received Discord message is relayed to Minecraft; requires Chat Sync. |
AccountLinkedEvent | no | When an account is linked via /connect, /ds link, or Discord verification. |
AccountUnlinkedEvent | no | When /disconnect unlinks an account, including staff unlinking. |
PluginReloadedEvent | no | After DiscordSynthesis reloads its configs. |
GameChatMessagePreProcessEvent
| Method | Returns / effect |
|---|---|
getPlayer() | The sender as IDSPlayer. |
getMessage() / setMessage(String) | Read or replace the raw message before the chat filter runs. |
getChannel() | The target Discord channel ID. |
isCancelled() / setCancelled(boolean) | Stop delivery to Discord. The Minecraft message still appears in-game. |
Note
Although setChannel(...) exists, the relay resolves its target before dispatching this event. Treat the channel as read-only for this integration.
GameChatMessagePostProcessEvent
| Method | Returns |
|---|---|
getPlayer() | The sender as IDSPlayer. |
getMessage() | The final message after filtering and formatting. |
getChannel() | The Discord channel ID. |
wasWebhook() | Whether delivery used a webhook rather than the bot. |
DiscordMessageReceivedEvent
| Method | Returns / effect |
|---|---|
getMessage() | The original JDA Message. |
getMember() | The JDA Member who sent it. |
getChannel() | The JDA TextChannel. |
getProcessedMessage() / setProcessedMessage(String) | Read or replace the relay text; initially the message's display content. |
isCancelled() / setCancelled(boolean) | Stop the message from reaching Minecraft. |
AccountLinkedEvent and AccountUnlinkedEvent
| Method | Returns | Events |
|---|---|---|
getMinecraftUUID() | Minecraft UUID. | Both |
getMinecraftName() | Minecraft username. | Both |
getDiscordId() | Linked or unlinked Discord user ID. | Both |
getDiscordName() | Discord username. | Linked only |
If a stored Minecraft UUID is malformed, unlinking still goes through but AccountUnlinkedEvent is skipped.
PluginReloadedEvent
This event has no additional methods and fires after configs reload. DiscordSynthesis can restart its core during a reload, and shutting down the event system clears listeners. If your integration survives a reload or disable/re-enable, obtain the current API and subscribe again once it is ready; a listener cleared during shutdown cannot receive the subsequent reload event.
Threading
Events fire on the thread that performed the original action. DiscordMessageReceivedEvent, and linking events triggered by Discord verification, arrive on JDA callback threads. Schedule back onto the platform's required thread before using thread-sensitive game APIs.
Bukkit.getScheduler().runTask(this, () -> {
// Use main-thread-only Bukkit APIs here.
});Action API
DiscordSynthesisAPI is a singleton initialized during startup. get() throws a checked Exception if the implementation is unavailable. Obtain it after DiscordSynthesis has started, and obtain the current instance again after a reload.
import me.osayed.discordsynthesis.core.api.DiscordSynthesisAPI;
import me.osayed.discordsynthesis.core.api.ChannelType;
DiscordSynthesisAPI api = DiscordSynthesisAPI.get();
api.sendMessage(ChannelType.CHATSYNC, "Hello from my plugin");Sending messages and embeds
import java.awt.Color;
import net.dv8tion.jda.api.EmbedBuilder;
EmbedBuilder embed = new EmbedBuilder()
.setTitle("Event started")
.setColor(Color.GREEN);
api.sendMessage(ChannelType.BROADCAST, embed);Both sendMessage overloads send asynchronously. The destination comes from the server's channel configuration. A call does nothing if the target channel is not configured.
| ChannelType | Configured channel |
|---|---|
CHATSYNC | Chat sync |
COMMANDLOG | Command log |
SERVERSTATUS | Server status |
CONSOLE | Console |
LEAVEJOIN | Player join / leave |
DEATHLOG | Death log |
REPORTS | Reports |
BROADCAST | Broadcasts |
Sending as a player with webhooks
Send under a player's name and skin, as chat sync does. The target channel must already have a webhook. sendChatSyncWebhook logs a warning and returns when none exists.
api.sendChatSyncWebhook(dsPlayer, "message text", "DisplayName");
api.sendCommandLogWebhook(dsPlayer, "/warp spawn", "DisplayName");Looking up Minecraft players
import me.osayed.discordsynthesis.core.IDSPlayer;
IDSPlayer player = api.getPlayer("Notch");
boolean online = player != null && player.isOnline();IDSPlayer is the platform-independent wrapper. It exposes getName(), getUniqueId(), getServerName(), getPlayerIP(), isOnline(), isPlayer(), setPlaceholders(String), connect(String), and playMentionSound(). Use a fresh lookup when checking connection status.
Compatibility
- The API is versioned with the plugin. Compile against the release your server runs.
- Keep your listeners registered once per instance and unsubscribe them when your plugin disables.
- Use the database link check for account linking and the player wrapper for Minecraft online status. Discord presence is a separate integration.
- On proxy networks, choose the node whose player and linking data your integration needs. See Proxy & networks.