Skip to content

Templates

A template is a tree of files (configs, plugins, world data) the daemon unpacks into an instance’s working directory before the instance boots. Groups reference templates by name. The controller content-addresses each template by a hash over its files and keeps every prior hash as a version snapshot.

prexorctl template exposes three subcommands against the controller’s /api/v1/templates REST API:

SubcommandEndpointPermission
template listGET /api/v1/templatestemplates.view
template versions <name>GET /api/v1/templates/{name}/versionstemplates.view
template rollback <name>POST /api/v1/templates/{name}/rollbacktemplates.update

There is no CLI upload, create, file-browse, or delete command. Those operations exist on the REST API and the dashboard but are not wired into prexorctl. See REST routes below.

Source: cli/cmd/template.go, java/cloud-controller/.../rest/route/TemplateRoutes.java.

Global flags

Every subcommand inherits the root persistent flags from cli/cmd/root.go:

FlagShortDefaultEffect
--json-jfalseEmit JSON instead of a table. Also enabled by PREXOR_OUTPUT=json.
--controller <url>-ccontext valueOverride the controller URL for this invocation.
--token <token>-tcontext valueOverride the auth token.
--context <name>active contextUse a named context for this invocation.
--no-colorfalseDisable colored output.
--asciifalseASCII glyphs only (no unicode box drawing).
--verbose-vfalsePrint HTTP request/response details.

All three subcommands call requireAuth() first. With no controller URL configured and no --controller/PREXOR_CONTROLLER override, they fail with:

no controller URL configured -- run 'prexorctl setup'

template list

List every template registered on the controller.

Terminal window
prexorctl template list
prexorctl template list --json

Calls GET /api/v1/templates. The controller filters out templates whose name starts with _ (synthetic module-plugin templates), so they never appear in CLI output.

Table columns, in order:

ColumnSource fieldRendering
NAMEnamecode style
HASHhashfirst 8 characters
SIZEsizeByteshumanized (B / KB / MB)
DESCRIPTIONdescriptiondim style

Example:

Terminal window
prexorctl template list
Listing templates on controller.example.com
NAME HASH SIZE DESCRIPTION
lobby a1b2c3d4 4.2 MB Hub + lobby plugins
survival 9f8e7d6c 812.0 KB Survival ruleset
─────────────────────────────────────────────
2 templates

SIZE formatting (formatBytes in cli/cmd/template.go):

  • 00 B
  • below 1024 → N B (no decimals)
  • below 1 MiB → N.N KB (one decimal, divided by 1024)
  • otherwise → N.N MB (one decimal, divided by 1024×1024)

JSON output

--json prints the raw template array straight from the controller. Each object carries the full TemplateDtoMapper.toDto shape — the table hides platform, and hash is not truncated:

[
{
"name": "lobby",
"description": "Hub + lobby plugins",
"platform": "paper",
"hash": "a1b2c3d4e5f6...",
"sizeBytes": 4404019
}
]

The CLI transparently unwraps the controller’s pagination envelope ({"data":[...]}) via Client.GetList, so --json always yields a bare array.

template versions <name>

Show the recorded version snapshots for one template, newest first.

Terminal window
prexorctl template versions lobby
prexorctl template versions lobby --json
  • Argument: <name> — exactly one (cobra.ExactArgs(1)). Omitting it or passing more than one fails before any request is made.
  • Calls GET /api/v1/templates/{name}/versions.
  • The controller returns 404 NOT_FOUND (Template not found: <name>) when the template does not exist; the CLI surfaces that as the request error.

Table columns:

ColumnSourceRendering
#computeddescending index, len(versions) - i — highest number is newest
HASHhashfirst 8 characters
SIZEsizeByteshumanized (same formatBytes rules as list)
CREATEDcreatedAtdim style, printed verbatim

The # column is a display index, not a stored version number. Row i (0 = first returned = newest) is labeled len(versions) - i, so the newest snapshot always carries the largest number.

Example:

Terminal window
prexorctl template versions lobby
Versions of template lobby on controller.example.com
# HASH SIZE CREATED
3 a1b2c3d4 4.2 MB 2026-06-07T11:02:14Z
2 77ad19be 4.1 MB 2026-06-05T08:41:00Z
1 0c44ef21 4.0 MB 2026-05-30T19:15:33Z
───────────────────────────────────────────
3 versions

JSON output

--json prints the controller’s version array verbatim. Each element is the TemplateDtoMapper.toVersionDto shape:

[
{
"templateName": "lobby",
"hash": "a1b2c3d4e5f6...",
"sizeBytes": 4404019,
"createdAt": "2026-06-07T11:02:14Z"
}
]

versions uses Client.Get (not GetList), so it does not unwrap a pagination envelope. The controller returns a bare array for this route.

template rollback <name>

Restore a template to a recorded snapshot.

Terminal window
prexorctl template rollback lobby
  • Argument: <name> — exactly one (cobra.ExactArgs(1)).
  • Calls POST /api/v1/templates/{name}/rollback.
  • On success the CLI prints Template '<name>' rolled back.

JSON output

Terminal window
prexorctl template rollback lobby --json
{"status":"rolled_back","template":"lobby"}

The CLI synthesizes this object locally from the request argument. It does not echo the controller’s response body, which is shaped differently ({"status":"restored","hash":"<hash>"} from TemplateDtoMapper.rollbackResponse).

Required request body — CLI/controller mismatch

The controller’s rollbackTemplate handler requires a target hash in the JSON request body and rejects an empty one:

400 BAD_REQUEST Missing 'hash' in request body

prexorctl template rollback posts a nil body (client.Post(path, nil, nil) in cli/cmd/template.go), so against the current controller the command returns that 400. There is no CLI flag to supply a hash. To roll back to a specific recorded version today, call the REST endpoint directly with the target hash from template versions --json:

Terminal window
prexorctl template versions lobby --json # find the target hash
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"hash":"0c44ef21..."}' \
https://controller.example.com/api/v1/templates/lobby/rollback

Controller responses for POST /api/v1/templates/{name}/rollback:

StatusCodeCondition
200restoredSnapshot restored; body {"status":"restored","hash":"<hash>"}
400BAD_REQUESThash missing or blank in body
404NOT_FOUNDTemplate not found, or no recorded version with that hash
422NO_SNAPSHOTVersion recorded but its snapshot archive is unavailable

A successful rollback rewrites the template’s current files to the snapshot. Future instance starts in groups referencing the template unpack the restored version; running instances are unaffected until they restart.

Scripting

list --json and versions --json are stable, scriptable surfaces. List every template name:

Terminal window
prexorctl template list --json | jq -r '.[].name'

Find the second-newest hash of a template (the typical “previous version” rollback target), then roll back via REST:

Terminal window
name=lobby
prev=$(prexorctl template versions "$name" --json | jq -r '.[1].hash')
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"hash\":\"$prev\"}" \
"https://controller.example.com/api/v1/templates/$name/rollback"

The list DTO exposes name, description, platform, hash, and sizeBytes — there is no timestamp field on a list row. Per-version timestamps come from template versions --json (createdAt).

REST routes not exposed by the CLI

TemplateRoutes.register() defines the full API surface. prexorctl covers only list, versions, and rollback. The rest are dashboard/REST-only:

Method + pathOperation
POST /api/v1/templatesCreate template
GET /api/v1/templates/{name}Get one template
PATCH /api/v1/templates/{name}Update description/platform
DELETE /api/v1/templates/{name}Delete template
DELETE /api/v1/templates/{name}/versions/{hash}Delete a version snapshot
GET /api/v1/templates/{name}/filesBrowse files (path, version query params)
POST /api/v1/templates/{name}/files/mkdirCreate directory
GET /api/v1/templates/{name}/files/contentRead file content (text only)
PUT /api/v1/templates/{name}/files/contentWrite file content
GET /api/v1/templates/{name}/files/downloadDownload a file
POST /api/v1/templates/{name}/files/uploadUpload file(s); extract=true unzips
POST /api/v1/templates/{name}/files/extractExtract a ZIP already in the template
POST /api/v1/templates/{name}/files/renameRename a file
DELETE /api/v1/templates/{name}/filesDelete a file or directory
POST /api/v1/templates/{name}/rehashRecompute the content hash
GET/PUT /api/v1/templates/{name}/variablesRead/replace {{var}} substitutions
GET /api/v1/templates/{name}/variables/scanScan files for {{var}} placeholders
GET /api/v1/templates/{name}/inheritanceInheritance chain (basebase-<platform> → name)
GET /api/v1/templates/{name}/searchFull-text search (q, maxResults ≤ 200)
GET /api/v1/templates/{name}/exportExport as tar.gz
POST /api/v1/templates/importImport a tar.gz (multipart)

Names must match [a-z0-9_][a-z0-9_-]* and be at most 32 characters (enforced on import). Templates prefixed _module-plugins- are synthetic and cannot be deleted (400 PROTECTED).

Next up