Skip to content

Configuration ​

configuration declares settings for your extension. Typora draws a form for them: the options button next to the extension's switch in Settings ▸ Extensions opens it. The user edits the form and saves it, and Typora stores the values in the user's settings.toml. Extensions can read their own settings but never write them — only the user changes them.

json
"contributes": {
  "configuration": {
    "title": "Writing Stats",
    "properties": {
      "dailyGoal": {
        "type": "number",
        "default": 500,
        "title": "Daily goal",
        "description": "Words per day."
      },
      "theme": {
        "type": "string",
        "enum": ["default", "dark"],
        "default": "default",
        "title": "Theme"
      },
      "showStreak": { "type": "boolean", "default": true, "title": "Show streak" }
    }
  }
}

Properties ​

Each key of properties is a setting name (a letter followed by letters, digits, _, . or -, up to 64 characters).

FieldDescription
type"string", "number" or "boolean". Required.
defaultDefault value, of the same type.
enumAllowed values; shown as a drop-down.
enumLabelsWhat the drop-down shows for each enum value, in the same order. Defaults to the values.
titleLabel in the form. Defaults to the key.
descriptionHelp text under the label.
placeholderHint shown in an empty text field.
format"file": a path, with a Choose… button that opens the system file picker (desktop only). "secret": a masked field that is never saved; see Secrets.
fileExtensionsWith "format": "file": the file types the picker offers, e.g. ["jar"].
minimum, maximumNumber settings: the allowed range. Values outside it are clamped when the form is saved.
whenShow the setting only while other settings have these values: { "backend": "java" }, or a list for any of several, { "backend": ["java", "command"] }. Hidden settings keep their values.

Booleans are shown as switches, enum properties as drop-downs, everything else as a text field. Settings appear in the order of properties. Restore Defaults in the form puts every field back to its default; values equal to the default aren't stored, so they follow the default if a later version changes it.

Secrets ​

A password belongs neither in settings nor in storage. Give the field "format": "secret" and name a command main registers as secretsCommand. It needn't appear in contributes.commands:

json
"configuration": {
  "secretsCommand": "signInWithPassword",
  "properties": {
    "username": { "type": "string", "default": "", "title": "User name" },
    "password": { "type": "string", "format": "secret", "default": "", "title": "Password" }
  }
}
js
typora.commands.register("signInWithPassword", async ({ password }) => {
  const token = await logIn(typora.settings.get("username"), password);
  await typora.secrets.set("token", token);
});

Save stores the other fields, then calls the command with the secrets the user typed, in a short-lived Worker. An empty field isn't passed, and if none was typed the command doesn't run. Whatever the command throws appears in the form, which stays open. The field is empty the next time the form opens.

Typora adds a line to the form saying it never writes the field to its settings, hands it to the extension only on Save, and where typora.secrets keeps credentials on this system. On Linux without a keyring, Save first asks whether to install one or go on without encryption, and the form then shows a warning.

What happens to the value in between is up to you, so use description to say so plainly: where it is sent, what you keep and how the user removes it. Keep it with typora.secrets, and leave the wording about how it is protected to Typora, since that depends on the system.

Modes ​

A setting that picks a mode, with the fields each mode needs:

json
"properties": {
  "backend": {
    "type": "string",
    "enum": ["command", "java", "kroki"],
    "enumLabels": ["plantuml command", "Java and plantuml.jar", "Kroki server"],
    "default": "command",
    "title": "Draw with"
  },
  "command": { "type": "string", "default": "plantuml", "title": "Command", "format": "file", "when": { "backend": "command" } },
  "jar": {
    "type": "string",
    "title": "plantuml.jar",
    "format": "file",
    "fileExtensions": ["jar"],
    "placeholder": "/path/to/plantuml.jar",
    "when": { "backend": "java" }
  },
  "server": { "type": "string", "default": "https://kroki.io", "title": "Kroki server", "when": { "backend": "kroki" } }
}

Reading settings ​

FromHow
Manifest (L0){config.key} in an inline rule's openUrl. Kroki renderers read the server setting.
A page (L1)await typora.view.settings() returns all values. Fences drawn by your renderers are drawn again after the user saves new settings.
main / background (L2)typora.settings.get("key"), or typora.settings.get() for all; typora.settings.onDidChange(values => …) fires when the user changes them.

Values are stored under [extensions.settings."<id>"] in ~/.config/Typora/settings.toml.

A server the user chooses ​

An extension that talks to the user's own server (a WordPress site, a self-hosted Kroki) can't list it in permissions.network ahead of time. Name the setting there instead:

json
"permissions": { "network": ["{config.siteUrl}"] },
"contributes": {
  "configuration": {
    "properties": {
      "siteUrl": { "type": "string", "default": "", "title": "Site URL", "placeholder": "https://example.com" }
    }
  }
}

Requests may then go to the origin of what the user entered (https://example.com/blog allows https://example.com; example.com means https://example.com). The default must be empty or an origin you also list, so only the user can point the extension at a new server. See Permissions.

Theme adapted from Nimbus (MIT).