> For the complete documentation index, see [llms.txt](https://buildsystem.eintosti.de/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://buildsystem.eintosti.de/v4/managing-worlds/downloading-a-world.md).

# Downloading Worlds

`/worlds download <world>` packs a world into a save a player can drop into their client's `saves` folder, and sends them a clickable link to fetch it. The feature is **off by default** — enabling it opens a port on the server that serves world data.

***

## Why an export is needed

Since Paper 26.1 a server world is no longer its own folder. Every world is a *dimension* of the main level:

```
worlds/<level-name>/
├── level.dat
└── dimensions/minecraft/
    ├── overworld/
    ├── the_nether/
    ├── the_end/
    └── <your-world>/        <- a BuildSystem world lives here
        ├── region/
        └── data/minecraft/world_gen_settings.dat
```

Copying that folder out by hand gives you something the client cannot open. It has no `level.dat`, its chunks sit under the wrong dimension, and its saved data is in the wrong place: Paper keeps a per-world copy of the level-scoped files (`world_gen_settings.dat`, `game_rules.dat`, `weather.dat`, …) inside each dimension folder, while a single-player save keeps one set at the root. A client that finds no generator settings there refuses the world outright.

The export rebuilds all of that. The world becomes the save's overworld, gets a `level.dat` named after it, and its data is split by scope:

```
<world>/
├── level.dat                                       from the main level, renamed and scrubbed
├── data/minecraft/                                 level-scoped: world_gen_settings, game_rules, weather, …
└── dimensions/minecraft/overworld/
    ├── region/  entities/  poi/
    └── data/minecraft/                             dimension-scoped: raids, world_border, chunk_tickets
```

A world still stored in the pre-26.1 flat layout already is a save, so only its `level.dat` is rewritten - it keeps its own nether and end, and dimensions belonging to other worlds are left behind.

***

## Enabling downloads

```yaml
world:
  download:
    enabled: false
    port: 8080
    url: "http://localhost:8080"
    expiration-minutes: 30
    max-size-mb: 2048
    max-storage-mb: 8192
    max-concurrent-downloads: 3
```

| Key                        | Default                   | Description                                                          |
| -------------------------- | ------------------------- | -------------------------------------------------------------------- |
| `enabled`                  | `false`                   | Runs the download server.                                            |
| `port`                     | `8080`                    | Port the server listens on. Must be reachable by players.            |
| `url`                      | `"http://localhost:8080"` | Base address players are linked to.                                  |
| `expiration-minutes`       | `30`                      | How long a link stays valid. The archive is deleted when it expires. |
| `max-size-mb`              | `2048`                    | Largest single export.                                               |
| `max-storage-mb`           | `8192`                    | Budget shared by all live downloads.                                 |
| `max-concurrent-downloads` | `3`                       | Transfers served at once.                                            |

Set `url` to whatever players actually reach — a domain, or the address of a reverse proxy. The server speaks plain HTTP, so put it behind a proxy and point `url` at the HTTPS address if links leave your network.

Toggling `enabled` takes effect on `/config reload`; no restart needed.

***

## Downloading a world

* **Command**: `/worlds download <world>`
* **Permission**: `buildsystem.download` (`OP`, supports `.self` / `.other`)

The world is saved first - waiting for its chunks to reach disk, so the archive never catches a region file mid-write - then zipped in the background. An animated progress bar runs in the action bar while it packs, showing real progress - bytes packed against the world's total size - with a sweeping highlight and spinner so a pause on one large region file still reads as working. When it is ready the player gets a clickable message; the link opens in their browser. Only one export per player runs at a time.

Restyle the bar through `worlds_download_progress` in `messages.yml`, which takes `%world%`, `%bar%`, `%percent%` and `%spinner%`. The finished message is `worlds_download_finished`; its `%button%` placeholder marks where the clickable link goes, and only that segment carries the click and hover - its text is `worlds_download_button`.

Extract the archive into `.minecraft/saves/` and the world appears in the single-player list.

{% hint style="info" %}
A dimension world has no `level.dat` of its own, so its export borrows the main level's - gamemode and difficulty start out matching the server's main level rather than the exported world. A flat-layout world keeps its own settings.
{% endhint %}

***

## What is exposed

Treat a link as private. Everything else is closed off:

* Each export is reachable only through a random 256-bit token that forms the entire URL path. Requests cannot name a file, so nothing outside `plugins/BuildSystem/downloads` is reachable.
* Archives and their links are deleted together: on expiry, on reload, and on shutdown. Anything left behind by a crash is wiped at startup, since its link did not survive the restart.
* An unknown or expired link returns a bare `404`.
* A link is pinned to the first client that uses it. Forwarding it to someone else does not work, while the player who asked for it can still retry or resume.
* Requests are rate limited to 30 per minute per address, and concurrent transfers are capped, so one host cannot saturate the endpoint. A request over the transfer cap gets `503` with `Retry-After`.
* Exports never include player data (`players/`, `playerdata/`, `stats/`, `advancements/`), so downloading the main world does not hand out the whole server's inventories. The exported `level.dat` also drops the server's brand, version and server-side datapacks.

{% hint style="warning" %}
`buildsystem.download` hands out the full contents of a world. Grant it with the same care as `buildsystem.backup`.
{% endhint %}
