# Configuration Everything lives in `plugins/Frame/config.yml`. Text is [MiniMessage](https://docs.advntr.dev/minimessage/format.html). There are only three ideas in the file: 1. **Defaults** — the plain settings. 2. **Groups** — override some of those, for some players. 3. **Conditions** — reusable "who counts" tests, used by groups. Everything in the file is the **bottom layer**. Any plugin using the Frame API sits above it, so your settings show through wherever no plugin has an opinion. --- ## Top level | Setting | Default | What it does | |---|---|---| | `config-version` | `2` | Layout version. Don't edit by hand — Frame uses it to add new settings on upgrade. | | `server-name` | `""` | This server's name, for `%server%` and a group's `servers:` matcher. Blank asks the server. | | `refresh-interval` | `20` | Ticks between refreshes. Only changed values are sent, so short intervals are cheap. | | `animation-interval` | `20` | Default ticks between animation frames. | ### Channels ```yml channels: tablist: true nametag: true sidebar: false bossbar: true actionbar: true ``` Switch a surface off and Frame hands it back to vanilla and other plugins. Toggle at runtime with `/frame channel <name> on|off` — that lasts until restart. ### Scoreboard ```yml scoreboard: manage: true yield-to-other-plugins: true ``` `manage: false` keeps Frame off scoreboards entirely (nametags, sidebar and objectives switch off; the tablist keeps working). `yield-to-other-plugins` is covered in [[Frame/Server Owners/Overview]]. ### LuckPerms ```yml luckperms: enabled: true use-prefix: true use-suffix: true ``` Fills in wherever the file leaves something blank. --- ## Tablist ```yml tablist: header: - "<gradient:#5e4fa2:#f79459><bold>MY SERVER</bold></gradient>" - "<gray>Welcome back, <white>%player%<gray>!" footer: - "<gray>%online%<dark_gray>/<gray>%max% online" name-format: "" spectator-fix: true allow-skin-override: false ``` `name-format` blank uses the LuckPerms prefix/suffix. Multiple `header` lines are stacked, not animated. **`spectator-fix`** — the client greys spectators out and forces them to the bottom of the list, ignoring your sort order. This reports them as survival to *other* players so your sorting survives someone switching to spectator. Their own view is unchanged. **`allow-skin-override`** — changing a tablist head means removing and re-adding the entry, which is briefly visible. Leave off unless you need it. ### Sorting Rules apply in order: the first decides position, the rest break ties. ```yml sorting: - by: group - by: text placeholder: "%player%" direction: a-to-z ``` | `by:` | Needs | Notes | |---|---|---| | `group` | `order:` (optional) | LuckPerms primary group. Omit `order:` to use your LuckPerms group weights. | | `permission` | `order:` | First permission the player has, best first. | | `value` | `placeholder:`, `order:` | Matches the placeholder's output against expected values. | | `number` | `placeholder:` | `direction:` `highest-first` (default) or `lowest-first`. | | `text` | `placeholder:` | `direction:` `a-to-z` (default) or `z-to-a`. | Delete the whole block for vanilla alphabetical ordering. ### Sorting within a category A rule can carry its own rules inside it. `nested:` gives a branch per category, and `then:` covers the categories you did not write a branch for: ```yml sorting: - by: group order: [staff, vip, default] nested: staff: - by: text placeholder: "%player%" vip: - by: number placeholder: "%vip_level%" direction: highest-first then: - by: text placeholder: "%player%" ``` Staff sort alphabetically among themselves, VIPs by level, everyone else alphabetically. A nested rule can only reorder players **within** their own category — it can never move someone out of their block. --- ## Nametags, sidebar and objectives ```yml nametag: prefix: "" suffix: "" color: "" # one of the 16 named colours sidebar: show-numbers: false hide-blank-lines: false title: "<gold><bold>MY SERVER" lines: - "<gray>Player: <white>%player%" belowname: enabled: false value: "%health%" title: "<red>❤" playerlist-objective: enabled: false value: "%ping%" display: number # number | hearts text: "<gray>%ping%<dark_gray>ms" ``` `show-numbers` turns off vanilla's red score numbers on the sidebar. Sidebars are capped at 15 lines. `nametag.color` sets the colour of the name itself above a player's head. It has to be one of the sixteen named colours — a vanilla scoreboard-team limit, not a Frame one. An unrecognised name is warned about once and ignored. Put RGB in `prefix`/`suffix` instead. `hide-blank-lines` drops any sidebar line that renders to nothing, rather than drawing an empty row. **Off by default**, because a bare `''` line is the usual way to space a sidebar and turning this on deletes those spacers. A line emptied by a `<condition:…>` is dropped either way — that is the point of wrapping a whole line. Both objectives need `value` to resolve to a number. `playerlist-objective.text` is a formatted replacement for the raw number and needs a 1.20.3+ client; older clients fall back to the number. Vanilla allows only one below-name slot, so everyone sees the same one. --- ## Conditional text Wrap anything in `<condition:'value'>…</condition>` and it only shows when the value is `true`. Placeholders resolve first, so put one straight in: ```yml sidebar: lines: - " <white>Player: %player%" - "<condition:'%lead_team_exists%'> <white>Team: %lead_team_name%</condition>" ``` **Wrap the whole line** and a false condition removes the row entirely — no empty row and no gap, and the lines below close up. Wrap only part of it and the rest of the line still shows, which is what you want for an optional badge: ```yml - " <white>Player: %player% <condition:'%player_is_op%'><gold>[OP]</gold></condition>" ``` Truthy means the literal word `true`, case-insensitive. **Anything else is false**, including a placeholder that never resolved — so a typo hides the line rather than printing `%broken_placeholder%` to everyone. Several conditions on one line are independent of each other. This works in every text setting, not just the sidebar: headers, footers, nametags and `name-format` too. > [!tip] Same syntax as Sign > These are the same conditions Sign uses for nametags, so a value that works > in one reads the same way in the other. --- ## Conditions Reusable tests, one idea per line. ```yml conditions: is-staff: any: - permission: frame.staff - permission: frame.admin ``` Group checks with `all:` (every check passes), `any:` (at least one) or `none:` (none may pass). They nest, and a condition can reference another by name. Each check is either `permission: some.node`, or a `placeholder:` plus **one** comparison: | Comparison | Meaning | |---|---| | `equals`, `not-equals` | Exact match, ignoring case | | `contains`, `not-contains` | Substring | | `starts-with`, `ends-with` | Prefix / suffix | | `matches` | Regular expression | | `above`, `at-least` | Greater than / or equal | | `below`, `at-most` | Less than / or equal | `above` and `below` compare as numbers when both sides are numeric, and as text otherwise — so a placeholder returning `N/A` is simply false rather than an error. --- ## Groups Override some settings, for some players. ```yml groups: - name: nether worlds: [world_nether] nametag: prefix: "<red>☠ " - name: lobby servers: [lobby] sidebar: title: "<aqua><bold>LOBBY" - name: staff condition: is-staff tablist: name-format: "<red>[Staff] <white>%player%" ``` A group applies when **every** matcher it lists passes — `worlds:`, `servers:` (compared against `server-name`) and `condition:`. A group with no matchers applies to everyone. Groups apply top to bottom and **later groups win**, so read the list as increasing specificity. Anything a group leaves out is inherited. > [!important] A group describes the player it matches > The nether group above puts a skull on people **in** the nether — not on everyone who can see them. Header, footer and sidebar are about the viewer's own screen instead, since those *are* their screen. Groups can set `tablist.header`, `tablist.footer`, `tablist.name-format`, `nametag.prefix`, `nametag.suffix`, `nametag.color`, `sidebar.title`, `sidebar.lines` and `sidebar.enabled`. --- ## Animated text Put `<cycle>` between frames to animate any line: ```yml header: - "<gray>Welcome, <white>%player%<cycle:2s><gray>Welcome, <yellow>%player%" ``` `<cycle:duration>` sets that line's speed — `500ms`, `2s`, `40t`, or a bare number of ticks. Put it on any one tag and leave the rest bare; without one, `animation-interval` applies. Write `\<cycle>` for a literal. Every other character is ordinary text, `|` included. For anything reused in more than one place, define it once: ```yml animations: loading: interval: 200ms frames: - "<gray>Loading<dark_gray>." - "<gray>Loading<dark_gray>.." - "<gray>Loading<dark_gray>..." ``` and write `%anim:loading%` anywhere. --- ## Placeholders Frame always understands `%player%`, `%displayname%`, `%ping%`, `%world%`, `%online%`, `%max%`, `%server%` and `%tps%`. ```yml placeholders: placeholderapi: true ``` adds PlaceholderAPI on top, including relational placeholders, which resolve per viewer-and-target pair. --- ## Related - [[Frame/Server Owners/Overview]] - [[Frame/Server Owners/Commands]]