> For the complete documentation index, see [llms.txt](https://docs.ohalee.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ohalee.com/products/obsidian-license/tiers-and-entitlements.md).

# Tiers & Entitlements

Sell Basic, Premium and one-off deals from a single build, using values the plugin can trust because they are signed.

Every license can carry a **tier** (a free-form label like `basic` or `premium`) and an **entitlement bag** (any flags and limits you like). Both are delivered inside the signed server response, so your plugin can read them at runtime and trust them.

That means one build, sold several ways. No separate Basic and Premium jars to compile, upload and keep in sync.

## Setting them

On any license, in the dashboard:

* **Tier**: `premium`
* **Entitlements**:

```json
{
  "max_homes": 25,
  "addons": true,
  "support_priority": "24h"
}
```

Change them whenever you like. The new values apply the next time that server checks its license, so an upgrade from Basic to Premium takes effect on the customer's next restart with nothing to redeliver.

## Reading them

With the [license check](/products/obsidian-license/license-check.md):

```java
ObsidianLicense.Result license = client.check(key, "my-plugin", machineId);

boolean premium = "premium".equals(license.tier());
boolean addons  = license.has("addons");

Object limit = license.entitlements().get("max_homes");
int maxHomes = limit instanceof Number n ? n.intValue() : 3;
```

With a [protected plugin](/products/obsidian-license/protected-plugins.md), the same values arrive on `PluginContext`:

```java
@Override
public void onEnable(PluginContext ctx) {
    boolean premium = "premium".equals(ctx.tier());
    long maxHomes = ctx.entitlementLong("max_homes", 3);
    if (ctx.hasEntitlement("addons")) {
        // enable the gated feature
    }
}
```

## Why this is safe to gate on

The tier and entitlement block is part of the message the server signs with its Ed25519 key, and your plugin verifies that signature against the public key you pinned in the config. A customer cannot edit the JSON in flight, and a fake server cannot mint a premium answer without the private key.

The usual caveat applies: in license-check mode someone who edits your jar can change what your code does with the answer. Signing guarantees the values are authentic, not that your own code honours them.

## Practical patterns

**Free tier in the same build.** Issue a license with `tier: free` and no entitlements, and gate the paid features. Now the free version and the paid version are one artifact, and an upgrade is a dashboard edit.

**Per-customer limits.** `max_players`, `max_regions`, `max_homes`. A customer who outgrows their plan gets a number changed, not a new download.

**Time-limited add-ons.** Combine an entitlement with the license expiry to sell a season pass on top of a perpetual license.

**Beta features.** Put `beta_ui: true` on a handful of licenses, ship the code to everyone, and enable it only where you want feedback. Pair it with the beta release channel when the change is big enough to need a separate build.
