Config
The config module provides a type-safe Kotlin DSL for structured configuration using property delegation. The Paper integration adds helpers for working with YAML files and Bukkit-specific types.
Modules
| Module | Description |
|---|---|
kraftlin-config-core |
Platform-agnostic config DSL with property delegation |
kraftlin-config-paper |
Paper integration with Bukkit type support |
kraftlin-config-bungee |
BungeeCord integration |
kraftlin-config-velocity |
Velocity integration |
Getting Started
Create a config class extending AbstractConfig:
class Config(plugin: Plugin) : AbstractConfig(wrapConfig(plugin)) {
val interval: Long by config("spawn_interval_ticks", 1200L)
val chance: Double by config("spawn_chance", 0.25)
var message: String by config("message", "Hello!")
}
Use in your plugin:
override fun onEnable() {
val config = Config(this)
config.saveDefaults() // Write defaults if not present
logger.info("Interval: ${config.interval}")
config.message = "Updated!"
config.save() // Persist changes
}
Core Concepts
Property Delegation
Configuration values are bound to Kotlin properties using delegation:
val readOnly: String by config("path.to.value", "default")
var mutable: Int by config("path", 42) // Can be changed and saved
Type Safety
The module provides compile-time type checking for all config values:
val count: Int by config("count", 10) // ✓ Int
val items: List<String> by config("items", listOf("a", "b")) // ✓ List<String>
// val wrong: Int by config("count", "text") // ✗ Compile error
Lifecycle
val config = Config(plugin)
// First time setup
config.saveDefaults() // Write defaults to file if not present
// After user edits file
config.reloadConfig() // Reload from disk, invalidate cache
// After programmatic changes
config.message = "New value"
config.save() // Write back to disk
Supported Types
Primitives
Boolean,Int,Long,Double,String
Built-in Complex Types
UUID- Stored as stringEnum<T>- Stored as lowercase name (supports bothUPPER_CASEandkebab-case)List<T>- For primitives, enums, UUIDs, and custom typesMap<String, T>- String-keyed maps with typed values
Custom Types
Use serialize and deserialize functions for custom types:
val customValue: LocalDateTime by config(
path = "date",
default = LocalDateTime.now(),
serialize = { it.toString() },
deserialize = { LocalDateTime.parse(it) }
)
Works with lists too:
val dates: List<LocalDateTime> by config(
path = "dates",
default = listOf(LocalDateTime.now()),
serialize = { it.toString() },
deserialize = { LocalDateTime.parse(it) }
)
Paper-Specific Types
Available in kraftlin-config-paper:
class BukkitConfig(plugin: Plugin) : AbstractBukkitConfig(wrapConfig(plugin)) {
// Materials and tags
val materials: Set<Material> by config(
"blocks",
listOf(Material.STONE, Tag.SHULKER_BOXES)
)
}
Material sets support:
- Individual materials: STONE, DIAMOND_ORE
- Material tags: #shulker_boxes, #wool
- Both with or without minecraft: prefix
- Returns EnumSet<Material> for performance
Advanced Features
Nested Configuration
Use inner classes for structured configuration:
class Config(plugin: Plugin) : AbstractConfig(wrapConfig(plugin)) {
val spring = EventConfig("spring")
val winter = EventConfig("winter")
inner class EventConfig(id: String) {
val enabled: Boolean by config("$id.enabled", true)
val startDate: String by config("$id.start_date", "2024-01-01")
val endDate: String by config("$id.end_date", "2024-12-31")
}
}
Results in YAML:
spring:
enabled: true
start_date: "2024-01-01"
end_date: "2024-12-31"
winter:
enabled: true
start_date: "2024-01-01"
end_date: "2024-12-31"
Comments
Add comments to config values:
val interval: Long by config(
"spawn_interval_ticks",
1200L,
"Time in ticks between spawns",
"20 ticks = 1 second"
)
Comments are written during saveDefaults() if the path doesn't have existing comments.
Multiple Files
Bind to arbitrary YAML files instead of the default config.yml:
val customConfig = object : AbstractConfig(wrapConfig(dataFolder.resolve("custom.yml"))) {
val value: Int by config("value", 5)
}
customConfig.saveDefaults()
Redundant Key Removal
Clean up old/unused config keys:
config.removeRedundantKeys() // Removes keys not defined in code
Database Configuration
Load database settings from a separate database.yml:
val database = loadSqlConfiguration(plugin)
// Returns SqlConfiguration(url, user, password)
We highly encourage this separation: 1. Reduces risk of accidentally committing sensitive data 2. Allows easy switching between database backends 3. Simplifies DevOps automation
Features:
- Auto-migrates legacy database.properties files
- Creates example config if none exists
- Includes helpful header comments
Example database.yml:
url: jdbc:postgresql://localhost:5432/mydb
user: myuser
password: mypassword
Platform Setup
Each platform provides a wrapConfig function to create the config wrapper:
class Config(plugin: Plugin) : AbstractConfig(wrapConfig(plugin)) {
val value: String by config("key", "default")
}
class Config(plugin: Plugin) : AbstractConfig(wrapConfig(plugin)) {
val value: String by config("key", "default")
}
class Config(dataDirectory: Path) : AbstractConfig(wrapConfig(dataDirectory)) {
val value: String by config("key", "default")
}
The wrapConfig overloads accept platform-specific types but all return the same ConfigWrapper, so
everything else (property delegation, lifecycle, types) works identically across platforms.
Why Kraftlin Config?
Benefits over manual YAML access:
Without Kraftlin:
val interval = config.getLong("spawn_interval_ticks", 1200L)
val items = config.getStringList("items")
// Manual type checking, scattered get/set calls
With Kraftlin:
class Config(plugin: Plugin) : AbstractConfig(wrapConfig(plugin)) {
val interval: Long by config("spawn_interval_ticks", 1200L)
val items: List<String> by config("items", listOf())
}
// Type-safe, organized, automatic defaults
Benefits: - Compile-time type safety - Centralized configuration definition - Automatic default value management - Clean property access throughout code - Lazy loading with caching - Support for custom types via serialization