Skip to main content

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:

FileCommitted?Contents
.envNo (gitignored)Secrets and connection strings - database URLs, OAuth secrets, the ingestion apiKey
config.jsonNo (gitignored; copy config.json.example)Non-secret operational config - site settings, webhook URLs, per-module settings
features.jsonYesBoolean flags gating entire modules/routes - checked independently of the other two
lang.jsonYesUser-facing string overrides only - never affects behaviour

Environment Variables (.env)

Copy .env.example to .env and populate the following values:

VariableRequiredDescription
PORTNoHTTP port (default: 8080)
siteAddressYesPublic base URL, e.g. https://example.com
TZNoTimezone (default: Australia/Sydney)
sessionCookieSecretYesRandom string, 32+ characters
DATABASE_URLYesMySQL connection string for the main database
LUCKPERMS_URLYesMySQL connection string for the LuckPerms database
QUICKSHOP_URLNoMySQL connection string for QuickShop (shop directory feature)
PUNISHMENTS_URLNoMySQL connection string for LiteBans (web punishments dashboard / profile pages)
apiKeyYesAPI key used by Minecraft plugins to authenticate
discordAPIKeyYesDiscord bot token
discordClientIdYesDiscord OAuth2 application client ID
discordClientSecretYesDiscord OAuth2 application client secret
twitchClientIdNoTwitch OAuth2 client ID (Watch feature)
twitchClientSecretNoTwitch OAuth2 client secret (Watch feature)
googleClientIdNoGoogle OAuth2 client ID (YouTube Watch feature)
googleClientSecretNoGoogle OAuth2 client secret (YouTube Watch feature)
youtubeApiKeyNoYouTube Data API v3 key (Watch feature)
VAPID_SUBJECTNoContact email for web push notifications
VAPID_PUBLIC_KEYNoWeb push VAPID public key
VAPID_PRIVATE_KEYNoWeb 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:

KeyDescription
announcementsAnnouncement system
applicationsStaff/player application listings
formsCustom form builder
forumsCommunity forums
supportSupport ticket system
serverServer management
ranksRank system
reportPlayer reporting
shopdirectoryIn-game shop directory (requires QUICKSHOP_URL)
vaultMap/resource vault
bridgeCommand bridge executor
watchCreator content watch page
voteVote tracking and monthly rewards
eventsEvent management
staffAuditReportAutomated staff audit reports
webstoreTebex/Stripe webstore integration (goal tracking, Map Token store)
financeFinance/revenue tracking features
mixedMixed/PGM module - match sync, stats, XP, map voting/ratings, map sync
discord.punishmentsDiscord punishment system
discord.events.generalKenobiDiscord join/leave/switch announcements
discord.events.guildMemberBoostServer boost notifications
discord.events.guildMemberVerifyAccount verification events
discord.events.nicknameCheckPeriodic nickname compliance checks
discord.events.unverifiedReminderReminders to unverified members
discord.events.ipAutoDetectIP detection on member join
filter.linkLink/URL filtering
filter.phraseProfanity/phrase filtering
web.loginWeb login
web.registerWeb 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
KeyDefaultPurpose
api-server.enabledtrueWhether the plugin's own local HTTP API server starts on enable
api-server.port8080Port the local HTTP API server binds to
api-urlhttps://craftingforchrist.net/apiBase URL the plugin calls for policy/social config
policy-book.enabled / .slottrue / 8Policy book feature and its inventory slot
social-paper.enabled / .slottrue / 7Social links GUI feature and its inventory slot
note

There are two independent HTTP paths here, confirmed from source:

  • In-game features fetch live from zander-web. PolicyService calls {api-url}/config/policy and {api-url}/config/social (with the default api-url, that is https://craftingforchrist.net/api/config/policy and .../config/social). zander-web serves exactly these routes in api/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: /policy returns config.siteConfiguration.policy, /social returns the enabled social platforms. The response shape matches what PolicyService parses ($.data.termsOfService, $.data.rules, etc.).
  • The addon's own local server does NOT proxy or cache from zander-web. PolicyApiServer (started when api-server.enabled: true, on api-server.port) serves GET /api/config/policy and GET /api/config/social from hardcoded values compiled into the plugin (the CraftingForChrist Legal repo 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
KeyDescription
velocitymultiplierVelocity multiplier applied in the hub world
hub.worldWorld name for the hub spawn
hub.x/y/z/yaw/pitchSpawn coordinates and rotation
messages.join / messages.leaveJoin/leave messages; %p% is replaced with the player name
misc.always_first_joinWhether to always treat every join as a first join
misc.slot_hub_compassInventory 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
KeyDescription
announcementMOTDTopLineTop line of the server MOTD
announcementTipPrefixPrefix prepended to rotating tip messages
announcementTipIntervalHow 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.

note

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: {}
KeyDefaultDescription
bridge.baseApiUrlhttps://your-api-hostBase URL of the zander-web API the bridge polls (trailing slash trimmed; falls back to http://localhost:3000 if blank)
bridge.apiKeyyour_api_key_hereSent as the x-access-token header on every request
bridge.serverSlugyour_server_slugIdentifies this server to the task queue; defaults to the Bukkit server name (or paper-server) if unset
bridge.claimTaskstrueWhether to atomically claim tasks when polling
bridge.pollBatchSize25Max tasks fetched per poll
bridge.pollIntervalSeconds5How often to poll for pending tasks
bridge.status.enabled / .reportIntervalSecondstrue / 60Periodic server-status report to /api/bridge/server/update
bridge.tebex.*disabledMaps Tebex purchase/subscription events to named routines
bridge.voting.*disabledMaps Votifier/NuVotifier vote events to named routines
Source lives on a feature branch

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.