Skip to content

Publishing to the Store ​

The Typora extension store at extensions.typora.io is built from one public repository, github.com/typora/typora-extensions. Extensions are never uploaded as binaries:

  • Every extension lives in the repository as source.
  • Every new extension and every update is a pull request.
  • Typora's CI validates, packages and publishes exactly what was reviewed.

Users find and install store extensions from Settings ▸ Extensions, which reads the store's catalog.

Submitting an extension ​

  1. Fork typora/typora-extensions and add your extension as extensions/<your.id>/. The folder name must equal the manifest id (for example jane.plantuml). The id is permanent.

  2. Fill in name, version, a one-sentence description, author, tags (the store's filters: Language Pack, Markdown Extension, Diagram, Math, Theme, Writing, Template, AI, Media, Image Uploader, Export, Publish, Data Sync, Productivity, File Viewer, View, Developer) and optionally keywords (store search terms). A README.md in the folder is shown on the extension's store page. Two optional images show Typora with the extension at work: cover.webp, 800 × 400 (2:1), on the store card — crop it from the screenshot without the window title bar — and screenshot.webp, 1760 × 1100 (16:10: an 880 × 550 window at 2x, no scrollbars), on the extension's page. Use WebP (lossless for screenshots, so text stays sharp) and keep each under 300 KB; PNG and JPEG also work, up to 1 MB. They are not packaged, and changing only them needs no version bump.

  3. Validate locally (Node 22, no dependencies):

    sh
    npm run validate              # every extension, or: node scripts/validate.mjs jane.plantuml
    npm run build                 # build the catalog and zips into dist/
  4. Open a pull request. Explain each permission you ask for in the description.

  5. After review and merge, the deploy workflow publishes the new version within a few minutes.

Every change to an extension's folder needs a version bump. Published packages are immutable and cached forever; CI fails a pull request that changes files without bumping version.

Unlisted extensions ​

For an extension still in development, add these top-level fields to typora-extension.json (the example is for a sync provider):

json
"tags": ["Data Sync"],
"unlisted": true

unlisted is an optional boolean. When it is true:

  • The extension has no homepage card and does not appear in website search, tag-filter results, or the app's extension browsing/search results.
  • Its page at https://extensions.typora.io/<id>/ remains accessible. Share that URL so testers can use Install or download the package.
  • The page is omitted from the sitemap and includes a noindex robots meta tag to ask search engines not to index it.
  • index.json retains the entry with unlisted: true so direct install links can resolve its package and checksum.

This controls discovery, not access: the source, catalog entry, page, and package remain public. It does not disable an already installed extension or bypass permission prompts. Search engines may take time to remove a previously indexed page.

Omit unlisted or set it to false when ready for public discovery. Changes to this manifest field follow the same version-bump and publishing process as other manifest changes. The Data Sync tag is independent of visibility; use it for sync providers whether listed or unlisted.

Automated checks ​

Every pull request runs:

  • Manifest checks: required fields, id pattern and folder name, permission shapes, and that every referenced file (main, background, entry pages, template and snippet folders) exists.
  • File checks: no symbolic links, at most 1 MB per file and 5 MB per extension. Dotfiles are not packaged. Language packs and MathJax fonts may be larger: 32 MB per dictionary file, 8 MB per font file, 128 MB per extension; font folders are not flagged as minified.
  • Code checks: flags eval(, new Function(, remote import() / importScripts() / <script src>, and very long (minified) lines.
  • Permission diff against main: permissions added or removed, network origins, tier change, and the review depth below, printed in the job summary.

Review depth follows permissions ​

The extensionReview
L0 (data only), no networkAutomated checks plus a light review
Declares network, document, workspace or clipboardHuman review of the code paths that use them
An update that widens permissionsHuman review; users are asked to consent again on update

Rules ​

  • No remote code. Everything that runs ships in the package. Don't load scripts, styles or WebAssembly from the network, and don't eval anything fetched or read from a document.
  • No minified or obfuscated sources. Commit readable source. If you use a build step, commit its output in readable form together with the source, and say so in the pull request.
  • Ask for the least. List network origins exactly (https://kroki.io, not https://*).
  • Widening permissions is an explicit change. Adding an origin, going from document: read to write, or adding workspace / clipboard must be called out in the pull request.
  • Documents are data. Treat fence bodies, front matter and file contents as untrusted input.
  • Keep the package small: no node_modules, build caches or test fixtures users don't need.

Reviewers may decline extensions that duplicate built-in features, collect data without saying so, or are unmaintained. A published version that turns out to be harmful is revoked and disabled in the app.

Testing against a local store ​

npm run dev in the repository builds the catalog for http://localhost:8788 and serves it. Start Typora with the environment variable TYPORA_EXTENSIONS_STORE=http://localhost:8788 to browse and install from it.

What gets published ​

For each extension, CI produces a deterministic package — a zip named download/<id>-<version>.typora-extension (the same source always gives the same SHA-256) and an entry in the catalog (index.json) with the extension's metadata, permissions, tier (L0, L1 or L2) and the download's hash. Typora checks the hash before installing.

Theme adapted from Nimbus (MIT).