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. |
Recommended¶
| 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. |
menus¶
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.).
| 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.