Plugin developers
This guide is for plugin authors targeting Silk. It covers the metadata Silk understands, the compatibility behaviors it provides, and the patterns that travel best across Paper, Folia, and Silk.
Recommended metadata
Add this to plugin.yml:
silk-supported: true Silk also honors the legacy upstream key:
folia-supported: true Either flag is sufficient. Marking your plugin tells Silk you have at least reviewed the regional threading model and consider your plugin compatibility-aware.
Practical guidance
- Use the region, entity, async, and global schedulers intentionally instead of
BukkitScheduler.runTask*. - Avoid cross-region mutable state without explicit synchronization.
- Treat event handlers as potentially parallel - do not assume two events run on the same thread.
- Prefer async-safe APIs where teleport, chunk access, or world interaction can cross ownership boundaries (
teleportAsync,getChunkAtAsync). - Use
Bukkit.isOwnedByCurrentRegion(entityOrLocation)before mutating entity state in a generic listener.
What Silk tries to help with
- Loading legacy plugins in warning mode instead of refusing immediately.
- Bridging legacy sync scheduler calls to the Global Region Scheduler so plugins do not crash on
runTaskLater/runTaskTimer. - Bridging legacy sync
Entity.teleport()/Player.teleport()to the async variant when on the owning region. - Classifying compatibility failures with a clear operator-facing message and auto-disabling the offending plugin to keep the server stable.
- Documenting unsafe assumptions clearly.
- Exposing compatibility toggles for transition periods (
silkmc-compatibility.yml).
What Silk will not do
- Introduce a fake global main thread that mutates world state.
- Silently succeed when a cross-region write cannot be safely performed.
- Bypass region ownership checks to keep a plugin running.
Patterns that travel well
// Schedule on the entity's owning region
entity.getScheduler().run(plugin, task -> {
entity.setHealth(20);
}, null);
// Schedule global tick work
Bukkit.getGlobalRegionScheduler().run(plugin, task -> { /* ... */ });
// Async teleport (works on Paper, Folia, Silk)
player.teleportAsync(target).thenAccept(success -> { /* ... */ }); Patterns to avoid on Silk
// Legacy sync schedule with no region context.
// Silk bridges this through the Global Region Scheduler, but plugins should
// migrate to the region/entity/global schedulers explicitly.
Bukkit.getScheduler().runTaskTimer(plugin, () -> { /* ... */ }, 0L, 20L);
// Synchronous cross-world teleport in a tick callback.
// Silk will warn and return optimistically; the actual teleport runs async.
entity.teleport(somewhereInAnotherWorld); Detection
Plugins that want to identify the server platform can check Bukkit.getServer().getName() or
ServerBuildInfo. Silk reports brand SilkMC with key gg.tame:silkmc. The
upstream Folia classes still exist under their original packages for binary compatibility, so feature-detection
(rather than name-detection) remains the most reliable approach.