For the complete documentation index, see llms.txt. This page is also available as Markdown.

Migration Guide

What changes when you upgrade, and what the plugin converts for you.


Upgrading from v3 to v4

4.0.0 is a major release. The plugin migrates its data, config.yml, and messages.yml automatically on the first server start. A working server needs no manual steps. The breaking changes are the runtime requirements (Java and Minecraft versions) and the buildsystem-api surface for dependent plugins.

Read the sections below for the full picture: requirements, automatic conversion, configuration, permissions, commands, language, and API.


1. System Requirements

These are hard requirements. The plugin will not start otherwise.

Requirement
v3
v4

Java

21

25

Minecraft

1.21+

26.1+

Server software

Spigot / Paper

Spigot / Paper

Upgrade your server's JDK to 25 before dropping in the new jar.


2. Automatic Data & File Conversion

On the first 4.0.0 boot the plugin upgrades each legacy structure in place. Nothing is lost; unrecognized values fall back to a sensible default.

Data Element
Legacy Format (v3)
New Format (v4)
Notes

Storage keying

worlds.yml / folders.yml keyed by world/folder name

Keyed by a stable UUID, with the name stored as a field

A rename is now a field update, not a key move, so renames no longer leave orphaned entries. Folder parent references switch from the parent's name to its UUID.

World status

IN_PROGRESS (uppercase enum)

in_progress (lowercase registry id)

Unrecognized values default to not_started.

World visibility

private: true / false

visibility: ADDED_PLAYERS / EVERYONE

One-to-one mapping.

Folder category

category: PUBLIC (uppercase enum)

category: public (lowercase registry id)

Unrecognized values default to public.

Status display names

status_in_progress: "&6In Progress" in messages.yml

displayName + color in statuses.yml

Seeded on first boot only if statuses.yml does not exist yet. The leading colour code is split into color; the remainder becomes displayName, preserving any rename/translation.

config.yml / messages.yml

Older layouts

Versioned, auto-migrated

Missing keys are appended and some keys are relocated; existing customisations are preserved.

Before re-keying, the plugin writes a one-time backup next to each affected file:

  • plugins/BuildSystem/worlds.yml.v3.bak

  • plugins/BuildSystem/folders.yml.v3.bak

The migration also creates two new files for the now-customizable statuses and categories:

  • plugins/BuildSystem/statuses.yml

  • plugins/BuildSystem/categories.yml

After the first boot, statuses.yml is authoritative. The status_* keys in messages.yml are no longer read and can be removed (see the Language File section below).


3. Configuration File Changes

config.yml is versioned and migrated on load, so custom values survive the upgrade. Two flags are gone and one is new.

Removed Keys

Silently ignored in v4, safe to delete from config.yml:

  • settings.per-option-permissions, superseded by the always-on buildsystem.setting.<option> nodes.

  • settings.restrict-template-access, superseded by the deny-to-restrict buildsystem.create.template.<name> model.

Replaced Keys

  • world.disabled-physics.* becomes world.defaults.physics-exceptions.*. The meaning is inverted along with the name: the old prevent-x: false becomes x: true, read as "this behavior still runs while physics are off". Existing configs are converted on load, so no manual edit is needed.

  • world.max-amount.public / world.max-amount.private become world.limits.public / world.limits.private. Values carry over on load. The meaning changed too: the limit is now the default number of worlds one player may create of that visibility, applied only to players who hold no buildsystem.create.<visibility>.<amount> node of their own, and ignored entirely for buildsystem.admin. Previously the value was compared against the total number of worlds on the server, so setting either key stopped all world creation once the server reached that many worlds. If you set these in v3 expecting a per-player cap, that is what you now get; if you were relying on the old behavior as a server-wide cap, there is no direct replacement.

New Keys

  • settings.world-permission-whitelist (default: []) restricts the permission strings /worlds setPermission may assign. An empty list allows any value.

  • world.void-block.enabled (default: true) and world.void-block.material (default: GOLD_BLOCK) control the block placed at the spawn of a newly generated void world so the first player to join does not fall.

  • world.defaults.physics-exceptions.<category> (default: false for all nine categories) seeds new worlds, and any existing world that has no stored value yet.

Changed Defaults


4. Permission Changes

Existing command permissions are unchanged. v4 adds the following.

New Static Nodes

Permission
Default
Description

buildsystem.savetemplate

OP

Run /worlds saveTemplate to copy a live world into templates/.

buildsystem.edit.pin

OP

Pin worlds to the top of navigator lists via the Edit GUI.

buildsystem.edit.icon

OP

Set a world's custom icon material or skull texture in the Edit GUI.

buildsystem.edit.physics

OP

Open the physics exceptions menu by right-clicking the physics toggle in the Edit GUI.

buildsystem.backup

OP

View a world's backup history GUI.

buildsystem.backup.create

OP

Force a manual backup with /worlds backup create.

buildsystem.setup

OP

Open /setup to manage statuses, categories, and default world icons.

New Dynamic Nodes

Resolved at runtime from the id of the status, category, template, or generator:

Permission Pattern
Default
Description

buildsystem.create.category.<categoryId>

OP

Create a world in a specific category (e.g. buildsystem.create.category.public). Admins with the create bypass may create in any category.

buildsystem.navigator.category.<categoryId>

true

Show the category in the navigator and enable its /worlds <category> shortcut. Deny it to hide a category and disable its command together.

buildsystem.setstatus.<statusId>

inherits buildsystem.setstatus (OP)

Assign a status to a world. Built-in statuses strip underscores for backwards compatibility (in_progress…inprogress); custom statuses use their full id (my_status…my_status).

buildsystem.create.template.<name>

true

Use a specific template. Deny the node to restrict it.

buildsystem.create.generator.<name>

true

Use a specific chunk generator. Deny the node to restrict it.

buildsystem.setting.<option>

true

Toggle a /settings option (e.g. buildsystem.setting.no-clip). Deny to restrict.

Removed Nodes

Permission
Replacement

buildsystem.create.private

buildsystem.create.category.private

If your setup grants buildsystem.create.private, switch to the per-category node above. This is not the same as the buildsystem.create.private.<amount> world-limit node, which is unchanged.


5. Navigator Command Changes

The fixed /worlds archive, /worlds private, and /worlds public subcommands are replaced by per-category /worlds <category> shortcuts, generated from the navigator layout instead of being hardcoded.

Existing setups keep working: the three built-in categories still answer to archive, private, and public. A real subcommand always wins a name clash, so a custom category named like a command can never shadow it.


6. Language File (messages.yml) Changes

  • Default English: delete messages.yml and restart to regenerate a fresh file with every v4 key.

  • Custom translations: keep the file; the plugin appends new keys on startup. Localize the newly added keys manually.

Obsolete Keys (can be removed)

No longer read. Their values were copied into statuses.yml on the first boot:

  • status_not_started, status_in_progress, status_almost_finished, status_finished, status_archive, status_hidden

Changed Keys

  • world_navigator_create_world, default changed to &bCreate a World because clicking the item now opens a category picker.

  • night_unknown_world is finally used. /night in an unknown world used to send the /day message by mistake, so if you translated only day_unknown_world, translate this one too.

Keys No Longer Used

The navigator rewrite left these behind. Nothing reads them, so they can be deleted from a translated messages.yml:

old_navigator_world_navigator, old_navigator_world_archive, old_navigator_private_worlds, new_navigator_world_navigator, new_navigator_world_archive, new_navigator_private_worlds, world_navigator_title, archive_title, archive_no_worlds, private_title, private_no_worlds, private_create_world, worldsettings_gamerule_, worldeditor_time_lore_unknown, explosions_deactivated_in_world, noai_activated_in_world

New Keys

Added in 4.0 and appended automatically. Translate them if you keep a custom file:

  • worlds_importall_world_failed, shown per world when a bulk import fails.

  • worlds_import_unknown_type, shown when /worlds import -t gets a value that is not a world type.

  • worlds_template_creation_error, shown when a template cannot be copied.


7. Developer API Changes

Plugins compiling against buildsystem-api must recompile against 4.0.0 and apply the changes below.

Breaking Changes

API Element
Legacy Code (v3)
Upgrade Action (v4)

Event package

de.eintosti.buildsystem.api.event.BuildWorldManipulationEvent

Import from de.eintosti.buildsystem.api.event.world instead.

World creation

WorldService.createWorld(...)

Fluent builder: worldService.newWorld(name)…build().

World importing

WorldService.importWorld(name, ...)

Fluent builder: worldService.importWorld(name)…build().

World data

Type<T> objects with a getter/setter per setting

Typed keys: data.get(WorldDataKey.PHYSICS) / data.set(WorldDataKey.PHYSICS, false). The built-in keys live on WorldDataKey.

Icons

XSeries XMaterial / Profileable in signatures

Bukkit Material everywhere; head textures are plain strings via Displayable.getIconSkullTexture(). XSeries is no longer a dependency of the API artifact.

Bukkit handle

BuildWorld.getWorld() returned a nullable World

Now returns Optional<org.bukkit.World>.

World status

BuildWorldStatus (enum)

Now an interface resolved via BuildSystem.getStatusRegistry(). Compare by getId() / equals(), never == or switch.

Categories

NavigatorCategory (enum)

Now an interface resolved via BuildSystem.getNavigatorCategoryRegistry(); Folder.getCategory() returns it. Compare by getId() / equals(), never == or switch.

Visibility

isPrivateWorld() / setPrivateWorld(boolean)

Replaced by WorldData.getVisibility() / setVisibility(Visibility) (EVERYONE / ADDED_PLAYERS).

API lookup

BuildSystemProvider.register() / unregister()

No longer public. Resolve via BuildSystemProvider.get() or Bukkit's ServicesManager.

Removed

  • BuildWorld.setLoaded(boolean). The read-only isLoaded() remains.

  • BuildWorld.asProfileable(). Head textures are read through Displayable.getIconSkullTexture().

  • WorldData.getConfigFormat() and WorldData.getAllData().

  • The public registration entrypoints on BuildSystemProvider (see above).

New (non-breaking)

Available to dependent plugins but not required for an upgrade:

  • World lifecycle events in event.world (BuildWorldCreateEvent, BuildWorldPostCreateEvent, BuildWorldDeleteEvent, BuildWorldPostDeleteEvent, BuildWorldRenameEvent, BuildWorldUnimportEvent, BuildWorldStatusChangeEvent), plus backup and folder events in event.backup and event.folder.

  • WorldService.importWorlds() for bulk import spread across ticks.

  • WorldStatusRegistry and NavigatorCategoryRegistry, exposed via BuildSystem.getStatusRegistry() / getNavigatorCategoryRegistry().

  • Displayable.getIconSkullTexture() for custom head icons.

  • PhysicsCategory in api.world.data, the behaviours covered by per-world physics exceptions.

Last updated