Configuration
All Zander components share a small set of configuration files. This page is a complete reference for every variable and option across the web platform and each Minecraft plugin.
Web Platform (zander-web)
zander-web layers configuration from four sources, in order of what they're responsible for:
| File | Committed? | Contents |
|---|---|---|
.env | No (gitignored) | Secrets and connection strings - database URLs, OAuth secrets, the ingestion apiKey |
config.json | No (gitignored; copy config.json.example) | Non-secret operational config - site settings, webhook URLs, per-module settings |
features.json | Yes | Boolean flags gating entire modules/routes - checked independently of the other two |
lang.json | Yes | User-facing string overrides only - never affects behaviour |
Environment Variables (.env)
Copy .env.example to .env and populate the following values:
| Variable | Required | Description |
|---|---|---|
PORT | No | HTTP port (default: 8080) |
siteAddress | Yes | Public base URL, e.g. https://example.com |
TZ | No | Timezone (default: Australia/Sydney) |
sessionCookieSecret | Yes | Random string, 32+ characters |
DATABASE_URL | Yes | MySQL connection string for the main database |
LUCKPERMS_URL | Yes | MySQL connection string for the LuckPerms database |
QUICKSHOP_URL | No | MySQL connection string for QuickShop (shop directory feature) |
PUNISHMENTS_URL | No | MySQL connection string for LiteBans (web punishments dashboard / profile pages) |
apiKey | Yes | API key used by Minecraft plugins to authenticate |
discordAPIKey | Yes | Discord bot token |
discordClientId | Yes | Discord OAuth2 application client ID |
discordClientSecret | Yes | Discord OAuth2 application client secret |
twitchClientId | No | Twitch OAuth2 client ID (Watch feature) |
twitchClientSecret | No | Twitch OAuth2 client secret (Watch feature) |
googleClientId | No | Google OAuth2 client ID (YouTube Watch feature) |
googleClientSecret | No | Google OAuth2 client secret (YouTube Watch feature) |
youtubeApiKey | No | YouTube Data API v3 key (Watch feature) |
VAPID_SUBJECT | No | Contact email for web push notifications |
VAPID_PUBLIC_KEY | No | Web push VAPID public key |
VAPID_PRIVATE_KEY | No | Web push VAPID private key |
config.json
Copy config.json.example to config.json. Key sections:
{
"debug": false,
"siteConfiguration": {
"siteUrl": "https://example.com",
"siteName": "My Network",
"tagline": "...",
"email": "support@example.com",
"googleTag": "G-XXXXXXXXXX",
"policy": {
"termsOfService": "https://...",
"rules": "https://...",
"privacy": "https://...",
"refund": "https://..."
},
"platforms": {
"webstore": "https://...",
"discord": "https://discord.com/invite/...",
"issueTracker": "https://..."
}
},
"discord": {
"guildId": "YOUR_GUILD_ID",
"supportPanelChannelId": "CHANNEL_ID",
"supportTicketCategoryId": "CATEGORY_ID",
"botChannelId": "CHANNEL_ID",
"webhooks": {
"welcome": "WEBHOOK_URL",
"networkChatLog": "WEBHOOK_URL",
"adminLog": "WEBHOOK_URL",
"staffChannel": "WEBHOOK_URL",
"staffAuditLog": "WEBHOOK_URL",
"staffPunishmentNotifications": "WEBHOOK_URL"
},
"nicknameReportChannelId": "CHANNEL_ID",
"roles": {
"verified": "ROLE_ID",
"muted": "ROLE_ID"
},
"punishments": {
"logChannelId": "CHANNEL_ID",
"appealBaseUrl": "/appeal",
"permissions": {
"can_warn": "zander.discord.punish.warn",
"can_kick": "zander.discord.punish.kick",
"can_ban": "zander.discord.punish.ban",
"can_mute": "zander.discord.punish.mute",
"can_view_history": "zander.discord.punish.history"
},
"requireDmSuccess": false
}
},
"watch": {
"contentChannelId": "CHANNEL_ID",
"contentPingRoleId": null,
"filters": {
"twitch": {
"titleMarkers": ["#cfc", "[cfc]"],
"tags": ["cfc"]
},
"youtube": {
"tags": ["cfc"],
"descriptionMarkers": ["#cfc"]
}
}
},
"events": {
"discordChannelId": "CHANNEL_ID"
},
"staffAuditReport": {
"enabled": true,
"dayOfWeek": "Monday",
"time": "12:00",
"timezone": "Australia/Sydney",
"webhookUrl": "WEBHOOK_URL"
}
}
features.json
This file enables or disables individual platform features. All values are booleans:
| Key | Description |
|---|---|
announcements | Announcement system |
applications | Staff/player application listings |
forms | Custom form builder |
forums | Community forums |
support | Support ticket system |
server | Server management |
ranks | Rank system |
report | Player reporting |
shopdirectory | In-game shop directory (requires QUICKSHOP_URL) |
vault | Map/resource vault |
bridge | Command bridge executor |
watch | Creator content watch page |
vote | Vote tracking and monthly rewards |
events | Event management |
staffAuditReport | Automated staff audit reports |
webstore | Tebex/Stripe webstore integration (goal tracking, Map Token store) |
finance | Finance/revenue tracking features |
mixed | Mixed/PGM module - match sync, stats, XP, map voting/ratings, map sync |
discord.punishments | Discord punishment system |
discord.events.generalKenobi | Discord join/leave/switch announcements |
discord.events.guildMemberBoost | Server boost notifications |
discord.events.guildMemberVerify | Account verification events |
discord.events.nicknameCheck | Periodic nickname compliance checks |
discord.events.unverifiedReminder | Reminders to unverified members |
discord.events.ipAutoDetect | IP detection on member join |
filter.link | Link/URL filtering |
filter.phrase | Profanity/phrase filtering |
web.login | Web login |
web.register | Web registration |
smDiscord, smTwitter, smYouTube, etc. | Social media platform links in the site footer |
Minecraft Plugins
zander-addon (plugins/zander-addon/config.yml)
api-server:
enabled: true
port: 8080
api-url: "https://craftingforchrist.net/api"
policy-book:
enabled: true
slot: 8
social-paper:
enabled: true
slot: 7
| Key | Default | Purpose |
|---|---|---|
api-server.enabled | true | Whether the plugin's own local HTTP API server starts on enable |
api-server.port | 8080 | Port the local HTTP API server binds to |
api-url | https://craftingforchrist.net/api | Base URL the plugin calls for policy/social config |
policy-book.enabled / .slot | true / 8 | Policy book feature and its inventory slot |
social-paper.enabled / .slot | true / 7 | Social links GUI feature and its inventory slot |
There are two independent HTTP paths here, confirmed from source:
- In-game features fetch live from zander-web.
PolicyServicecalls{api-url}/config/policyand{api-url}/config/social(with the defaultapi-url, that ishttps://craftingforchrist.net/api/config/policyand.../config/social). zander-web serves exactly these routes inapi/routes/config.js(configApiRoute, mounted under/api/config), returning{ success: true, data: {...} }. zander-web is the source of truth for the policy book and social-links GUI:/policyreturnsconfig.siteConfiguration.policy,/socialreturns the enabled social platforms. The response shape matches whatPolicyServiceparses ($.data.termsOfService,$.data.rules, etc.). - The addon's own local server does NOT proxy or cache from zander-web.
PolicyApiServer(started whenapi-server.enabled: true, onapi-server.port) servesGET /api/config/policyandGET /api/config/socialfrom hardcoded values compiled into the plugin (the CraftingForChristLegalrepo URLs and a fixed set of social links). It never calls zander-web - it is a standalone, static endpoint, independent of the live fetch above.
zander-auth (plugins/zander-auth/config.yml)
BaseAPIURL: "http://localhost:8080/api"
APIKey: "KEY"
MOTDTopLine: "&e&lMy &2&nMinecraft&r &6&oAUTH Server"
APIKey is sent as an x-access-token header on the join-verification call to {BaseAPIURL}/user/verify.
zander-hub (plugins/zander-hub/config.yml)
velocitymultiplier: 3
hub:
world: 'world'
x: 0.5
y: 33.0
z: 0.5
yaw: -180.0
pitch: 0.0
messages:
join: '&7%p% joined.'
leave: '&7%p% left.'
misc:
always_first_join: false
slot_hub_compass: 4
| Key | Description |
|---|---|
velocitymultiplier | Velocity multiplier applied in the hub world |
hub.world | World name for the hub spawn |
hub.x/y/z/yaw/pitch | Spawn coordinates and rotation |
messages.join / messages.leave | Join/leave messages; %p% is replaced with the player name |
misc.always_first_join | Whether to always treat every join as a first join |
misc.slot_hub_compass | Inventory slot for the hub compass item |
zander-velocity (plugins/zander-velocity/config.yml)
config-version: 1
BaseAPIURL: "http://localhost:8080/api"
APIKey: "KEY"
announcementMOTDTopLine: "&e&lMy &2&nMinecraft&r &6&oServer"
announcementTipPrefix: "&7&l[&6&lTIP&7&l]&r "
announcementTipInterval: 10
| Key | Description |
|---|---|
announcementMOTDTopLine | Top line of the server MOTD |
announcementTipPrefix | Prefix prepended to rotating tip messages |
announcementTipInterval | How often (in minutes) tips rotate |
APIKey is sent as an x-access-token header, not a Bearer token - zander-velocity talks to a separate, older set of internal routes (/heartbeat, /web/configuration, /report/create, /user/create, /session/create, /discord/join) rather than the /api/mixed/* ingestion contract used by zander-pgm.
zander-waterfall (plugins/zander-waterfall/config.yml)
BaseAPIURL: "http://localhost:8080/api"
APIKey: "KEY"
announcementMOTDTopLine: "&e&lMy &2&nMinecraft&r &6&oServer"
announcementTipPrefix: "&7&l[&6&lTIP&7&l]&r "
announcementTipInterval: 10
Confirmed by reading zander-waterfall/src/main/resources/config.yml directly: it has the same fields as zander-velocity's config (BaseAPIURL, APIKey, announcementMOTDTopLine, announcementTipPrefix, announcementTipInterval) with identical default values. The only difference is that zander-velocity also carries a leading config-version: 1 line, which the Waterfall template omits.
zander-waterfall's plugin.yml declares main: org.modularsoft.zander.proxy.ZanderProxyMain, but the actual class package in source is org.modularsoft.zander.waterfall.ZanderProxyMain. This may be a stale plugin.yml left over from a package rename - if the plugin fails to load with a ClassNotFoundException, this mismatch is the likely cause.
zander-pgm (plugins/zander-pgm/config.yml)
server:
id: "mixed-1"
displayName: "Mixed Server 1"
environment: "production"
api:
baseUrl: "https://example.com/api" # accepts host or host/api
websocketUrl: "wss://example.com/ws/mixed"
token: "change-me" # must match zander-web's apiKey
connectTimeoutSeconds: 10
requestTimeoutSeconds: 15
sync:
heartbeatSeconds: 30
retryFailedEvents: true
maxQueueSize: 10000
features:
matchLifecycle: true
playerStats: true
xp: true
levels: true
achievements: true
mapTokens: true
mapVoting: true
mapRatings: true
moderation: false
chatTags: false
api.token is sent as an Authorization: Bearer header (not x-access-token) - this is the same apiKey value configured in zander-web's .env, but presented differently than the proxy plugins. moderation and chatTags default false - Mixed intentionally excludes moderation and chat-tag management from this plugin. See the full key reference in the zander-pgm module docs for XP/stats/voting/ratings sub-sections.
zander-bridge (plugins/zander-bridge/config.yml)
bridge:
baseApiUrl: "https://your-api-host"
apiKey: "your_api_key_here"
serverSlug: "your_server_slug"
claimTasks: true
pollBatchSize: 25
pollIntervalSeconds: 5
status:
enabled: true
reportIntervalSeconds: 60
tebex:
enabled: false
purchase:
defaultRoutine: null
priority: 0
packageRoutines: {}
subscription:
expirationRoutine: null
cancellationRoutine: null
priority: 0
packageRoutines: {}
voting:
enabled: false
defaultRoutine: null
priority: 0
serviceRoutines: {}
| Key | Default | Description |
|---|---|---|
bridge.baseApiUrl | https://your-api-host | Base URL of the zander-web API the bridge polls (trailing slash trimmed; falls back to http://localhost:3000 if blank) |
bridge.apiKey | your_api_key_here | Sent as the x-access-token header on every request |
bridge.serverSlug | your_server_slug | Identifies this server to the task queue; defaults to the Bukkit server name (or paper-server) if unset |
bridge.claimTasks | true | Whether to atomically claim tasks when polling |
bridge.pollBatchSize | 25 | Max tasks fetched per poll |
bridge.pollIntervalSeconds | 5 | How often to poll for pending tasks |
bridge.status.enabled / .reportIntervalSeconds | true / 60 | Periodic server-status report to /api/bridge/server/update |
bridge.tebex.* | disabled | Maps Tebex purchase/subscription events to named routines |
bridge.voting.* | disabled | Maps Votifier/NuVotifier vote events to named routines |
The key list above is confirmed from the real zander-bridge/src/main/resources/config.yml and the PaperBridgePlugin/BridgeService source. That source is not on master - it lives on the unmerged branch codex/support-velocity-and-paper-in-bridge. A maintainer building the bridge should check out that branch. See the Overview page for the full module note.