Packaging Format
Plugin Packaging and Format
A Plugin Archive is a ZIP archive that contains an IDA plugin and its associated ida-plugin.json metadata file.
The metadata file should be found in the root directory of the plugin within the archive.
For example:
plugin.zip
├── ida-plugin.json
└── plugin.py
Or for a native plugin:
plugin.zip
├── ida-plugin.json
├── plugin.so
├── plugin.dylib
└── plugin.dll
ida-plugin.json
The ida-plugin.json file is the marker for an IDA Pro plugin.
A typical ida-plugin.json file might look like this:
{
"$schema": "https://hcli.docs.hex-rays.com/schemas/ida-plugin.json",
"IDAMetadataDescriptorVersion": 1,
"plugin": {
"name": "oplog",
"entryPoint": "oplog_entry.py",
"version": "0.1.2",
"idaVersions": ["9.1", "9.2"],
"description": "oplog is an IDA Pro plugin that records operations during analysis.",
"license": "Apache 2.0",
"categories": [
"ui-ux-and-visualization"
],
"requiresPython": ">=3.11",
"pythonDependencies": ["pydantic>=2"],
"urls": {
"repository": "https://github.com/williballenthin/idawilli"
},
"authors": [{
"name": "Willi Ballenthin",
"email": "wballenthin@hex-rays.com"
}],
"keywords": [
"activity-tracking",
"workflow-analysis",
"reverse-engineering-methodology",
"ai-training-data",
"analysis-visualization"
]
}
}
And a minimal ida-plugin.json could look like this:
{
"$schema": "https://hcli.docs.hex-rays.com/schemas/ida-plugin.json",
"IDAMetadataDescriptorVersion": 1,
"plugin": {
"name": "oplog",
"entryPoint": "oplog_entry.py",
"version": "0.1.2",
"urls": {
"repository": "https://github.com/williballenthin/idawilli"
},
"authors": [{
"name": "Willi Ballenthin",
"email": "wballenthin@hex-rays.com"
}]
}
}
In addition to the primary fields described initially on the Hex-Rays docs, HCLI compatibility added new, required fields in ida-plugin.json:
version: the version of the plugin archiveurls.repository: the repository that publishes the pluginauthors(ormaintainers): name and/or email. Social media handles are ok!
And there are new optional fields:
.plugin.requiresPythonis an optional PEP 440 version specifier, such as">=3.11". When HCLI selects a version to install, upgrade, or bundle, it skips versions whoserequiresPythonexcludes the target Python version, so an older compatible version can install instead. HCLI also checks the active IDA Python environment before installing and stops with guidance if it is incompatible. When HCLI installs from a plugin repository that is not a plugin bundle, it detects IDA's Python version only when a candidate version declaresrequiresPython..plugin.pythonDependenciesis a list of packages on PyPI that will be installed.plugin.keywordsis a list of terms to help users searching for plugins.plugin.platformsis recommended, defaults to all platforms. The possible values are:windows-x86_64,windows-aarch64,linux-x86_64,linux-aarch64,macos-x86_64, andmacos-aarch64..plugin.licensefor the code license of your project.plugin.settingsis a list of descriptors of settings.plugin.dependenciesdeclares companion plugins to install alongside this one; entries can be strings ("dep-a") or objects ({"plugin": "dep-a", "required": false}) to mark optional dependencies.plugin.componentsdeclares tightly-coupled sub-plugins bundled inside the same archive (e.g.,["helper-a", "helper-b"])
If there's a problem with the ida-plugin.json file, then the plugin is invalid and won't work with the repo.
Unfortunately even things like trailing commas will break strict JSON parsers like the one used by HCLI.
So, you can use hcli plugin lint /path/to/plugin[.zip] to check for problems and suggestions.
Editor validation with JSON Schema
HCLI publishes a JSON Schema for ida-plugin.json at
https://hcli.docs.hex-rays.com/schemas/ida-plugin.json.
Reference it from the top of your ida-plugin.json to get inline validation,
autocompletion, and hover documentation in editors like VSCode:
{
"$schema": "https://hcli.docs.hex-rays.com/schemas/ida-plugin.json",
"IDAMetadataDescriptorVersion": 1,
"plugin": { ... }
}
The schema is generated from the same Pydantic models HCLI uses to validate
plugins, so editor warnings match what hcli plugin lint will report.
Shared Settings
HCLI is aware of settings that plugins declare in ida-plugin.json and prompts users for their value
during installation. The answers, including answers equal to the default, are written into ida-config.json.
A setting that already has a value there is not prompted again when the plugin is reinstalled or upgraded;
use hcli plugin config <plugin> setup to change it. The settings can be queried at plugin runtime
using the ida-settings (v3) Python package:
import ida_settings
api_key = ida_settings.get_current_plugin_setting("openai_key")
Plugin authors are encouraged to adopt this configuration system, as it provides a centralized way to manage edits from both the CLI and (eventually) the GUI, users don't have to manually edit source code/config files, and the data can be easily exported/imported.
Coming Soon
The associated IDA plugin ida-settings-editor that lets users configure plugin settings within IDA is not yet available. We plan to introduce it in a future release.
Setting Fields
Each setting in the settings array supports the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
key |
string | yes | Unique code-level identifier (e.g., "api_key") |
type |
string | yes | Either "string" or "boolean" |
required |
boolean | yes | Whether the setting must be provided |
default |
string/boolean | no | Default value when not configured |
name |
string | yes | Human-readable name (e.g., "OpenAI API key") |
documentation |
string | no | Human-readable explanation |
validation_pattern |
string | no | Regex pattern for string validation |
choices |
array | no | List of acceptable string values |
prompt |
boolean | no | Whether to prompt during installation (default: true) |
Setting "prompt": false is useful for advanced or niche settings that have sensible defaults and shouldn't clutter the installation experience. These settings can still be configured later via hcli plugin config.
Example settings configuration:
{
"settings": [
{
"key": "api_key",
"type": "string",
"required": true,
"name": "API Key",
"documentation": "Your API key from https://example.com/keys"
},
{
"key": "cache_size",
"type": "string",
"required": false,
"default": "100",
"name": "Cache Size",
"documentation": "Maximum number of cached items",
"prompt": false
}
]
}
Plugin Dependencies
A plugin can declare other plugins as loose dependencies via the dependencies field. Each entry is either a string (a plugin reference: bare name, name with version pin, or name with host URL) or an object with a plugin field and an optional required flag. String entries are required by default.
{
"plugin": {
"dependencies": [
"go-runtime-detector",
"go-string-extractor==1.2.0",
"helper@https://github.com/org/repo",
{"plugin": "nice-to-have", "required": false}
]
}
}
When a user installs or upgrades a plugin that declares dependencies, HCLI resolves each one across all configured repositories and installs it as an independent top-level plugin. The dependencies of the plugin's components count too. Dependencies are resolved recursively: if a dependency itself declares further dependencies, those are installed too. HCLI selects the newest version of each plugin that the current installation supports and whose required dependencies can also be resolved, so it can select an older version of the plugin or of a dependency. Dependencies that are already installed and satisfy the spec are skipped. If a pinned dependency is installed at a lower version, it is upgraded automatically; a higher installed version is not downgraded. A dependency on the name of an installed plugin resolves only from the host of the installed plugin.
HCLI resolves all dependencies before it writes any file. When a required dependency cannot be resolved, the install or upgrade fails and nothing changes. When an optional dependency ("required": false) cannot be resolved, or one of its own required dependencies cannot be resolved, HCLI logs the reason and skips it. HCLI then installs the plugin, and after it the dependencies, each one before the dependencies that need it. When a required dependency fails to install during a fresh install, HCLI stops and rolls back the parent plugin. During an upgrade the plugin files are already at the new version and cannot be cheaply reversed, so dependency failures are reported but do not roll back the upgrade. When an optional dependency fails to install, HCLI logs the failure and continues with the remaining dependencies.
If a bare dependency name matches plugins from different repository hosts, installation fails with an ambiguity error. Use the name@host format (e.g. helper@https://github.com/org/repo) to pin a dependency to its source repository. For non-community plugins, hcli plugin lint recommends using name@host for all dependency entries to guard against name collisions.
Dependencies are not bundled inside the declaring plugin's archive. Each dependency is its own plugin with its own ida-plugin.json and its own archive in the repository.
Plugin Suites (Components)
When several plugins are tightly coupled and must share a single lifecycle, a plugin can declare them as components via the components field. Unlike loose dependencies, components are bundled inside the same archive as the declaring plugin (the "suite root"). They are always installed and removed with the suite.
{
"plugin": {
"name": "go-analysis-suite",
"version": "2.0.0",
"entryPoint": "go_suite.py",
"components": ["go-runtime-detector", "go-string-extractor"],
"urls": { "repository": "https://github.com/example/go-suite" },
"authors": [{ "name": "Jane Doe" }]
}
}
Each component is a subdirectory that contains its own ida-plugin.json. The directory name must match the component's plugin.name. Components can themselves declare further components, up to a nesting depth of 10.
go-analysis-suite.zip
└── go-analysis-suite/
├── ida-plugin.json (name: go-analysis-suite, components: [...])
├── go_suite.py
├── go-runtime-detector/
│ ├── ida-plugin.json (name: go-runtime-detector)
│ └── detector.py
└── go-string-extractor/
├── ida-plugin.json (name: go-string-extractor)
└── extractor.py
HCLI installs the entire suite directory into $IDAUSR/plugins/<suite-name>/. Component subdirectories are extracted alongside the root plugin's files. Because hcli plugin status only scans one level deep, components are hidden from the top-level listing unless --show-components is passed.
Component names must not collide with any installed top-level plugin or with components of other installed suites. HCLI checks for collisions before installing. Uninstalling a component individually is not allowed; uninstall the suite root instead.
Version pins (==) and host qualifiers (@) are not valid in components entries since the components are always bundled in the same archive.
Components can declare their own pythonDependencies. When a suite is installed, HCLI collects dependencies from the root and every component at every nesting depth, then installs them together. The hcli plugin dependencies install command also covers component dependencies.
Components can declare their own settings. During install, HCLI prompts for (or accepts via --config) settings declared by any component. To pass a setting on the command line, prefix the key with the component or dependency name: --config component-name.key=value or --config dependency-name.key=value. Keys without a prefix target the root plugin.
Component settings are stored in ida-config.json keyed by the component's own name. At runtime inside IDA, get_current_plugin() returns the component name when called from component code, so get_current_plugin_setting() reads the correct section. After install, hcli plugin config <component-name> manages component settings.
Source Archives and Binary Archives
For many pure-Python IDA plugins, source archives are often sufficient. This means you don't have to create any GitHub Actions workflows; you just have to tag your releases in GitHub.
If you distribute your pure-Python IDA plugin via PyPI, like IPyIDA does, then the plugin directory in your source archive becomes very simple. You can put the following content in a subdirectory of the project (preferred), or publish a separate repository for the metadata:
.
├── ida-plugin.json
└── entry_stub.py
With ida-plugin.json:
{
"$schema": "https://hcli.docs.hex-rays.com/schemas/ida-plugin.json",
"IDAMetadataDescriptorVersion": 1,
"plugin": {
"name": "plugin1",
"entryPoint": "entry_stub.py",
"version": "1.0",
"idaVersions": ["9.0", "9.1", "9.2"],
"pythonDependencies": ["ida-plugin1==1.0"],
"urls": {
"repository": "https://github.com/foo/bar"
},
"authors": [{
"email": "user@example.com"
}]
}
}
And the entry stub:
from ida_plugin1.ida_plugin import PLUGIN_ENTRY
"Fat" Binary Plugin Archives
Plugin archives can contain multiple compiled versions of a plugin, e.g., foo-plugin.so and foo-plugin.dylib.
In this case, the entry point must specify the bare path to the plugin, e.g., foo-plugin, and IDA will append the appropriate extension based on the platform.
You don't have to support all platforms. It's acceptable to publish multiple "thin" archives, one for each platform; however, "fat" archives are convenient.
The file extensions must be exactly:
.dll- Windows x86-64.so- Linux 86-64_x86_64.dylib- macOS x86_64 (not yet supported by IDA)_aarch64.dylib- macOS aarch64 (not yet supported by IDA).dylib- macOS Universal Binary, only if Intel/ARM dylibs aren't present. (note: you must use this for macOS currently)
Remember, plugin names must be unique within a plugin archive, which means:
Allowed:
root/
plugin/
ida-plugin.json (name: foo-plugin)
foo-plugin.so
foo-plugin.dylib
foo-plugin.dll
Disallowed:
root/
plugin-linux/
ida-plugin.json (name: foo-plugin)
foo-plugin.so
plugin-windows/
ida-plugin.json (name: foo-plugin) <-- name collides!
foo-plugin.dll
(uncommon) Multi-Plugin Archives
Because the ida-plugin.json file marks the root of a plugin within the archive, an archive can contain multiple plugins:
plugins.zip
├── plugin1
│ ├── plugin1.py
│ └── ida-plugin.json
└── plugin2
├── plugin2.py
└── ida-plugin.json
If one of the plugins declares the others as components, HCLI treats the archive as a plugin suite and installs from the root manifest (see Plugin Suites). If none of the manifests reference each other, the archive contains unrelated plugins, which is unusual but supported.
Plugin archives can also be collected into a plugin bundle for offline installation on air-gapped machines.