Configuration
ShadowControl
One panel for the whole suite: what you have, what is outdated, what to update.
Where the files are
plugins/ShadowControl/ The plugin creates them on first start and updates them by itself on new versions, keeping your changes.
Applying changes
/sctrl reload Reloads the files without restarting the server.
No setting found
Try another word, or search every plugin.
Settings
The files that decide how the plugin behaves. Start here.
config.yml
General settings 52 settings
config.ymlplugins/ShadowControl/config.yml File notes
The panel that holds the Shadow suite together. It tells you what you have installed, what is running, what needs updating, and it updates everything with one button.
The other files are:
catalog.yml the list of suite plugins lang/<locale>.yml the words menus/<name>.yml the layout of the panels
On update your files are moved to the new version automatically: what you changed stays, the old copies are in backup/<date>/, and if something needs your attention you will find PORTING-<version>.txt in the plugin folder.
A note on language. Every setting name and every comment in this file is in English, because a setting is read by whoever runs the server and those people come from everywhere. What your players read is a different matter: that lives in lang/, it is translated, and the language you serve by default is messages.default-locale below.
config-version1general1 keyserver-id"server-1"The name of this server. It is sent to the repository along with the licence, so whoever sells can tell one installation from another. Nothing about your players is ever sent.
Messages
Adding a language = dropping a file into lang/. It is picked up on its own, no setting to change.
messages2 keysdefault-locale"en_US"The language everyone gets unless their own client asks for another, and the language of the console. Ships with en_US and it_IT: set "it_IT" here for an Italian server.
follow-client-localetrueEach player reads in the language their client is set to, when a file for it exists. Turn this off to serve one language to everyone.
The limits. Do not remove them
A command that calls a paid service, open to anyone who joins, with no ceiling, is a credit card left on the counter: the first person who writes a macro empties it overnight. The limits stop three different things -- the macro, the persistent player, and the sum of a hundred reasonable ones.
Keep reading
-1 removes a limit. max-spend-per-day is in dollars and uses the prices written below: it is an estimate, not an invoice.
ai.limits4 keyscooldown-seconds15per-player-per-day20per-server-per-day400max-spend-per-day5.0Claude (Anthropic)
ai.claude2 keysapi-key""model"claude-opus-5"OpenAI
Key: https://platform.openai.com
Only touch endpoint to use a service that speaks OpenAI's Chat Completions (a proxy, a model you run yourself).
ai.openai3 keysapi-key""model"gpt-4o-mini"endpoint""The prices, in dollars per million tokens
They do two jobs: showing what you are spending in /sctrl ai, and making max-spend-per-day work. A model that is not in this list still works, but counts as zero against the spend -- better to add it than to have a ceiling that never triggers.
Keep reading
WATCH OUT: prices change. These are from August 2026 and should be checked against:
Anthropic https://www.anthropic.com/pricing OpenAI https://openai.com/api/pricing
The Anthropic ones below are verified; the OpenAI ones are indicative, so check their page before trusting the ceiling.
For a rough idea, with about 3,000 tokens of context, a short question and a short answer, 200 questions a day:
claude-haiku-4-5 ~25 dollars a month gpt-4o-mini ~5 dollars a month claude-sonnet-5 ~70 dollars a month claude-opus-5 ~120 dollars a month
Prompt caching, which the plugin asks for on its own, cuts a good deal off all of them: the context is identical on every question and it is the big part.
ai.pricing7 keysAnthropic
ai.pricing.claude-opus-52 keysinput5.00output25.00ai.pricing.claude-sonnet-52 keysinput3.00output15.00ai.pricing.claude-haiku-4-52 keysinput1.00output5.00ai.pricing.claude-opus-4-82 keysinput5.00output25.00ai.pricing.claude-sonnet-4-62 keysinput3.00output15.00OpenAI (indicative: check them)
ai.pricing.gpt-4o2 keysinput2.50output10.00ai.pricing.gpt-4o-mini2 keysinput0.15output0.60Licence
One jar, two editions. With no key it runs FREE: the panel is complete, you see what you have, what is running, what needs updating, and you update one plugin at a time, up to 3 Shadow plugins installed.
Keep reading
With a signed key it runs PREMIUM, which adds what you need once the plugins get numerous: the button that updates them all at once, the automatic checks, the automatic download, the notice to admins when they join, and the automatic restarts and scheduled commands of automation.yml.
How to activate
You have a purchase CODE (the marketplace one):
/sctrl license claim <code>
The server introduces itself to the service, which signs it a licence bound to ITS addresses. One second, no email to send.
You already hold a KEY:
/sctrl license activate <key>
The key ends up in license.yml, not here: that way a support request saying "send me your config" does not arrive with somebody's licence inside it.
If something does not add up
Nothing bad ever happens: the worst case is going back to the free edition, said plainly in the console. The plugin does not shut down and does not interrupt anything halfway.
A licence that just expired, or that does not recognise this server's addresses, gets grace-days days of tolerance before falling back to free: almost always that is a renewal on its way or a proxy, not theft.
license4 keysTHE KEY LIVES IN license.yml (same folder as this file), or in the central plugins/ShadowControl/licenses.yml for the whole suite. The 'key' below is only a fallback for deploy systems that generate config.yml: leave it empty.
The key does NOT live here: it lives in license.yml, in this folder, written at first start (or in plugins/ShadowControl/licenses.yml for the whole suite). The key, if you would rather write it by hand than use the command. It counts for less than license.yml, which is where the command writes.
activation-url'https://shadow-licences.riccardobotins.workers.dev'The service that turns a purchase code into a licence. Base address: the plugin appends /claim and /check itself.
grace-days7Days of tolerance before falling back to free.
resolve-public-iptrueAsk the licence service (activation-url + /ip) how this server looks from outside: it answers with the address it sees, and nothing about the server is sent. It is the only reliable way to know your own public address behind a NAT, and it is what makes a licence sold for a domain work on a machine whose local addresses are all 10.x. Only when a licence is installed: a server without a key asks nobody. false = never ask.
server-address""The address you advertise the server under, if the plugin cannot work it out on its own. Only needed in odd setups.
The catalogue
The list of products lives in catalog.yml. That file is yours and is never overwritten, so new products reach you on their own: from the built-in catalogue inside the jar, from the repository, and from what is installed on this server.
Keep reading
auto-discover: a plugin installed here whose name starts with "prefix" is shown in the panel even if nobody has described it in the catalogue yet. It is there so you never miss a piece of the suite. Set it to false if you want the panel to show only what catalog.yml says and nothing else.
catalog2 keysauto-discovertrueprefix"Shadow"Where updates come from
AUTO -> use url if there is one, otherwise folder; with both empty,
the ShadowMC update server
(https://shadow-updates.riccardobotins.workers.dev/suite.json)
HTTP -> always the website
LOCAL -> always the folder
OFF -> no checking at all, the panel only shows what you haveKeep reading
Website (url)
A JSON file listing the latest version of every plugin. The format is in docs/04-REPOSITORY.md, with a ready example. Use https: over http, anyone in the middle can swap the file you are downloading.
Folder (folder)
A folder full of jars. A network drive, a USB stick, the folder you build into: any of them works. The panel opens each jar, asks it what plugin it is and what version it holds, and keeps the newest. It cannot be the server's own plugins folder.
Examples: folder: "C:/ShadowSuite/releases" folder: "/home/mc/suite-releases"
repository6 keysmodeAUTOurl""folder""license-key""The key the shop gave you, for the paid plugins. It travels as an Authorization header, never inside the address.
timeout"15s"How long to wait for an answer before giving up.
max-download-mb200A safety limit for any single downloaded file.
Automatic checks
check3 keyson-starttrueLook for anything new five seconds after startup.
interval"6h"How often to check again. "0" to never check on its own.
notify-adminstrueTell whoever holds shadowcontrol.notify, in chat, when they join and there is something to update. The notice comes with buttons ready.
Updates
How it works, briefly: the new file is never written over a running plugin. It lands in the folder the server reads at startup (plugins/update), and it is the server itself that swaps them when it restarts. That is why a restart is needed after an update, and why an update cannot break a plugin while it is working.
updates3 keysauto-downloadfalsetrue -> when an automatic check finds something, download it and
leave it waiting for the restart
false -> download nothing until you press the buttonallow-restartfalseRestarts by hand: the button in the panel and /sctrl restart now, in and at. Automatic restarts have their own switch in automation.yml. WATCH OUT: the server only comes back up on its own if something starts it again (a loop in your start file, spigot.yml's restart-script, your panel). /sctrl restart setup tells you what this server has; without anything it just stays down.
restart-delay-seconds10The countdown of "restart now", in seconds: chat, title, action bar, boss bar and sounds as set in automation.yml, in each player's language.
bStats - anonymous usage statistics (https://bstats.org)
What is sent, every 30 minutes: that this server runs ShadowControl, the plugin, Minecraft/Paper and Java versions, the operating system, the number of CPU cores, how many players are online (a number, nothing else), a random serverUuid that identifies nothing, and the edition in use (free, premium or creator). No player data leaves the server: no names, no UUIDs, no addresses, and not the list of plugins you have installed.
Keep reading
false switches it off for this plugin only. enabled: false in plugins/bStats/config.yml switches it off for every plugin on this server at once.
metrics1 keyenabledtrue automation.yml
Automation 49 settings
automation.ymlplugins/ShadowControl/automation.yml File notes
Automatic restarts and scheduled commands.
THE ONE THING TO READ. A restart is a stop followed by a start, and the start is not ours to do: something outside the server has to bring it back - the restart script of spigot.yml, a loop in your start file, your panel, a service manager. See "method". /sctrl restart setup tells you what this server has, and when nothing would bring it back, AUTOMATIC restarts are refused: the console and the panel say so in red.
Free edition: restarts by hand (/sctrl restart now | in | at) with the whole countdown, cancel, delay, simulate, setup and /nextrestart. Everything that happens by itself - the schedule, uptime, empty and health restarts, postponements, the commands before and after, the proxy, the tasks - is Premium. Without a licence this file is kept as it is and starts working the day one is activated.
Durations: 30s, 5m, 2h, 1d. Times use "timezone". Try everything without stopping anything: /sctrl restart simulate 1m
config-version1enabledtruetimezone"system""system" = the machine's zone, printed at startup. A server hosted in Germany for Italian players is the usual surprise: write it out.
e.g. "Europe/Rome"
restart11 keysenabledtruemethodAUTOAUTO | SCRIPT | STOP | PTERODACTYL
SCRIPT stops and runs restart-script of spigot.yml
STOP stops; something else starts it again (a loop in your
start file, systemd with Restart=always, Docker with a
restart policy, a panel that restarts stopped servers)
PTERODACTYL asks your panel to restart it (panel.yml, Premium)
AUTO the first one that works. With AUTO and nothing that
brings the server back, AUTOMATIC restarts are refused
and the console says why: a 5 a.m. "restart" that only
stops is the worst result there is. Write STOP if you
know something restarts it that the plugin cannot see.schedule[ ]When. The nearest line wins.
"daily 05:00" every day
"mon,thu 04:30" on those days ("sat-sun 03:00" works too)
"every 6h" counted from the server start
"cron 0 4 * * *" minute hour day month weekdayA time that does not exist on the day summer time starts happens an hour later; one that exists twice when it ends happens once. Empty on purpose: nothing restarts by itself until you write a line, for instance
schedule: - "daily 05:00"
max-uptime"0"Also after this long up. "0" = never.
restart.when-empty3 keysRestart as soon as nobody is online, once up at least this long, inside the window. A short countdown, cancelled if somebody joins. after-uptime "0" = off.
after-uptime"0"window"02:00-08:00"countdown"30s"restart.health8 keysWhen the server is unwell for a while.
enabledfalsetps-below15.0mspt-above00 = not checked
memory-above-percent90Old-generation heap still in use AFTER garbage collection, in %. The plain heap figure saw-tooths all day and means nothing.
for"5m"min-uptime"1h"max-per-day2A server that keeps failing needs a person, not a loop. -1 = no limit, 0 = never restart for health.
countdown"5m"How much warning the players get.
On Folia there is no single TPS: only the memory rule works there.
restart.postpone5 keysif-players-above0Scheduled and automatic restarts only: one typed by hand is not postponed. 0 = never postpone for players.
by"15m"max-times2After this many postponements it restarts anyway: a server that never restarts because somebody is always on is the problem the schedule was written for.
wait-for-matchestrueShadowGames: let running matches end, up to max-wait.
max-wait"10m"restart.countdown7 keysmarks[30m, 15m, 10m, 5m, 1m, 30s, 10s, 5s, 4s, 3s, 2s, 1s]+9
The moments something is said. The longest one is when the countdown of a planned restart begins.
chat[30m, 15m, 10m, 5m, 1m, 30s, 10s]title[1m, 10s, 5s, 4s, 3s, 2s, 1s]actionbar[30s, 10s, 5s, 4s, 3s, 2s, 1s]restart.countdown.bossbar3 keysfrom"5m"colorREDPINK BLUE RED GREEN YELLOW PURPLE WHITE
overlayNOTCHED_10PROGRESS NOTCHED_6 NOTCHED_10 NOTCHED_12 NOTCHED_20
restart.countdown.sound4 keysname"BLOCK_NOTE_BLOCK_PLING"volume1.0pitch1.0at[1m, 10s, 3s, 2s, 1s]restart.countdownuse-shadowannouncementAUTOWith ShadowAnnouncement installed its "restart" template does the talking: one voice for every announcement. AUTO | ALWAYS | NEVER
The words are in lang/<language>.yml, under automation.restart, each player in their own language.
restart.before1 keyConsole commands at a time before the restart. A command whose time has already passed when the countdown starts (a restart asked with ten seconds) runs at once.
1m[save-all]restartproxy-send-to""Send players to this server of your network instead of kicking them (BungeeCord channel; on Velocity turn on bungee-plugin-message-channel). "" = kick, with the message in lang/.
restart.after-start2 keysConsole commands after the next start, once the server is up. Only after a restart made by this plugin, never after a crash.
delay"30s"commands[ ]tasks1 keytasks.example-reminder6 keysOne block per task; the name is yours. The panel can switch a task on and off and run it now, but commands are written only here.
enabledfalsewhen["every 30m"]Same forms as the restart schedule. Empty = only by hand (/sctrl tasks run <name>).
tasks.example-reminder.conditions5 keysmin-players5max-players-1-1 = no maximum
min-tps17.00 = not checked
days[ ]empty = every day; e.g. [ SAT, SUN ]
between""e.g. "10:00-23:00"
tasks.example-reminderrun["broadcast: lang:automation.example.reminder"]console: <command> once, as the console each-player: <command> once per online player, <player> = name broadcast: <text> MiniMessage, or lang:<key> for lang/ texts
randomfalsetrue = run ONE line picked at random instead of all of them.
missedSKIPA time missed while the server was down: SKIP | RUN_ONCE
catalog.yml
The suite catalogue 24 settings
catalog.ymlplugins/ShadowControl/catalog.yml File notes
The list of the suite's products. This file tells the panel WHAT exists; whether a plugin is installed it works out on its own by looking at the plugins folder.
Adding a product = adding an entry here. No new jar is needed for it.
On update this file is moved to the new version automatically: what you wrote stays, new products are added, and an entry you deleted stays deleted. The old copy is in backup/<date>/; if something needs your attention you will find PORTING-<version>.txt in the plugin folder. Products missing here still reach the panel, from three places:
- the built-in catalogue inside the jar, for entries missing here - the repository, if it announces a product we do not know - the server itself: a plugin named Shadow-something that is installed here is shown even if nobody has described it (turn it off with catalog.auto-discover in config.yml)
Fields:
name the exact name inside the plugin's jar.
It MUST match: it is the key we recognise what the
server has loaded by.
display how you read it in the panel
tier CORE, FREE, FREEMIUM (free, with a Premium edition
unlocked by a key) or PREMIUM. Informational only.
icon a Minecraft material
command the plugin's main command, for the "open" button on
the card. Leave empty if it has none.
description two or three lines, the ones read in the panel, in
English: whoever reads in a language that has no
lines of its own below gets these
description-it the same lines in Italian, for whoever reads the
panel in Italian. Any language works the same way:
description-de, description-fr, ...
display-it the name in Italian, only where the name is words
formerly the names the plugin had before it was renamed. A jar
still declaring one of them is this product in an
older version, and updating it installs the new name.Names and descriptions are what an administrator reads, so they may be written in your own language. If you rewrite a description and leave its description-it as it ships, your text is shown to every reader, whatever their language. Everything else here is a setting.
config-version1plugins21 keysplugins.shadowcontrol7 keysnameShadowControldisplay"ShadowControl"tierFREEMIUMFree, with a Premium edition unlocked by a key.
iconCOMPARATORcommand"/sctrl"description["The panel you are looking at.", "It holds the whole suite together."]+1
description-it["Il pannello che stai guardando.", "Tiene insieme tutta la suite."]+1
plugins.shadowessentials8 keysnameShadowEssentialsformerly[ShadowBasics]Up to 1.0.1 it was called ShadowBasics.
display"ShadowEssentials"tierCOREiconNETHER_STARcommand"/se"description["The heart of the suite: homes, warps, kits,", "VIP, chat and the everyday commands."]+1
description-it["Il cuore della suite: case, warp, kit,", "VIP, chat e i comandi di ogni giorno."]+1
shadowcontrol:
shadowpermissions, shadoweconomy, shadowregions, shadowoptimizer, shadowcommand, shadowspawners, shadowcrazychest, shadowskyblock, shadowoneblock, shadowacidisland, shadowbattlepass, shadowquests, shadowminions, shadowcustomizer, shadowcustomizermodels, shadowlogin, shadowblocker, shadowgamesplugins.shadowedit8 keysnameShadowEditformerly[ShadowWorldEdit, ShadowCreator]Up to 1.0.2 it was called ShadowCreator (and ShadowWorldEdit in test builds).
display"ShadowEdit"tierFREEMIUMiconBRUSHcommand"/shadowedit"description["The world editor and the generator.", "WorldEdit and Iris, in a single jar."]+1
description-it["L'editor di mondi e il generatore.", "WorldEdit e Iris, in un jar solo."]+1
panel.yml
The panel 3 settings
panel.ymlplugins/ShadowControl/panel.yml File notes
ShadowControl - panel.yml. Kept out of config.yml on purpose: a support request saying "send me your config" must not carry your panel key. Written once and then yours: updates never touch it.
Fill it in and the restart method AUTO asks your Pterodactyl panel to restart the server (Premium): the cleanest way on a hosted server.
pterodactyl3 keysapi-key""a CLIENT key (ptlc_...): Account -> API Credentials
server""the short id in the panel address of this server
Languages
The texts the plugin shows to players, one file per language.
lang/
Texts and messages 2 files
lang/plugins/ShadowControl/lang/ File notes
MiniMessage format. Placeholders are written <like-this>. This file is the final fallback: any key missing everywhere else is looked up here, one key at a time.
Every message the plugin shows to players, one file per language. The keys are not listed one by one: here are the files, how many texts they hold and the placeholders they use.
| File | Language | Texts |
|---|---|---|
lang/en_US.yml reference | English | 654 |
lang/it_IT.yml | Italiano | 654 |
Most used placeholders
The plugin replaces them with the right value: leave them written as they are.
<prefix><n><time><v><id><s><plugin><file><t><when><task><label><reason><p>
Message groups
prefix1command38confirm14update42notify6license31ai24ticket13list3plugin-status8info46status1changelog2menu238plugin-tier4update-phase7ticket-status4format3automation169
Other
Ready-made content, examples, imports: the files you copy and edit as a model.
ai/
The assistant and its packs 20 files
ai/plugins/ShadowControl/ai/ 20 files with the same structure. Below, 2 of them in full, one for each different shape; the others are listed by name.
assistant.ymlpacks/shadowacidisland.ymlpacks/shadowbattlepass.ymlpacks/shadowblocker.ymlpacks/shadowcommand.ymlpacks/shadowcontrol.ymlpacks/shadowcrazychest.ymlpacks/shadowcustomizer.ymlpacks/shadoweconomy.ymlpacks/shadowessentials.ymlpacks/shadowgames.ymlpacks/shadowlogin.ymlpacks/shadowminions.ymlpacks/shadowoneblock.ymlpacks/shadowoptimizer.ymlpacks/shadowpermissions.ymlpacks/shadowquests.ymlpacks/shadowregions.ymlpacks/shadowskyblock.ymlpacks/shadowspawners.yml
Example ai/assistant.yml
File notes
What the assistant knows about YOUR server.
This file is the one place you write yourself: the packs in packs/ arrive ready made, this one does not, because nobody knows what your server is like except you.
The more precise it is, the better it answers, and the less it invents.
One tip: write down the things people ask you over and over. If somebody asks how to become VIP every single evening, put it in here and they will stop asking you.
Write it in the language your players speak: this is material for answers, not a setting.
server| This is a Minecraft server with the Shadow suite installed. ⏎ What you really know about this server is in the plugin packs that ⏎ arrive together with the question: those are the real commands here, and ⏎ they are th …The server's story, in prose. What you would tell somebody joining today.
Keep reading
What is written below works from the first minute: it says what is true of any server running this suite, and it tells the assistant to admit what it does not know instead of inventing it. Nothing here is a placeholder, so leaving it as it is costs you nothing.
Rewriting it is still the single best hour you can spend on the assistant. What is worth adding, in order of how often it gets asked:
- the server's name and what kind of server it is (survival, skyblock, minigames, roleplay) - the rules people actually get punished for - how you become VIP, or how you climb a rank - what this server does NOT have: half of the wrong answers come from something being explained that does not exist here - where serious problems get reported, and what you do about them - the opening times of events, and when the map resets
style""An extra line about tone, if you need one. Leave empty for the normal tone.
Example ai/packs/shadowacidisland.yml
File notes
Prompt pack for ShadowAcidIsland.
It only comes into play if ShadowAcidIsland is installed on this server, and only when the router decides the question is about this subject. That is exactly what the keywords are for: put in them the words your players actually use.
The commands below are the plugin's real ones. If you change one, change it here too: the assistant does not discover them on its own, and a wrong command said with confidence is worse than an "I do not know".
idshadowacidislandpluginShadowAcidIslandsummary"The AcidIsland mode: an ocean that burns, the acid tide the whole server lives through together, and the purifier that pushes the acid back."keywords[acidisland, "acid island", acid, ocean, sea, tide, purifier, "water purifier", burns, "it burns me", "the water hurts", island, "my island", storm, "how not to die"]+12
What your players actually call these things.
commands["/island - opens the island panel (alias /is, /sis)", "/island create - creates your island", "/island home - takes you back to your island", "/island sethome - moves the island's arrival point", "/island visit <player …+26
The real commands, exactly as they are typed.
howto| The difference to explain first is this: it is not the void, it is an OCEAN, ⏎ and that ocean burns. Anyone who ends up in the water takes damage. It is the ⏎ number one question from new players ("why does the water …