Appearance
Manifest Reference
Every extension has a typora-extension.json file at the top of its folder. Typora validates it when the extension is installed and every time it loads.
Validation is forgiving. If one contribution is malformed, Typora drops that contribution, keeps the rest, and lists the problem in the extension's row in Settings ▸ Extensions. Only a missing or invalid id, name or version disables the whole extension.
Example
json
{
"id": "someone.plantuml",
"name": "PlantUML",
"version": "1.2.0",
"engines": { "typora": ">=2.0.0" },
"description": "PlantUML diagrams drawn by a Kroki server.",
"author": "Someone",
"repository": "https://github.com/someone/typora-plantuml",
"permissions": {
"network": ["https://kroki.io"]
},
"contributes": {
"fenceRenderers": [{ "lang": ["plantuml", "puml"], "kroki": "plantuml" }],
"configuration": {
"properties": {
"server": { "type": "string", "default": "https://kroki.io", "title": "Server" }
}
}
}
}Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Unique, stable id shaped like author.name: at most 64 characters, lowercase letters, digits and hyphens, at least two dot-separated parts (^[a-z0-9][a-z0-9-]*(\.[a-z0-9][a-z0-9-]*)+$). The install folder must have the same name. |
name | string | yes | Display name. Also used as the group name for the extension's items in menus and palettes. |
version | string | yes | Semantic version: 1.2.3, optionally with a -pre or +build suffix. |
description | string | One sentence shown in Settings and in the store. | |
author | string | Shown in Settings and in the store. | |
repository | string | URL of the source repository. | |
engines | object | { "typora": ">=2.0.0" } — the Typora versions the extension targets. | |
os | string[] | The platforms it works on: darwin (macOS), win32 (Windows), linux. Leave it out for all. Elsewhere Typora lists the extension as Only on macOS (etc.) and doesn't run it; the store marks it the same way. | |
l10n | string | Folder of <locale>.json message catalogs, read by typora.l10n.t(). | |
keywords | string[] | Search terms for the store. | |
tags | string[] | Store filters, from: Language Pack, Markdown Extension, Diagram, Math, Theme, Writing, Template, AI, Media, Image Uploader, Export, Publish, Data Sync, Productivity, File Viewer, View, Developer. | |
unlisted | boolean | Optional; defaults to false. Set to true to hide the extension from store homepage cards and website/app search while keeping its direct page URL and installation available. See Unlisted extensions. | |
permissions | object | What the extension may access. See below and Permissions and Security. | |
binaries | object | Programs in the package, one file per platform and CPU. See below. | |
contributes | object | What the extension adds. See below. | |
main | string | Path to an ES module (.js or .mjs) run in a Worker, once per window. See Worker API. | |
background | string | Path to an ES module run in a Worker once for the whole app. See Worker API. | |
activationEvents | string[] | When to start main. Usually derived — see Activation. |
All paths in the manifest (main, entry, folders) are relative to the extension folder, use /, and may not contain .. or start with / or a URL scheme.
permissions
| Key | Value | Grants |
|---|---|---|
network | array of origins | fetch from pages and scripts, and Kroki rendering, to these origins. Each origin is https://host or http://host, optionally with a port, and may start with *. for subdomains: "https://*.example.com". No paths. An entry "{config.<key>}" stands for the server the user enters in that setting. |
document | "read" or "write" | Read the active document; "write" also allows editing it. |
workspace | "read" | List and read files inside the folder open in the window. |
clipboard | true | Read and write clipboard text. |
ai | true | Send text to the model the user set up in Settings ▸ Intelligence (typora.ai). |
process | "*" or array of command names | Run programs as the user (typora.process, view.process): "*" — anything, including the user's shell; a list — these commands, by name on the user's login-shell PATH or at a full path to a program with that name. Any form also allows the package's binaries. |
Any permission makes Typora ask the user before the extension runs.
binaries
Programs you ship, one build per <os>-<arch>:
json
"permissions": { "process": [] },
"binaries": {
"helper": {
"darwin-arm64": "bin/darwin-arm64/helper",
"darwin-x64": "bin/darwin-x64/helper",
"linux-x64": "bin/linux-x64/helper",
"linux-arm64": "bin/linux-arm64/helper",
"win32-x64": "bin/win32-x64/helper.exe",
"win32-arm64": "bin/win32-arm64/helper.exe"
}
}Targets: darwin-arm64, darwin-x64, darwin-universal (a fat binary), linux-arm64, linux-x64, win32-arm64, win32-x64. Start one with typora.process.spawn({ binary: "helper" }, args); Typora picks the build for the machine's CPU, falling back where the OS translates: an Apple-silicon Mac tries darwin-arm64, darwin-universal, then darwin-x64 (Rosetta); Windows on Arm tries win32-arm64, then win32-x64. Without a build for the machine, spawn rejects with "helper has no build for linux-arm64."
You don't need to keep the executable bit (zips drop it; Typora sets it before running), but macOS binaries for Apple silicon must be signed — ad hoc is enough (codesign -s - helper; Apple's linker signs ad hoc by default).
binaries needs permissions.process. See Permissions and Security.
contributes
| Key | Type | Tier | Page |
|---|---|---|---|
themes | folder paths | L0 | Writing Themes |
templates | folder path | L0 | Templates, snippets, formats, keys |
snippets | folder path | L0 | Templates, snippets, formats, keys |
customFormats | array | L0 | Templates, snippets, formats, keys |
keyBindings | array | L0 | Templates, snippets, formats, keys |
texMacros | object | L0 | TeX Macros |
mathjax | object | MathJax components | MathJax Fonts and TeX Packages |
localizations | array | L0 | Language Packs |
dictionaries | array | L0 | Language Packs |
inputRules | array | L0 | Input Rules |
inlineRules | array | L0 | Inline Rules |
fenceRenderers | array | L0 (Kroki) / L1 / L2 | Fence Renderers |
detailViews | array | L0 / L1 / L2 | Detail Views |
fileViewers | array | L1 / L2 | File Viewers |
commands | array | L2 | Commands and Menus |
imageUploaders | array | L2 | Image Uploaders |
menus | object | L2 | Commands and Menus |
configuration | object | L0 | Configuration |
Local ids inside contributes (commands[].id, detailViews[].id, fileViewers[].id, inlineRules[].id, fenceRenderers[].id, imageUploaders[].id) are scoped to your extension. They may contain letters, digits, ., _ and -, up to 64 characters.
Not yet available
grammars is reserved for upcoming syntax-highlighting contributions. Typora currently ignores it.
Activation
main is not started until it is needed. Typora derives the activation events from contributes when activationEvents is omitted:
| Event | Fired when |
|---|---|
onCommand:<id> | One of your commands is run. |
onFileViewer:<id> | A file opens in one of your fileViewers that has no entry. |
onDetailView:<id> | One of your detailViews with "provider": "main" is shown. |
onFence:<id> | A fence for one of your fenceRenderers with "resolver": true is rendered. |
onImageUploader:<id> | One of your imageUploaders uploads images. |
onStartupFinished | The window has finished loading. |
If main has nothing to wait for — for example it only registers formatters or watches the document — it starts with onStartupFinished. You can also list "activationEvents": ["onStartupFinished"] explicitly, as the Writing Stats sample does, to run at startup regardless. Prefer lazy events: every window pays for an eagerly started Worker.
background is not driven by activation events: while the extension is enabled, its background Worker runs in Typora's background host for as long as the app runs.
Checks across fields
Typora also reports these combinations:
- A fence renderer with
"kroki"needs anetworkpermission. - A
"{config.<key>}"network entry needs a string setting<key>whosedefaultis empty or one of the listed origins. - A fence renderer with
"resolver": true, or a detail view with"provider": "main", needsmain. - A file viewer needs an
entryor amainthat resolves it.