Documentation Index Fetch the complete documentation index at: https://mintlify.com/minekube/gate/llms.txt
Use this file to discover all available pages before exploring further.
Project Setup
This guide walks you through creating a new Go project that uses Gate from scratch. If you prefer a pre-configured setup, see the Plugin Template instead.
Prerequisites
Before you begin, ensure you have:
Go 1.21 or higher installed (download here )
Basic Go knowledge (modules, packages, functions)
A text editor (VS Code, GoLand, Vim, etc.)
Terminal access for running commands
Verify your Go installation:
go version
# Should output: go version go1.21 or higher
Creating a New Project
Create Project Directory
Create a new directory for your project: mkdir my-gate-proxy
cd my-gate-proxy
Initialize Go Module
Initialize a new Go module: go mod init github.com/yourusername/my-gate-proxy
Replace github.com/yourusername/my-gate-proxy with your actual module path.
Add Gate Dependency
Add Gate as a dependency: go get -u go.minekube.com/gate@latest
This will download Gate and update your go.mod file.
Create Main File
Create a main.go file with the following content: package main
import (
" context "
" go.minekube.com/gate/cmd/gate "
" go.minekube.com/gate/pkg/edition/java/proxy "
)
func main () {
// Add our plugin to be initialized on Gate start
proxy . Plugins = append ( proxy . Plugins , proxy . Plugin {
Name : "MyProxy" ,
Init : func ( ctx context . Context , p * proxy . Proxy ) error {
return initPlugin ( p )
},
})
// Execute Gate entrypoint and block until shutdown
gate . Execute ()
}
func initPlugin ( p * proxy . Proxy ) error {
// Your plugin initialization code goes here
return nil
}
Create Configuration File
Create a config.yml file for Gate’s configuration: config :
bind : 0.0.0.0:25565
motd : "&bMy Gate Proxy"
servers :
lobby :
address : localhost:25566
survival :
address : localhost:25567
try :
- lobby
This configures Gate to:
Listen on port 25565
Connect players to the “lobby” server first
Fall back to other servers if lobby is unavailable
Download Dependencies
Download all module dependencies: go mod download
go mod tidy
Build and Run
Build and run your proxy: You should see output indicating Gate has started: INFO Gate is now running on 0.0.0.0:25565
Project Structure
A typical Gate project structure:
my-gate-proxy/
├── main.go # Entry point
├── plugin.go # Plugin logic (optional)
├── commands.go # Command definitions (optional)
├── events.go # Event handlers (optional)
├── go.mod # Module definition
├── go.sum # Dependency checksums
├── config.yml # Gate configuration
└── README.md # Documentation
Understanding go.mod
After running go get, your go.mod should look like:
module github . com / yourusername / my - gate - proxy
go 1.21
require go . minekube . com / gate v0 . 62.3
require (
connectrpc . com / connect v1 . 19.1 // indirect
github . com / go - logr / logr v1 . 4.3 // indirect
github . com / robinbraemer / event v0 . 1.1 // indirect
github . com / spf13 / viper v1 . 21.0 // indirect
go . minekube . com / brigodier v0 . 0.2 // indirect
go . minekube . com / common v0 . 3.0 // indirect
// ... other indirect dependencies
)
Key dependencies you’ll commonly use:
go.minekube.com/gate - Core proxy functionality
go.minekube.com/brigodier - Command framework
go.minekube.com/common - Minecraft components (chat, colors)
github.com/robinbraemer/event - Event system
Adding Common Features
Register a Command
Create commands.go:
package main
import (
" fmt "
" go.minekube.com/brigodier "
" go.minekube.com/common/minecraft/component "
" go.minekube.com/gate/pkg/command "
" go.minekube.com/gate/pkg/edition/java/proxy "
)
func registerCommands ( p * proxy . Proxy ) {
// Register /ping command
p . Command (). Register (
brigodier . Literal ( "ping" ). Executes (
command . Command ( func ( c * command . Context ) error {
player , ok := c . Source .( proxy . Player )
if ! ok {
return c . Source . SendMessage (
& component . Text { Content : "Pong!" },
)
}
return player . SendMessage ( & component . Text {
Content : fmt . Sprintf ( "Pong! Your ping is %s " , player . Ping ()),
})
}),
),
)
}
Update main.go to call it:
func initPlugin ( p * proxy . Proxy ) error {
registerCommands ( p )
return nil
}
Handle Events
Create events.go:
package main
import (
" github.com/robinbraemer/event "
" go.minekube.com/common/minecraft/color "
" go.minekube.com/common/minecraft/component "
" go.minekube.com/gate/pkg/edition/java/proxy "
)
func subscribeToEvents ( p * proxy . Proxy ) {
// Listen for player login
event . Subscribe ( p . Event (), 0 , onPlayerLogin )
// Listen for server switch
event . Subscribe ( p . Event (), 0 , onServerSwitch )
}
func onPlayerLogin ( e * proxy . PostLoginEvent ) {
player := e . Player ()
player . SendMessage ( & component . Text {
Content : "Welcome to the server!" ,
S : component . Style { Color : color . Green },
})
}
func onServerSwitch ( e * proxy . ServerPostConnectEvent ) {
player := e . Player ()
if server := player . CurrentServer (); server != nil {
player . SendMessage ( & component . Text {
Content : "You are now on: " + server . Server (). ServerInfo (). Name (),
S : component . Style { Color : color . Yellow },
})
}
}
Update main.go:
func initPlugin ( p * proxy . Proxy ) error {
registerCommands ( p )
subscribeToEvents ( p )
return nil
}
Working with Components
Gate uses a component system for rich text:
import (
" go.minekube.com/common/minecraft/color "
" go.minekube.com/common/minecraft/component "
)
// Simple text
msg := & component . Text {
Content : "Hello, world!" ,
}
// Colored text
msg := & component . Text {
Content : "This is red!" ,
S : component . Style { Color : color . Red },
}
// Bold and italic
msg := & component . Text {
Content : "Bold and italic" ,
S : component . Style {
Color : color . Gold ,
Bold : component . True ,
Italic : component . True ,
},
}
// Clickable text
msg := & component . Text {
Content : "Click me!" ,
S : component . Style {
ClickEvent : component . SuggestCommand ( "/ping" ),
HoverEvent : component . ShowText ( & component . Text {
Content : "Click to run /ping" ,
}),
},
}
// Compound message
msg := & component . Text {
Content : "Welcome " ,
Extra : [] component . Component {
& component . Text {
Content : player . Username (),
S : component . Style { Color : color . Yellow },
},
& component . Text { Content : "!" },
},
}
Complete Example
Here’s a complete main.go with commands and events:
package main
import (
" context "
" fmt "
" github.com/robinbraemer/event "
" go.minekube.com/brigodier "
" go.minekube.com/common/minecraft/color "
" go.minekube.com/common/minecraft/component "
" go.minekube.com/gate/cmd/gate "
" go.minekube.com/gate/pkg/command "
" go.minekube.com/gate/pkg/edition/java/proxy "
)
func main () {
proxy . Plugins = append ( proxy . Plugins , proxy . Plugin {
Name : "MyProxy" ,
Init : func ( ctx context . Context , p * proxy . Proxy ) error {
return initPlugin ( p )
},
})
gate . Execute ()
}
func initPlugin ( p * proxy . Proxy ) error {
registerCommands ( p )
subscribeToEvents ( p )
return nil
}
func registerCommands ( p * proxy . Proxy ) {
// /ping command
p . Command (). Register (
brigodier . Literal ( "ping" ). Executes (
command . Command ( func ( c * command . Context ) error {
player , ok := c . Source .( proxy . Player )
if ! ok {
return c . Source . SendMessage (
& component . Text { Content : "Pong!" },
)
}
return player . SendMessage ( & component . Text {
Content : fmt . Sprintf ( "Pong! Latency: %s " , player . Ping ()),
S : component . Style { Color : color . Green },
})
}),
),
)
// /broadcast <message> command
p . Command (). Register (
brigodier . Literal ( "broadcast" ). Then (
brigodier . Argument ( "message" , brigodier . StringPhrase ). Executes (
command . Command ( func ( c * command . Context ) error {
message := c . String ( "message" )
msg := & component . Text {
Content : "[Broadcast] " + message ,
S : component . Style { Color : color . Gold },
}
for _ , player := range p . Players () {
_ = player . SendMessage ( msg )
}
return nil
}),
),
),
)
}
func subscribeToEvents ( p * proxy . Proxy ) {
event . Subscribe ( p . Event (), 0 , onPlayerLogin )
event . Subscribe ( p . Event (), 0 , onServerSwitch )
}
func onPlayerLogin ( e * proxy . PostLoginEvent ) {
player := e . Player ()
_ = player . SendMessage ( & component . Text {
Content : fmt . Sprintf ( "Welcome, %s !" , player . Username ()),
S : component . Style { Color : color . Green , Bold : component . True },
})
}
func onServerSwitch ( e * proxy . ServerPostConnectEvent ) {
player := e . Player ()
if server := player . CurrentServer (); server != nil {
_ = player . SendMessage ( & component . Text {
Content : "Connected to " + server . Server (). ServerInfo (). Name (),
S : component . Style { Color : color . Aqua },
})
}
}
Building Your Project
Development Build
For quick testing:
Production Build
Create an optimized binary:
# Basic build
go build -o my-proxy
# Optimized build (smaller binary)
CGO_ENABLED = 0 go build -ldflags= "-s -w" -o my-proxy
# Run the binary
./my-proxy
Cross-Compilation
Build for different platforms:
# Linux (most common for servers)
GOOS = linux GOARCH = amd64 CGO_ENABLED = 0 go build -o my-proxy-linux
# Windows
GOOS = windows GOARCH = amd64 CGO_ENABLED = 0 go build -o my-proxy.exe
# macOS (Intel)
GOOS = darwin GOARCH = amd64 CGO_ENABLED = 0 go build -o my-proxy-macos-intel
# macOS (Apple Silicon)
GOOS = darwin GOARCH = arm64 CGO_ENABLED = 0 go build -o my-proxy-macos-arm
Configuration Best Practices
Separate Environments
Create different config files for different environments:
config.dev.yml # Development
config.prod.yml # Production
config.test.yml # Testing
Run with a specific config:
./my-proxy --config config.prod.yml
Environment Variables
Use environment variables for sensitive data:
config :
forwarding :
mode : modern
# Secret loaded from environment variable
export GATE_VELOCITY_SECRET = "your-secret-here"
./my-proxy
Validation
Gate validates your configuration on startup. Common issues:
# ❌ Invalid: missing address
servers :
lobby : {}
# ✅ Valid: includes address
servers :
lobby :
address : localhost:25566
Troubleshooting
Error : go: module go.minekube.com/gate: Get "https://proxy.golang.org/..." failedSolutions :
Check your internet connection
Verify firewall isn’t blocking Go module proxy
Try setting GOPROXY environment variable:
export GOPROXY = https :// proxy . golang . org , direct
go get -u go.minekube.com/gate@latest
Error : import cycle not allowedSolution : Reorganize your code to avoid circular imports. For example:// ❌ Bad: main.go imports plugin.go, plugin.go imports main.go
// ✅ Good: Use separate packages or interfaces
package main
package plugin
Error : bind: address already in useSolutions :
Find and stop the process using the port:
# Linux/macOS
lsof -i :25565
kill < PI D >
# Windows
netstat -ano | findstr :25565
taskkill /PID < PI D > /F
Or change the port in config.yml:
config :
bind : 0.0.0.0:25566
Error : config file not foundSolution : Ensure config.yml is in the same directory as your binary:ls -la config.yml
# If missing, create it from the template above
Or specify the path explicitly: ./my-proxy --config /path/to/config.yml
Backend servers not connecting
Error : Players can’t connect to backend serversSolutions :
Verify backend servers are running:
Check firewall rules allow connections
Verify addresses in config.yml are correct
Check backend server logs for connection attempts
Testing Your Proxy
Manual Testing
Start your proxy:
Start a backend server (e.g., Paper, Spigot) on port 25566
Connect with Minecraft client to localhost:25565
Unit Testing
Create main_test.go:
package main
import (
" testing "
)
func TestPluginInit ( t * testing . T ) {
// Test your plugin initialization
}
Run tests:
Use Goroutines for Async Operations
// ❌ Bad: Blocks the event handler
func onPlayerJoin ( e * proxy . PostLoginEvent ) {
time . Sleep ( 5 * time . Second ) // Blocks!
e . Player (). SendMessage ( msg )
}
// ✅ Good: Runs asynchronously
func onPlayerJoin ( e * proxy . PostLoginEvent ) {
player := e . Player ()
go func () {
time . Sleep ( 5 * time . Second )
player . SendMessage ( msg )
}()
}
Batch Player Operations
// Send message to all players efficiently
for _ , player := range p . Players () {
go func ( p proxy . Player ) {
_ = p . SendMessage ( msg )
}( player )
}
Resource Cleanup
Use context cancellation:
func initPlugin ( p * proxy . Proxy ) error {
ctx := context . Background ()
// Start background task
go periodicTask ( ctx )
// Cleanup on shutdown
event . Subscribe ( p . Event (), 0 , func ( e * proxy . ShutdownEvent ) {
// Cancel context, close connections, etc.
})
return nil
}
Next Steps
Commands Guide Learn how to create powerful commands
Events Guide Master the event system
Simple Proxy Example Study a complete working example
API Reference Explore the full API documentation
Additional Resources