> ## Documentation Index
> Fetch the complete documentation index at: https://simplecloud.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Prefixes

> Learn how SimpleCloud's prefixes plugin handles rank groups, chat formatting, tab list entries, name tags and cross-server sync

export const DependencySnippet = ({dependencies = [], repositories = [{
  id: "simplecloud",
  url: "https://repo.simplecloud.app/snapshots"
}, {
  id: "buf",
  url: "https://buf.build/gen/maven"
}], language = "en", type = "snapshot"}) => {
  const [versions, setVersions] = useState({});
  const [failed, setFailed] = useState({});
  const dependencyKey = dependencies.map(dep => `${dep.groupId}:${dep.artifactId}:${dep.version || ""}`).join("|");
  const copy = {
    en: {
      loading: "Loading the latest dependency version from the Maven repository...",
      failed: "The latest version could not be loaded. The examples below use dynamic version selectors that resolve the newest available artifact during dependency resolution."
    },
    de: {
      loading: "Die neueste Dependency-Version wird aus dem Maven-Repository geladen...",
      failed: "Die neueste Version konnte nicht geladen werden. Die Beispiele unten verwenden dynamische Versionsselektoren, die beim Auflösen der Dependency das neueste verfügbare Artefakt verwenden."
    }
  };
  const text = copy[language] || copy.en;
  useEffect(() => {
    let cancelled = false;
    dependencies.forEach(dep => {
      if (dep.version) return;
      const key = `${dep.groupId}:${dep.artifactId}`;
      const fetchVersion = async () => {
        try {
          const url = type === "snapshot" ? `https://repo.simplecloud.app/api/maven/latest/version/snapshots/${dep.groupId.replace(/\./g, "/")}/${dep.artifactId}?type=raw` : `https://search.maven.org/solrsearch/select?q=g:${dep.groupId}+AND+a:${dep.artifactId}&rows=1&wt=json`;
          const response = await fetch(url, {
            cache: "no-store"
          });
          if (!response.ok) throw new Error(`Version request failed: ${response.status}`);
          if (type === "snapshot") {
            const version = (await response.text()).trim();
            if (!cancelled && version) {
              setVersions(current => ({
                ...current,
                [key]: version
              }));
            }
            return;
          }
          const data = await response.json();
          const version = data.response?.docs?.[0]?.latestVersion;
          if (!cancelled && version) {
            setVersions(current => ({
              ...current,
              [key]: version
            }));
          }
        } catch (error) {
          if (!cancelled) {
            setFailed(current => ({
              ...current,
              [key]: true
            }));
          }
        }
      };
      fetchVersion();
    });
    return () => {
      cancelled = true;
    };
  }, [dependencyKey, type]);
  const keyFor = dep => `${dep.groupId}:${dep.artifactId}`;
  const hasPendingVersion = dependencies.some(dep => !dep.version && !versions[keyFor(dep)] && !failed[keyFor(dep)]);
  const hasFailedVersion = dependencies.some(dep => failed[keyFor(dep)]);
  const getVersion = (dep, buildTool) => {
    if (dep.version) return dep.version;
    const version = versions[keyFor(dep)];
    if (version) return version;
    return buildTool === "maven" ? "[0,)" : "latest.integration";
  };
  const generateKotlin = () => {
    const repoLines = repositories.map(repo => `    maven("${repo.url}")`).join("\n");
    const depLines = dependencies.map(dep => {
      const depType = dep.type || "implementation";
      return `    ${depType}("${dep.groupId}:${dep.artifactId}:${getVersion(dep, "gradle")}")`;
    }).join("\n");
    return `repositories {\n${repoLines}\n}\n\ndependencies {\n${depLines}\n}`;
  };
  const generateGroovy = () => {
    const repoLines = repositories.map(repo => `    maven { url '${repo.url}' }`).join("\n");
    const depLines = dependencies.map(dep => {
      const depType = dep.type || "implementation";
      return `    ${depType} '${dep.groupId}:${dep.artifactId}:${getVersion(dep, "gradle")}'`;
    }).join("\n");
    return `repositories {\n${repoLines}\n}\n\ndependencies {\n${depLines}\n}`;
  };
  const generateMaven = () => {
    const repoLines = repositories.map(repo => `    <repository>
      <id>${repo.id || "repo"}</id>
      <url>${repo.url}</url>
    </repository>`).join("\n");
    const depLines = dependencies.map(dep => {
      const scope = dep.type === "compileOnly" ? "provided" : "compile";
      return `    <dependency>
      <groupId>${dep.groupId}</groupId>
      <artifactId>${dep.artifactId}</artifactId>
      <version>${getVersion(dep, "maven")}</version>
      <scope>${scope}</scope>
    </dependency>`;
    }).join("\n");
    return `<repositories>\n${repoLines}\n</repositories>\n\n<dependencies>\n${depLines}\n</dependencies>`;
  };
  if (hasPendingVersion) {
    return <Info>{text.loading}</Info>;
  }
  return <>
      {hasFailedVersion && <Warning>
          {text.failed}
        </Warning>}

      <Tabs>
        <Tab title="Gradle (Kotlin)">
          <CodeBlock language="kotlin">{generateKotlin()}</CodeBlock>
        </Tab>
        <Tab title="Gradle (Groovy)">
          <CodeBlock language="groovy">{generateGroovy()}</CodeBlock>
        </Tab>
        <Tab title="Maven">
          <CodeBlock language="xml">{generateMaven()}</CodeBlock>
        </Tab>
      </Tabs>
    </>;
};

## Overview

The Prefixes Plugin handles rank displays on your servers. Every player belongs to a group, and that group defines how the player appears in chat, in the tab list and above their head.

The plugin:

* Applies prefixes, suffixes, colors and display names to chat, tab list and name tags
* Reads groups either from its own `config.yml` or from LuckPerms
* Syncs chat messages and tab list entries across the servers of your network
* Provides an API to read prefix data and register custom groups

## Supported Software

| Software      | Support    |
| ------------- | ---------- |
| Paper & Forks | ✅ Full     |
| Minestom      | 🔄 Planned |
| Fabric        | 🔄 Planned |

<Note>
  Want to add support for another server software? Submit a pull request on
  [GitHub](https://github.com/simplecloudapp/prefixes-plugin/pulls)!
</Note>

## Quick Setup

1. Download the plugin from [GitHub](https://github.com/simplecloudapp/prefixes-plugin/releases)
2. Place it in your server template's plugins folder
3. Start your server
4. Edit `config.yml` and `messages.yml` to your needs

***

## Configuration

### config.yml

The main configuration file. It controls the group source, the groups themselves, individual plugin features and cross-server sync.

```yaml theme={null}
# The config format version. Do not change this value.
version: 1

# ─────────────────────────────────────────────────────────────────────────────
# General
# Controls how player groups are resolved.
# ─────────────────────────────────────────────────────────────────────────────
general:
  # Where groups are read from.
  # Available: CONFIG, LUCKPERMS
  source: CONFIG

  # Group used when a player matches no other group.
  # Must reference a group defined below when source is CONFIG.
  default-group: default

# ─────────────────────────────────────────────────────────────────────────────
# Features
# Each part of the plugin can be enabled independently.
# ─────────────────────────────────────────────────────────────────────────────
features:
  # Format chat messages using the group's chat-format.
  chat: true

  # Apply prefixes, suffixes, colors and sorting to the tab list.
  tablist: true

  # Apply the group's configured display-name.
  # When disabled, the player's normal Minecraft name is used.
  display-name: true

# ─────────────────────────────────────────────────────────────────────────────
# Sync
# Shares chat messages and tab list entries across the SimpleCloud network.
# Disabling sync does not disable local formatting.
# ─────────────────────────────────────────────────────────────────────────────
sync:
  # Master switch for all cross-server communication.
  enabled: true

  # Select which features are synchronized.
  # The corresponding feature above must also be enabled.
  channels:
    chat: true
    tablist: true

  # Groups and persistent servers from which updates are received.
  #
  # Special values:
  #   CURRENT - Current server group, or this persistent server.
  #   ALL     - Every server in the network. Must be used alone.
  #
  # Literal SimpleCloud group names and persistent server IDs can also be used.
  # Multiple sources can be combined, except when using ALL.
  sources:
  - CURRENT

# ─────────────────────────────────────────────────────────────────────────────
# Groups
# Only used when general.source is CONFIG.
#
# Supported placeholders:
#   <prefix>       Group prefix
#   <suffix>       Group suffix
#   <color>        Group color
#   <playername>   Normal Minecraft player name
#   <displayname>  Formatted display name
#   <message>      Chat message
# ─────────────────────────────────────────────────────────────────────────────
groups:
- name: owner
  # Higher priorities are selected first and sorted higher in the tab list.
  priority: 100
  # Permission required to use this group.
  permission: 'simplecloud.prefix.group.owner'
  prefix: '<#DC2626><bold>Owner</bold> <#475569>| '
  suffix: ''
  # Used by the <color> placeholder and player name coloring.
  color: '<#DC2626>'
  # Formatted player name.
  display-name: '<color><playername>'
  # Format used when features.chat is enabled.
  chat-format: '<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>'

- name: admin
  priority: 90
  permission: 'simplecloud.prefix.group.admin'
  prefix: '<#F59E0B><bold>Admin</bold> <#475569>| '
  suffix: ''
  color: '<#F59E0B>'
  display-name: '<color><playername>'
  chat-format: '<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>'

- name: default
  priority: 0
  # An empty permission matches every player.
  permission: ''
  prefix: '<#94A3B8>Player <#475569>| '
  suffix: ''
  color: '<#94A3B8>'
  display-name: '<color><playername>'
  chat-format: '<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>'
```

**LuckPerms mapping**

When `source` is `LUCKPERMS`, every value is read from the LuckPerms group:

| LuckPerms value     | Prefixes value | Default if unset                                                    |
| ------------------- | -------------- | ------------------------------------------------------------------- |
| Group name          | `name`         | –                                                                   |
| Group weight        | `priority`     | `0`                                                                 |
| Group prefix        | `prefix`       | empty                                                               |
| Group suffix        | `suffix`       | empty                                                               |
| Meta `color`        | `color`        | `#FFFFFF`                                                           |
| Meta `display-name` | `display-name` | `<color><playername>`                                               |
| Meta `chat-format`  | `chat-format`  | `<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>` |

```bash theme={null}
/lp group admin setweight 90
/lp group admin meta addprefix 90 "<#F59E0B><bold>Admin</bold> <#475569>| "
/lp group admin meta set color "<#F59E0B>"
/lp group admin meta set display-name "<color><playername>"
/lp group admin meta set chat-format "<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>"
```

#### Placeholders

Here is a list of all available placeholders.

| Placeholder     | Available in                  | Description                                          |
| --------------- | ----------------------------- | ---------------------------------------------------- |
| `<prefix>`      | `chat-format`                 | Prefix of the player's group                         |
| `<suffix>`      | `chat-format`                 | Suffix of the player's group                         |
| `<color>`       | `chat-format`, `display-name` | Team color of the player's group, applied as styling |
| `<playername>`  | `chat-format`, `display-name` | Plain player name                                    |
| `<name>`        | `chat-format`                 | Alias of `<playername>`                              |
| `<displayname>` | `chat-format`                 | Rendered display name of the player                  |
| `<message>`     | `chat-format`                 | Chat message content                                 |

<Tip>
  Use `<color>` instead of writing the same hex code into every value. It is applied as a style, so
  everything after it inherits the group color.
</Tip>

#### Feature switches

Use `features` to enable or disable each local part of the plugin:

* `chat` formats local chat messages with the group's `chat-format`
* `tablist` applies the group's prefix, suffix, color and priority to the tab list
* `display-name` applies the configured `display-name`; when disabled, the normal Minecraft name is used

#### Cross-server sync

Sync can share two things over the SimpleCloud network:

* **Chat**: formatted chat messages from players on other servers
* **Tab list**: tab list entries for players on other servers

Set `sync.enabled` to `false` to disable all cross-server communication without disabling local formatting. Under `channels`, choose whether chat, tab list entries or both are synchronized. A channel only works when its matching option under `features` is also enabled.

The `sources` list controls which servers this server receives updates from:

* `CURRENT` receives updates from the current server group, or only this persistent server when running as a persistent server
* `ALL` receives updates from every server in the network and must be the only list entry
* A server group name or persistent server ID receives updates from that specific source

You can combine multiple group names and persistent server IDs. To exchange updates in both directions, configure each participating server to receive from the other server's group or ID.

***

## Commands

| Command            | Permission                            | Description                 |
| ------------------ | ------------------------------------- | --------------------------- |
| `/scprefix`        | `simplecloud.prefixes.command`        | Shows the command overview. |
| `/scprefix help`   | `simplecloud.prefixes.command`        | Shows the command overview. |
| `/scprefix reload` | `simplecloud.prefixes.command.reload` | Reloads the configuration.  |

***

## API

The API lets you read the prefix data of a player and register your own groups.

### Dependency

<DependencySnippet
  dependencies={[
{
  groupId: "app.simplecloud.plugin",
  artifactId: "prefixes-api",
  type: "compileOnly",
},
]}
  repositories={[
{ id: "simplecloud", url: "https://repo.simplecloud.app/snapshots" },
]}
/>

### Access the API

<Tabs>
  <Tab title="Paper">
    <CodeGroup>
      ```kotlin Kotlin theme={null}
      import app.simplecloud.prefixes.api.PrefixesApi
      import org.bukkit.Bukkit

      val api = Bukkit.getServicesManager().getRegistration(PrefixesApi::class.java)?.provider
          ?: throw IllegalStateException("PrefixesApi is not registered")
      ```

      ```java Java theme={null}
      import app.simplecloud.prefixes.api.PrefixesApi;
      import org.bukkit.Bukkit;
      import org.bukkit.plugin.RegisteredServiceProvider;

      RegisteredServiceProvider<PrefixesApi> registration =
          Bukkit.getServicesManager().getRegistration(PrefixesApi.class);

      if (registration == null) {
          throw new IllegalStateException("PrefixesApi is not registered");
      }

      PrefixesApi api = registration.getProvider();
      ```
    </CodeGroup>
  </Tab>
</Tabs>

### Player Management

Get the group of a player:

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val group = api.getPrimaryGroup(player.uniqueId).await()

  player.sendMessage(Component.text("Your group: ${group?.name ?: "none"}"))
  ```

  ```java Java theme={null}
  api.getPrimaryGroup(player.getUniqueId()).thenAccept(group -> {
      String name = group != null ? group.getName() : "none";

      player.sendMessage(Component.text("Your group: " + name));
  });
  ```
</CodeGroup>

Get the display values of a player:

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val data = api.getPrefixData(player.uniqueId).await()

  player.sendMessage(data.prefix.append(data.displayName).append(data.suffix))
  ```

  ```java Java theme={null}
  api.getPrefixData(player.getUniqueId()).thenAccept(data -> {
      player.sendMessage(data.getPrefix().append(data.getDisplayName()).append(data.getSuffix()));
  });
  ```
</CodeGroup>

Check whether a player is in a specific group:

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val vip = api.getGroups().find { it.name == "vip" } ?: return

  if (vip.containsPlayerAsync(player.uniqueId).await()) {
      player.sendMessage(Component.text("Welcome back!"))
  }
  ```

  ```java Java theme={null}
  PrefixesGroup vip = api.getGroups().stream()
      .filter(group -> group.getName().equals("vip"))
      .findFirst()
      .orElse(null);

  if (vip == null) return;

  vip.containsPlayerAsync(player.getUniqueId()).thenAccept(isVip -> {
      if (isVip) {
          player.sendMessage(Component.text("Welcome back!"));
      }
  });
  ```
</CodeGroup>

### Group Management

List all groups, highest priority first:

<CodeGroup>
  ```kotlin Kotlin theme={null}
  api.getGroups().forEach { group ->
      println("${group.priority} - ${group.name}")
  }
  ```

  ```java Java theme={null}
  api.getGroups().forEach(group ->
      System.out.println(group.getPriority() + " - " + group.getName()));
  ```
</CodeGroup>

Register your own group:

<CodeGroup>
  ```kotlin Kotlin theme={null}
  class VipGroup : PrefixesGroup {
      override val name = "vip"
      override val priority = 50
      override val permission = "simplecloud.prefix.group.vip"
      override val prefix = miniMessage.deserialize("<#A3E635>VIP <#475569>| ")
      override val suffix = Component.empty()
      override val color = TextColor.fromHexString("#A3E635")
      override val displayName = "<color><playername>"
      override val chatFormat = "<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>"

      override fun containsPlayer(id: UUID) =
          containsPlayerAsync(id).join()

      override fun containsPlayerAsync(id: UUID) =
          CompletableFuture.completedFuture(false)
  }

  if (!api.addGroup(VipGroup()).await()) {
      println("A group named vip already exists")
  }
  ```

  ```java Java theme={null}
  public class VipGroup implements PrefixesGroup {

      @Override
      public String getName() {
          return "vip";
      }

      @Override
      public int getPriority() {
          return 50;
      }

      @Override
      public String getPermission() {
          return "simplecloud.prefix.group.vip";
      }

      @Override
      public Component getPrefix() {
          return MiniMessage.miniMessage().deserialize("<#A3E635>VIP <#475569>| ");
      }

      @Override
      public Component getSuffix() {
          return Component.empty();
      }

      @Override
      public TextColor getColor() {
          return TextColor.fromHexString("#A3E635");
      }

      @Override
      public String getDisplayName() {
          return "<color><playername>";
      }

      @Override
      public String getChatFormat() {
          return "<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>";
      }

      @Override
      public boolean containsPlayer(UUID id) {
          return containsPlayerAsync(id).join();
      }

      @Override
      public CompletableFuture<Boolean> containsPlayerAsync(UUID id) {
          return CompletableFuture.completedFuture(false);
      }
  }

  api.addGroup(new VipGroup()).thenAccept(created -> {
      if (!created) {
          System.out.println("A group named vip already exists");
      }
  });
  ```
</CodeGroup>

***

## Best Practices

* Keep priorities unique so the tab list order stays predictable
* Give every group the same `chat-format` unless a rank should visibly stand out in chat
* Use `<color>` in `display-name` and `chat-format` instead of repeating hex codes
* Point `default-group` at a group with an empty `permission` so every player matches something
