Skip to content

Build extensions / Concepts

Extension settings

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

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
}
]
}
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 has the rules.

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:

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.

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.

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.

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**.

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

package.json
{ "verify": "verify" }

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

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 with "verify": true in a test file.

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.