# Extension settings

> Give an extension settings that OrbTorrent draws, such as an API key kept in the Keychain, read them with getPreferenceValues, and check them with verify.

An extension's settings are declared in `package.json`, and OrbTorrent draws them on the extension's page. You don't build any interface.

```json title="package.json"
{
  "preferences": [
    {
      "name": "apiKey",
      "type": "password",
      "title": "API key",
      "description": "Your free VirusTotal API key.",
      "placeholder": "64 letters and digits",
      "required": true
    },
    {
      "name": "scope",
      "type": "dropdown",
      "title": "Files to check",
      "default": "risky",
      "data": [
        { "title": "Programs, archives and documents", "value": "risky" },
        { "title": "All files", "value": "all" }
      ]
    },
    {
      "name": "uploadUnknown",
      "type": "checkbox",
      "title": "Files VirusTotal hasn't seen",
      "label": "Upload them to be scanned (up to 32 MB each)",
      "default": false
    }
  ]
}
```

## Types

| Type | Value | Draws |
|---|---|---|
| `textfield` | Text | A text field. |
| `password` | Text | A secure field, with **Save** and **Remove**. The value is kept in the macOS Keychain, never in a file, and hidden in logs. It can't have a default. |
| `checkbox` | `true` or `false` | A checkbox, with `label` beside it. |
| `dropdown` | One of `data`'s values | A menu of `data`'s titles. |

Every setting has a `name` (letters, digits and underscores, starting with a letter), a `type` and a `title`, and can have a `description`, a `placeholder`, a `default`, and `required`. [Manifest](https://orbtorrent.com/docs/extensions/reference/manifest#preferences) has the rules.

## Read them

```ts title="src/scan.ts"
import { Download, Result, getPreferenceValues } from "@orbtorrent/api";

export default async function scan(download: Download): Promise<Result> {
  const { apiKey, scope, uploadUnknown } = getPreferenceValues();
  // …
}
```

**Every setting has a value** when a hook runs: what the user chose, or its `default`, or else `""`, `false`, or a dropdown's first option. You never need to check for `undefined`.

**They're typed.** `orb build` writes `orb-env.d.ts` from `package.json`, so `getPreferenceValues()` knows each setting's name and type, and a dropdown's values:

```ts title="orb-env.d.ts"
// Generated by `orb build` from package.json. Don't edit: change package.json instead.

declare global {
  interface OrbPreferences {
    /** Your free VirusTotal API key. */
    apiKey: string;
    scope: "risky" | "all";
    uploadUnknown: boolean;
  }
}

export {};
```

Leave `orb-env.d.ts` out of version control: the template's `.gitignore` already does.

## Required settings

While a `required` setting is empty, the extension shows **Needs setup** on the Extensions page, and its hooks are **skipped** rather than run. Its page says which settings to add. Use `required` for anything the extension can't work without, such as an API key.

## Help the user set it up

Put the steps to set your extension up, such as how to get an API key, in `help.md` next to `package.json`. OrbTorrent shows it beside the settings, under **How to Set It Up**.

```md title="help.md"
### Get a free API key

1. Sign up or sign in at [virustotal.com](https://www.virustotal.com).
2. Open your profile menu and choose **API key**.
3. Copy the key into **API key** here and press **Verify**.
```

## Check them with verify

To check settings when the user presses **Verify**, such as that an API key works, name a file in `verify`:

```json title="package.json"
{ "verify": "verify" }
```

Its default export gets the settings, and says whether they're right:

```ts title="src/verify.ts"
import { VerifyResult, fetch } from "@orbtorrent/api";

/** The EICAR test file, which every antivirus engine knows. */
const knownFile = "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f";

export default async function verify(preferences: OrbPreferences): Promise<VerifyResult> {
  const key = preferences.apiKey.trim();
  if (!key) return { ok: false, message: "Enter your API key." };
  const response = await fetch(`https://www.virustotal.com/api/v3/files/${knownFile}`, {
    headers: { "x-apikey": key, accept: "application/json" },
  });
  switch (response.status) {
    case 200:
      return { ok: true, message: "The key works." };
    case 401:
    case 403:
      return { ok: false, message: "VirusTotal didn't accept this key. Copy it again from your VirusTotal profile." };
    default:
      return { ok: false, message: `VirusTotal answered with status ${response.status}. Try again in a moment.` };
  }
}
```

This is shortened from VirusTotal Scan's own, which looks up a file every antivirus engine knows.

- Return `{ ok: true }`, with an optional `message`, or `{ ok: false, message }`. The message shows under the settings.
- `verify` has the same permissions as the hooks, and the same limits as a `beforeStart` hook: 15 seconds, of which 10 of CPU.
- [Test it](https://orbtorrent.com/docs/extensions/testing#test-verify) with `"verify": true` in a test file.

## When settings change

Settings are read fresh for every run, so a hook always sees the latest. Removing an extension deletes its settings, including passwords in the Keychain.
