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

# Settings

> Register your own screens into Discord's settings navigation.

### What is Settings?

`settings` is the registry of **settings screens**. Register an entry and it becomes a real route in Discord's settings navigation, listed under the Unbound section alongside the built-in General, Plugins, Design, and Developer pages.

It is a registry of *screens*, not of values. Persisting what those screens read and write is [storage](/modules/storage)'s job.

```ts theme={null}
import { settings } from '@unbound-app/api';
// equivalent: import settings from '@unbound-app/api/settings';
// runtime global: unbound.settings
```

<Note>
  Most plugins don't need this module. A plugin that exports `getSettingsPanel` already gets a settings button on its card in the Plugins list, and Unbound renders the returned node on a screen for you. Reach for `registerSettings` when you want a **top-level entry of your own** in the settings list, separate from your plugin's card.
</Note>

### Registering a screen

`registerSettings` takes one or more entries and adds them to the registry. Entries are keyed, so registering the same key twice overwrites the previous entry and logs a warning.

```tsx theme={null}
import { settings } from '@unbound-app/api';

import MyScreen from './my-screen';

settings.registerSettings({
	type: 'route',
	key: 'MY_PLUGIN_SETTINGS',
	useTitle: () => 'My Plugin',
	parent: null,
	section: 'Unbound',
	screen: {
		route: 'MY_PLUGIN_SETTINGS',
		getComponent: () => MyScreen,
	},
});
```

<Warning>
  Register in your plugin's `start` and call `removeSettings` with the same key in `stop`. Entries are not cleaned up for you, so a disabled plugin otherwise leaves a dead route behind.
</Warning>

### The entry shape

An entry mirrors Discord's `SETTING_RENDERER_CONFIG` route shape, which is why it looks the way it does.

<ParamField path="type" type="'route'" required>
  Always `'route'`. Routes are the only entry kind Unbound registers.
</ParamField>

<ParamField path="key" type="string" required>
  Unique identifier for the entry, and the key `removeSettings` deletes by. Built-ins use screaming snake case (`UNBOUND_GENERAL`); namespace yours so it can't collide.
</ParamField>

<ParamField path="useTitle" type="() => string" required>
  Hook returning the title shown in the list and the screen header. Being a hook, it can pull from [i18n](/modules/i18n) or from storage and re-render when either changes.
</ParamField>

<ParamField path="parent" type="string | null" required>
  The key of the parent route, or `null` for a top-level entry. Built-ins pass `null`.
</ParamField>

<ParamField path="screen" type="SettingsScreen" required>
  The screen this route renders.

  <Expandable title="SettingsScreen">
    <ParamField path="route" type="string" required>
      The route name. Built-ins set this to the same value as `key`.
    </ParamField>

    <ParamField path="getComponent" type="() => ComponentType<any>" required>
      Returns the component to render. Called lazily, so the component is only resolved when the screen is opened.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="section" type="string">
  The section label the entry is grouped under in the settings overview. Built-ins use `'Unbound'`.
</ParamField>

<ParamField path="usePredicate" type="() => boolean">
  Hook deciding whether the entry is shown. The built-in Developer page uses this to appear only while developer mode is on.
</ParamField>

<ParamField path="excludeFromDisplay" type="boolean">
  Hides the entry from the settings list while keeping the route navigable. Use it for screens you push to programmatically rather than ones the user picks from a list.
</ParamField>

<ParamField path="IconComponent" type="ComponentType<any>">
  Component rendered as the row's leading icon. Built-ins render Discord's `TableRowIcon` with an id resolved through [assets](/modules/assets).
</ParamField>

### Conditional and hidden entries

`usePredicate` is a hook, so it can subscribe to storage and hide or show the entry as settings change. This is how the Developer page appears only once developer mode is enabled.

```tsx theme={null}
import { settings, storage, assets } from '@unbound-app/api';
import { Discord } from '@unbound-app/api/metro/components';

const store = storage.getStore('my-plugin');

settings.registerSettings({
	type: 'route',
	key: 'MY_PLUGIN_ADVANCED',
	useTitle: () => 'Advanced',
	usePredicate: () => store.useSettingsStore(({ key }) => key === 'advanced').get('advanced', false),
	IconComponent: () => <Discord.TableRowIcon source={assets.getIDByName('WrenchIcon')} />,
	parent: null,
	section: 'Unbound',
	screen: {
		route: 'MY_PLUGIN_ADVANCED',
		getComponent: () => AdvancedScreen,
	},
});
```

### Removing and inspecting

<CodeGroup>
  ```ts removeSettings theme={null}
  // Accepts any number of keys.
  settings.removeSettings('MY_PLUGIN_SETTINGS', 'MY_PLUGIN_ADVANCED');
  ```

  ```ts sections theme={null}
  // The current registry, keyed by entry key.
  const registered = settings.sections();
  ```
</CodeGroup>

<Note>
  `sections()` returns only entries registered through this API. Unbound's own built-in screens are merged in separately when the navigation config is read, so they don't appear in the result.
</Note>

### Settings vs storage

The two modules are complementary and it's worth keeping them straight:

|                 | [settings](/modules/settings)         | [storage](/modules/storage)              |
| --------------- | ------------------------------------- | ---------------------------------------- |
| Owns            | Screens in the settings navigation    | Persisted values                         |
| Written to disk | No                                    | Yes, to `settings.json`                  |
| Reactive        | Via `useTitle` / `usePredicate` hooks | Via `useSettingsStore` and change events |

A settings screen almost always uses both: `settings` puts the screen in the list, and `storage` holds what the controls on it read and write. Build the screen itself out of [components](/modules/components), and see [creating an addon](/addons/creating) for where the registration goes in a plugin's lifecycle. Addon enabled-state is persisted by the [managers](/modules/managers), not by this module.
