Skip to content

plugin.json reference

Every Python plugin declares its identity, entry point, and menu-bar placement in a plugin.json file next to its main.py. This is the same schema the C++ side uses; only the entry and module fields are Python-specific.

Example

{
  "id":          "org.example.wordcount",
  "name":        "Word Count",
  "version":     "1.0.0",
  "vendor":      "Example, Inc.",
  "description": "Counts words and characters in the active document.",
  "apiVersion":  1,
  "entry":       "python",
  "module":      "main",
  "pyRequires":  [],
  "order":       900,
  "menus": [
    { "title": "Plugins", "barPriority": 570, "itemPriority": 100 }
  ]
}

Fields

Required

Field Type Notes
id string Reverse-DNS unique identifier. Used everywhere — enable / disable, log lines, per-plugin settings. Must not change across versions.
apiVersion integer Must equal the host's MTE_PLUGIN_API_VERSION. Currently 1. A mismatch surfaces in the Plugins page and the plugin is listed but not loaded.
entry string Must be "python" to route through the Python backend. "native" or omitted routes through the Qt loader.
module string Name of the Python module that contains register(ctx). If "main", the worker looks for main.py inside the plugin folder. Dotted package names ("src.main") are a future extension — Phase 1 supports flat names only.
Field Type Notes
name string Human-readable display name shown in the Plugins page.
version string Semver; free-form otherwise.
vendor string Author / organization. Shown in the Plugins page.
description string One or two lines. Shown in the Plugins page.

Optional

Field Type Notes
order integer Load order across plugins; lower loads earlier. Defaults to 1000. Ties broken by id.
menus array of objects See below.
pyRequires array of strings Pip requirement strings (e.g. ["requests>=2.31"]). On the plugin's first launch the editor provisions a per-plugin virtual environment, pip-installs these into it, and runs the plugin's worker under it (see Packaging). Non-string and empty entries are skipped.
permissions array of strings Coarse capability declarations (e.g. ["network"]), shown read-only in the Permissions column of Preferences → Plugins so users can see what a plugin says it does. Declaration only — not enforced yet.
thirdParty array of objects Third-party components the plugin bundles, for attribution in the About dialog. Each entry: name (required), license, url, file. Ship the licence text named by file alongside the plugin under Plugins/Licenses/<id>/. See below.

Each entry declares one top-level menu the plugin contributes to and sets its position. Repeat one object per top-level menu you touch (Tools, Plugins, Help, etc.).

"menus": [
  { "title": "Plugins", "barPriority": 570, "itemPriority": 100 }
]
Field Type Notes
title string, required The top-level menu label the item lives under (Plugins, Tools, …). Must match the leftmost segment of the paths you pass to ctx.menus.add_item().
barPriority integer, optional Left-to-right rank in the menu bar. Lower = further left. Core menus have fixed priorities spaced 100 apart (File 100, Edit 200, …, Window 600); 570 puts your submenu near the Plugins position. Repeated across plugins that target the same top-level menu; last-wins is fine because it's idempotent.
itemPriority integer, optional Vertical rank of your plugin's whole block inside the leaf menu. Defaults to 1000. Lower = higher up.

You do NOT list individual items here — those are added by ctx.menus.add_item() at runtime. menus is only about menu-bar placement.

thirdParty

Declare any third-party component your plugin bundles so the About dialog can attribute it. Each entry:

"thirdParty": [
  { "name": "SomeLib", "license": "MIT",
    "url": "https://example.com/", "file": "SomeLib-LICENSE.txt" }
]
Field Type Notes
name string, required The component. Entries without a non-empty name are dropped.
license string, optional SPDX-ish label, e.g. MIT, LGPL-3.0.
url string, optional Project/home page.
file string, optional Basename of the licence text you ship, resolved at Plugins/Licenses/<your-plugin-id>/<file>.

You own the licence texts: ship them with your plugin package under Plugins/Licenses/<id>/. The editor lists the components and points users at that folder; it does not keep third-party texts for your plugin in its own Licenses/ folder.

Static probe

At discovery time — and again when a .mteplugin package is installed — the host reads module, opens <pluginDir>/<module>.py, and confirms it contains a top-level def register(...) — no code runs. The installer rejects a failing package with an error dialog; discovery silently skips the plugin with an error line in the log:

PythonPluginBackend: skipping 'org.example.wordcount': module
'.../main.py' does not declare a top-level `def register(...)`.

Keep register at column 0 of a line beginning with def register(.

Loading order and menu-bar placement

If your plugin contributes to Plugins with barPriority: 570, the menu appears at rank 570 in the menu bar. Two plugins with different barPriority values for the same title race: whichever loaded last wins, but since both usually declare the same rank for a given menu it does not matter. See PLUGIN_ARCHITECTURE.md §13 for the full ordering model.

What does NOT belong here

The host parses the fields documented above and ignores everything else — don't invent fields; an unknown key is silently dead weight.