Real-time webstore integration for Paper and Folia servers. Syncs purchases from Tebex, CraftingStore, MineStoreCMS, PayNow and KantoPay (or records them manually), broadcasts donations in-game, posts Discord notifications, tracks goals and milestones, and exposes PlaceholderAPI placeholders and a developer API.
Overview
BlockyDonations polls your webstore on a configurable interval and processes new purchases automatically. When a donation is detected it fires a configurable multi-line chat broadcast with optional player-head rendering, triggers a GG Wave reaction window, sends a Discord embed and updates leaderboard, goal and milestone state.
Storage is SQLite, MySQL or MariaDB. Polling and Discord requests run asynchronously so the main thread is never blocked, and Folia's threaded schedulers are detected automatically. Multi-server setups are supported through database leader election and optional Redis event sync.
Supported Providers
- Tebex
- CraftingStore
- MineStoreCMS
- PayNow
- KantoPay
- Manual - donations recorded with
/bd record
Optional Integrations
- PlaceholderAPI - required for all
%bd_*%placeholders - Floodgate - Bedrock player and skin resolution
- SkinRestorer - uses restored skins for heads and renders
- NoteBlockAPI -
.nbssongs as GG Wave music - Chat plugins - ChatControl, ZelChat, AdvancedChat, TownyChat and others
- FancyHolograms / FancyNpcs - load order only; use the leaderboard and skin placeholders
- Redis - cross-server events
No restart needed for most changes. Run /bd reload after editing config files. Adding or removing a provider is the exception.
Installation
- Download BlockyDonations from BuiltByBit
- Place the JAR in your
plugins/folder - Start the server - default files generate automatically
- Edit
plugins/BlockyDonations/config.ymlwith your store credentials - Run
/bd reloador restart to activate
PlaceholderAPI required: install it separately for %bd_*% placeholders to work in scoreboards, holograms and NPCs.
Config updates: when the plugin version changes, config files are merged with the new defaults. Your values are kept and the old files are backed up to plugins/BlockyDonations/backup/.
Configuration
Store Providers
store.providers is a list. Use one entry for a single store or several to run them together. The legacy store.provider key is migrated automatically.
store:
providers: # tebex | craftingstore | minestorecms | paynow | kantopay | manual
- tebex
tebex:
secret-key: "YOUR_TEBEX_SECRET_KEY_HERE"
craftingstore:
api-token: "YOUR_CRAFTINGSTORE_TOKEN_HERE"
paynow:
api-key: "YOUR_PAYNOW_API_KEY_HERE"
store-id: "YOUR_PAYNOW_STORE_ID_HERE"
minestorecms:
secret-key: "YOUR_MINESTORECMS_SECRET_KEY_HERE"
store-url: "https://store.yourserver.com"
kantopay:
api-key: "YOUR_KANTOPAY_API_KEY_HERE"
currency: "PHP"
goal-target: 0.0
goal-period: "month" # day | week | month | year | all
include-test: false
manual:
store-name: "Manual Store"
currency: "USD"
Restart required. Adding or removing a provider needs a full server restart. /bd reload only reloads the settings of providers that are already running.
Database
SQLite (local file) or MySQL/MariaDB (external). MySQL enables multi-server clustering.
database:
type: "sqlite" # sqlite | mysql | mariadb
sqlite:
file: "donations.db"
mysql:
host: "127.0.0.1"
port: 3306
database: "blockydonations"
username: "root"
password: "change-me"
use-ssl: false
parameters: "useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"
pool:
maximum-pool-size: 10
minimum-idle: 2
connection-timeout-ms: 10000
Sync Settings
sync:
interval: 120 # seconds between syncs (min 30)
fetch-limit: 50 # max purchases per sync cycle
Broadcast Message
The broadcast is a YAML list - each entry is one chat line. Player head rows ({player_head_1} through {player_head_8}) render the donor's face alongside the text. Full MiniMessage is supported, including gradients, hover effects and click events.
broadcast:
enabled: true
message:
- ' '
- '{player_head_1} <#FF8800>✦ <gradient:#FF8800:#FFD700><bold>New Donation!</bold></gradient>'
- '{player_head_2}'
- '{player_head_3} <white>Player: <#FFD700>{player}'
- '{player_head_4} <white>Package: <#00BBFF>{package}'
- '{player_head_5} <white>Amount: <#49FF00>{amount_formatted}'
- '{player_head_6}'
- '{player_head_7} <click:open_url:"https://store.servername.com"><hover:show_text:"<gold>Click to Open!"><#FFD700>Visit our store!</hover></click>'
- '{player_head_8} <white>Write <#FFD700>"GG" <white>in chat to celebrate! <#FF3232>❤'
- ' '
sound:
enabled: true
type: "ENTITY_PLAYER_LEVELUP"
volume: 1.0
pitch: 1.0
head-render-type: "helm" # helm (with hat) | avatar (raw face)
Broadcast Placeholders
{player} Player name
{package} Package / product name
{amount} Raw amount (e.g. 9.99)
{amount_formatted} Amount with symbol (e.g. $9.99)
{currency} ISO currency code
{currency_symbol} Symbol (e.g. $)
{provider} Store provider name
{store_name} Store display name
{store_url} Store URL
{store_url_encoded} URL-encoded (use in open_url tags)
{player_head_1..8} Full 8-pixel row (top=1, bottom=8)
{player_head} Row 1 only (compact)
{player_face_url} mc-heads.net avatar URL
{skin_minotar_avatar|helm|bust|body}
{skin_starlight_archer|bust|chibi|walking|portrait|default}
Multi-Provider & Currencies
Every provider keeps its own revenue, leaderboards, latest purchase, goals and sales. Purchases are stored per currency, so amounts in different currencies are never mixed.
Combined Leaderboards
With more than one provider, /bd top and the unprefixed top placeholders combine all providers using cached exchange rates. If rates are missing or older than three days, the view falls back to the first provider.
store:
exchange-rates:
enabled: true
preferred-display-currency: "USD"
refresh-interval-hours: 12
combined-leaderboard-pool-multiplier: 4
Provider-Scoped Placeholders
Prefix any placeholder (except milestones) with a provider key to scope it to that store:
%bd_tebex_store_name%
%bd_kantopay_top_alltime_1%
%bd_paynow_goal_bar%
Manual Provider
Add manual to store.providers, then record a donation with /bd record <player> <amount> [package] [currency]. It runs through the normal pipeline: broadcast, Discord, goals and milestones. The currency must match store.manual.currency.
Network Sync
With MySQL or MariaDB, database.multi-server enables leader election so only one server polls each provider, and announcements are claimed so each donation is announced once. Redis adds live event sync between servers.
database:
multi-server:
enabled: true
leader-election: true
server-id: "auto"
lease-seconds: 45
claim-timeout-seconds: 120
claim-batch-size: 50
redis:
enabled: false
host: "127.0.0.1"
port: 6379
password: ""
ssl: false
key-prefix: "blockydonations"
network-secret: ""
dedupe-ttl-seconds: 90
sync-gg-wave: true
sync-goal-reached-chat: true
sync-donation-message: true
Redis syncs donation broadcasts, GG Waves, goal announcements and milestone completions. Events are signed with HMAC-SHA256 when redis.network-secret is set - use the same secret on every server.
Set redis.password, redis.ssl and redis.network-secret for any Redis instance that is not on localhost.
Commands
Base command: /blockydonations - aliases: /bd, /blockydon, /d, /donations
broadcast, discord, effects, goal, milestone, ggwave, all.manual provider.-p creates a fixed-value card, -d a percentage discount, -e takes yyyy-MM-dd. Also check, list [page] and remove. Support varies by provider; unsupported options are logged and ignored.Permissions
| Node | Default | Description |
|---|---|---|
| blockydonations.* | op | All BlockyDonations permissions. |
| blockydonations.admin | op | All admin commands. |
| blockydonations.stats | op | Goals, sales, stats and latest commands. |
| blockydonations.command.reload | op | Use /bd reload. |
| blockydonations.command.sync | op | Use /bd sync. |
| blockydonations.command.status | op | Use /bd status. |
| blockydonations.command.test | op | Use /bd test. |
| blockydonations.command.ggwave | op | Use /bd ggwave. |
| blockydonations.command.record | op | Use /bd record. |
| blockydonations.command.giftcard | op | Use /bd giftcard. |
| blockydonations.command.update | op | Use /bd update. |
| blockydonations.command.stats | op | Use /bd stats. |
| blockydonations.command.latest | op | Use /bd latest. |
| blockydonations.command.goals | op | Use /bd goals. |
| blockydonations.command.sales | op | Use /bd sales. |
| blockydonations.command.top | true | Use /bd top - open to all players. |
| blockydonations.command.help | true | Use /bd help - open to all players. |
PlaceholderAPI
All placeholders require PlaceholderAPI and start with %bd_. With multiple providers, add a provider key after the prefix (for example %bd_tebex_goal_bar%) to scope a placeholder.
Store Info
%bd_store_name% Store display name
%bd_store_url% Store URL
%bd_store_currency% Currency code (e.g. USD)
%bd_store_currency_symbol% Currency symbol (e.g. $)
%bd_store_provider% Provider name
Latest Donation
%bd_latest_player% Donor username
%bd_latest_package% Package name
%bd_latest_amount% Raw amount
%bd_latest_amount_formatted% Amount with symbol
%bd_latest_currency% Currency code
Revenue
%bd_revenue_total% Total revenue (raw)
%bd_revenue_monthly% Monthly revenue (raw)
%bd_revenue_total_formatted% Total with symbol
%bd_revenue_monthly_formatted% Monthly with symbol
Leaderboards
Replace alltime with monthly or recent, and 1 with any rank up to leaderboard.size.
%bd_top_alltime_1% Player name
%bd_top_alltime_1_amount% Raw amount
%bd_top_alltime_1_formatted% Amount with symbol
%bd_top_alltime_1_uuid% Player UUID
%bd_top_alltime_1_skin% Skin PNG URL (Java + Bedrock)
%bd_top_alltime_1_skin_body% 3D full-body render URL (Java + Bedrock)
Empty ranks return the default Steve skin URLs, so NPC and menu plugins always receive a valid image.
Requesting Player's Leaderboard Info
%bd_top_alltime_amount% Player's all-time amount
%bd_top_alltime_rank% Player's all-time rank
%bd_top_monthly_amount% Same for monthly
%bd_top_recent_rank% Same for recent
Player Statistics
%bd_player_total% Total donated (raw)
%bd_player_monthly% Monthly total (raw)
%bd_player_recent% Last N days (leaderboard.recent-days)
%bd_player_total_formatted% Total with symbol
%bd_player_monthly_formatted% Monthly with symbol
%bd_player_recent_formatted% Recent with symbol
%bd_player_rank_alltime% All-time leaderboard rank
Goals
%bd_goal_name% Goal name
%bd_goal_current% Current progress (raw)
%bd_goal_target% Target amount (raw)
%bd_goal_percent% Completion percentage
%bd_goal_remaining% Amount remaining
%bd_goal_bar% Visual progress bar
%bd_goal_completed% true / false
%bd_goal_completions% Times the primary goal was reached
%bd_goal_completions_<name>% Times a named goal was reached
Sales
%bd_sale_name% Active sale name
%bd_sale_discount% Discount (e.g. 25% or $5.00 off)
%bd_sale_active% true / false
Milestones
Milestone placeholders are global (not provider-scoped) and work for offline players.
%bd_milestone_<id>_name% %bd_milestone_<id>_target%
%bd_milestone_<id>_current% %bd_milestone_<id>_percent%
%bd_milestone_<id>_remaining% %bd_milestone_<id>_bar%
%bd_milestone_<id>_completed% %bd_milestone_<id>_completions%
%bd_milestone_chain_<chain>_bar% %bd_milestone_chain_<chain>_tier%
%bd_milestone_chain_<chain>_percent% %bd_milestone_chain_<chain>_current%
%bd_milestone_chain_<chain>_target% %bd_milestone_chain_<chain>_remaining%
%bd_milestone_chain_<chain>_completed%
Broadcast
Broadcast text supports full MiniMessage syntax. Player head rows sample the donor's skin and render it as colored block characters across consecutive chat lines. Skins are resolved asynchronously: cached or fallback heads are shown immediately while fresh ones load in the background.
Java skins come from Mojang or SkinRestorer when installed. Bedrock skins are resolved through Floodgate and Geyser.
Head Render Type
broadcast:
head-render-type: "helm" # helm (with hat layer) | avatar (raw face only)
Skin Render URLs
Use these in custom messages:
{skin_minotar_avatar} Minotar avatar
{skin_minotar_helm} Minotar with hat
{skin_minotar_bust} Minotar bust
{skin_minotar_body} Minotar full body
{skin_starlight_archer} StarlightSkins archer pose
{skin_starlight_bust} StarlightSkins bust
{skin_starlight_chibi} StarlightSkins chibi
Chat Reactions (GG Wave)
A reaction window opens after each qualifying donation. Players who type a reaction word (like "GG") get their message colored. Start or stop one manually with /bd ggwave.
Basic Configuration
chat-reactions:
enabled: true
listener-priority: "LOWEST" # HIGHEST | HIGH | NORMAL | LOW | LOWEST
window-seconds: 15
min-amount: 2 # minimum donation to trigger the wave (0 = no minimum)
chance: 100 # 0-100 chance for the wave to activate
match-anywhere: true # false = exact match, true = word anywhere in message
mute-non-reactions: false # true = block other chat while the wave is active
case-sensitive: false
words:
- "gg"
- "ggs"
- "thank you"
- "thanks"
- "w"
- "pog"
- "poggers"
Text Formatting
chat-reactions:
format: "rainbow" # rainbow | gradient | solid | solidrainbow
rainbow:
gradient: "#FF0000:#FF9A00:#FFF200:#00D26A:#8B00FF"
gradient-smoothness: 8 # 2-32 (higher = smoother)
solid-color: "#FFD700"
solid-rainbow-colors:
- "#FF0000"
- "#FF7700"
- "#FFFF00"
decorations:
bold: true
italic: false
underline: false
Rewards
Run commands when players react. Prefix with [CONSOLE] (default), [PLAYER], [MESSAGE] or [PLUGIN_MESSAGE], and append [CHANCE:n] for a per-command chance. {player} is sanitized before use.
chat-reactions:
rewards:
enabled: true
commands:
- "[CONSOLE] eco give {player} 100"
run-once-per-wave: false # true = one reward per player per wave
chance: 25
word-overrides: [] # per-word chance, commands or enabled flag
Sounds
chat-reactions:
sounds:
wave-start:
enabled: true
type: "item.goat_horn.sound.0"
volume: 1.0
pitch: 1.0
wave-music:
enabled: false
trigger: "always" # always | on-first
type: "music_disc.cat"
nbs: # requires NoteBlockAPI; songs go in plugins/BlockyDonations/songs/
enabled: false
file: "random"
volume: 80
category: "RECORDS"
reaction:
enabled: true
type: "entity.experience_orb.pickup"
broadcast: true # false = only the typing player hears it
word-sounds: []
Regex Patterns
Patterns are tested before the word list; the first match wins.
chat-reactions:
sounds:
patterns:
- pattern: "(?i)^g+$"
replace-with: "GG"
color: "gradient" # rainbow | gradient | solid | none
sound:
type: "entity.player_levelup"
volume: 1.0
pitch: 1.2
broadcast: true
Compatible chat plugins: ChatControl, ZelChat, AdvancedChat, TownyChat, VentureChat, EssentialsX, HeroChat and DeluxeChat. ZelChat and AdvancedChat use dedicated hooks. If reactions are not colored, try changing listener-priority.
Discord (Webhook + Bot)
Bot Mode (Optional)
Requires a restart. When enabled with a valid token, the bot takes priority over the webhook.
bot:
enabled: false
token: "YOUR_BOT_TOKEN"
channel-id: "YOUR_CHANNEL_ID"
auto-create-thread:
enabled: false
name: "Thank you {player}"
embeds:
- "donation-embed"
- "goal-embed"
auto-archive-duration: 1440 # 60 | 1440 | 4320 | 10080
Webhook Setup
- Open Discord server settings, go to Integrations, then Webhooks.
- Create a webhook, choose a channel and copy the URL.
- Paste it into
discord.ymland setdiscord.enabled: true. - Run
/bd reload, then/bd test discord.
Donation Embed
discord:
enabled: false
webhook-url: "https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN"
donation-embed:
enabled: true # disable to keep goal embeds only
title: "New Donation! 🎉"
description: "**{player}** donated **{currency_symbol}{amount}** for **{package}**!"
color: "#FFD700"
footer: "Server Goal: {goal_percent}% / 100% {goal_bar}"
timestamp:
enabled: true
author:
enabled: true
image:
provider: "starlight" # minotar | starlight | crafatar | nmsr | none
placement: "image" # thumbnail | image | description | none
starlight:
type: "random"
fallback:
enabled: true
probe-timeout-seconds: 5
providers:
- "nmsr"
- "crafatar"
- "minotar"
branding-images:
- url: "https://leafstudios.dev/images/logo_small.png"
placement: "thumbnail"
deduplication:
enabled: true
window-seconds: 60
Embed placeholders: {player} {package} {amount} {currency} {currency_symbol} {store} {skin_url} {goal_name} {goal_current} {goal_target} {goal_percent} {goal_remaining} {goal_bar} {goal_completions}
Goal Reached Embed
discord:
goal-embed:
enabled: true
title: "🎉 Goal Reached! 🎉"
description: "Thanks to **{latest_donator}** and everyone who donated.\n\nWe've reached our goal of **{goal_target_formatted}**!\nThis is completion #**{goal_completions}**."
color: "#FFD700"
timestamp:
enabled: true
Payloads are queued, deduplicated (network-wide when Redis is on) and sent at most once per second. Rate limits are retried automatically.
Leaderboards
Three leaderboards are maintained per currency:
- alltime - total donations since install
- monthly - current UTC calendar month
- recent - past N days, set by
leaderboard.recent-days
leaderboard:
size: 10 # 1-20
recent-days: 7
Use /bd top in-game, or the placeholders in holograms, scoreboards and NPCs. With several providers the boards are combined using exchange rates, and you can scope them with /bd top <provider> or %bd_<provider>_top_*%.
Goals
Goal modes. external syncs goals from the store. internal tracks progress from received purchases and is required for PayNow. Set goals.internal.combined: true to track one goal across all providers in the preferred display currency.
When a goal completes, BlockyDonations announces it in chat and Discord, runs reward commands, and fires GoalCompletedEvent. Internal goals reset to 0 automatically so they can be completed again.
goals:
type: "external" # external | internal
internal:
name: "Server Goal"
combined: false
current: 0.0 # re-seeds progress when changed
goal: 100.0
overrides: # per-provider type, internal.name, internal.current, internal.goal
paynow:
type: "internal"
track-completions: true
announce-in-chat: true
progress-bar-length: 25
progress-bar-style: "pipes" # blocks | pipes | custom
progress-bar-filled: "█"
progress-bar-almost-filled: "█"
progress-bar-empty: "█"
progress-bar-color-filled: "<green><b>"
progress-bar-color-almost-filled: "<yellow><b>"
progress-bar-color-empty: "<gray><b>"
rewards:
enabled: true
chance: 100
commands: # [CONSOLE] | [PLAYER] | [MESSAGE], optional [CHANCE:50]
- "[CONSOLE] eco give {player} 500"
Milestones
Milestones reward individual players for reaching spending targets. Each can be one-time or repeatable, limited to a date window, and run reward commands on completion. Enable them in milestones.yml.
milestones:
enabled: true
announce-complete: false # true = announce to all servers
Metrics
- TOTAL_SPEND - lifetime spend across all purchases
- PACKAGE_SPEND - lifetime spend on matching packages
- PACKAGE_COUNT - qualifying purchases, with optional per-package weights
Package matching is a case-insensitive substring match on the package name, so it works on every provider.
goal:
metric: PACKAGE_SPEND
target: 500.0
packages:
- "vip"
- "vip+"
goal:
metric: PACKAGE_COUNT
target: 15
packages:
key: 1
bundle: 3
Full Example
milestones:
list:
- id: patron
chain: patron
repeatable: false
display-name: "<gradient:#FF0080:#00CCFF><bold>Patron Rank</bold></gradient>"
goal:
metric: TOTAL_SPEND
target: 500.0
currency: USD # recommended when using multiple currencies
providers: # optional provider filter
- tebex
active:
from: "2026-09-01T00:00:00Z"
until: "2028-12-31T23:59:59Z"
rewards:
- "[CONSOLE] lp user {player} parent add patron"
bar:
length: 20
style: gradient
gradient: "#FF0080:#00CCFF"
filled: "█"
empty: "█"
messages:
progress:
- "{bar} <#49FF00>{percent}%"
complete:
- "<gradient:#FF0080:#00CCFF><bold>✦ PATRON UNLOCKED ✦</bold></gradient>"
Chains
Milestones sharing a chain value form ordered tiers (for example patron, then patron-plus). The %bd_milestone_chain_*% placeholders show the player's current tier and a bar toward the final tier.
Rewards & Testing
Reward lines accept [CONSOLE] (default), [PLAYER] and [MESSAGE] prefixes. Test one with /bd test milestone <player> <id>.
{player} Player name
{milestone} Display name
{amount_formatted} Triggering purchase amount
{progress} Current progress
{goal} Target value
{remaining} Remaining to completion
{percent} Completion percent
{bar} Rendered progress bar
{completions} Times completed
Effects
Effects fire for all online players when a donation is announced. Test them with /bd test effects.
Fireworks
effects:
firework:
enabled: true
type: BALL_LARGE # BALL | BALL_LARGE | BURST | CREEPER | STAR
power: 1 # 0-4
flicker: true
trail: true
colors:
- ORANGE
- YELLOW
fade-colors: [] # empty = same as colors
Lightning & Title
effects:
lightning:
enabled: false
damaging: false # true = deals damage
title:
enabled: false
title: "<#FFD700><bold>✦ New Donation! ✦<reset>"
subtitle: "<#FFD700>{player} <white>donated <#FFD700>{amount_formatted}<white>!"
fade-in: 10 # ticks (20 = 1 second)
stay: 60
fade-out: 20
Developer API
Get the API from Bukkit's services manager:
BlockyDonationsApi api = Bukkit.getServicesManager().load(BlockyDonationsApi.class);
double spent = api.getPlayerSpend(LeaderboardType.ALL_TIME, "Notch");
Available data: store info, latest and recent purchases, goals, the active sale, leaderboards (ALL_TIME, MONTHLY, RECENT), player spend and rank (by name or UUID), milestones and progress, and forceSync().
Events
DonationReceivedEvent- a new purchase was storedDonationAnnounceEvent- before the broadcast; cancellableGoalCompletedEvent- a goal was completed and announcedSyncCompletedEvent- a sync cycle finished