Plugin components
A plugin is a folder. What it contributes depends on which files you put in it. This page goes through every kind of component, where it lives, how it is named once loaded, and the variables it can use.
The plugin folder at a glance
my-plugin/
.syntic-plugin/plugin.json manifest (a root plugin.json also works)
skills/<skill>/SKILL.md skills (or one SKILL.md at the root)
commands/*.md slash commands
agents/*.md helper agents
hooks/hooks.json hooks
.mcp.json MCP servers
.lsp.json language servers
output-styles/*.md output styles
bin/ executables, first on PATH for the plugin's processes
settings.json default settingsEach component is read from its default place unless the manifest names other paths for it. A path you name replaces the default for that component; it does not add to it. See Component paths.
Skills
A skill is a folder holding a SKILL.md. Put several under skills/, or make the plugin itself a single skill by placing SKILL.md at its root.
skills/
draft-changelog/SKILL.md
bump-version/SKILL.mdOnly sub-folders that contain a SKILL.md count, so support folders such as skills/templates/ are left alone. Skills from plugins keep their own names. When a user or project skill has the same name, that one wins.
Skills from plugins are listed for the model on every turn, which costs tokens; see Measure a plugin. The frontmatter keys (description, when_to_use, allowed-tools, model, effort, context, disable-model-invocation and the rest) are the same as for any skill and are covered on the skills page.
A skill can also start one of the plugin’s monitors the first time it runs.
Commands
Each Markdown file under commands/ becomes a slash command. Once the plugin is installed, its commands are named with the plugin first:
| File | Command |
|---|---|
commands/ship.md | /release-helper:ship |
commands/db/migrate.md | /release-helper:db:migrate |
commands/audit/SKILL.md | /release-helper:audit (the folder is one command; other files in it are support material) |
The prefix means a plugin command never replaces one of your own. If you do define a command with exactly the same full name, yours wins.
Command files use the same frontmatter as your own custom commands: description, argument-hint, allowed-tools, model, effort, context: fork and so on. In the body you can use:
| Placeholder | Becomes |
|---|---|
${SYNTIC_PLUGIN_ROOT} | The plugin’s folder. |
${SYNTIC_PLUGIN_DATA} | The plugin’s data folder. |
${SYNTIC_SKILL_DIR} | For a SKILL.md command, its folder. |
${SYNTIC_SESSION_ID} | The current session id. |
${user_config.KEY} | An option value (never a sensitive one). |
Inline shell steps (!`command`) run with the plugin’s variables exported and its bin/ on PATH.
Agents
Each Markdown file under agents/ defines a helper agent, using the same format as custom subagents. It is offered as <plugin>:<name>, for example release-helper:changelog-writer, where the name comes from the file’s name frontmatter or else the file name. The plugin variables are filled in inside the agent’s prompt.
A plugin can make the main session start as one of its agents through default settings.
Hooks
hooks/hooks.json uses the same event names, matchers and fields as hooks in your settings. A { "hooks": { ... } } wrapper is optional.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|NotebookEdit",
"hooks": [
{ "type": "command", "command": "${SYNTIC_PLUGIN_ROOT}/bin/fmt-changed" }
]
}
]
}
}Every command hook from a plugin runs with SYNTIC_PLUGIN_ROOT, SYNTIC_PLUGIN_DATA and each option as SYNTIC_USER_CONFIG_<KEY> exported, and with the plugin’s bin/ first on PATH. Because the variables are exported rather than pasted in, paths with spaces work when you quote them as usual. Events, input fields and exit codes are listed in the hooks reference; the hooks guide shows common recipes.
Changes to a loaded plugin’s hooks/hooks.json are picked up during the session and apply from the next turn.
MCP servers
.mcp.json declares MCP servers in the same shape as your own MCP config. Both { "mcpServers": { ... } } and a bare { "name": { ... } } map are accepted.
{
"mcpServers": {
"tickets": {
"command": "${SYNTIC_PLUGIN_ROOT}/bin/tickets-mcp",
"args": ["--base-url", "${user_config.base_url}"],
"env": { "TICKETS_TOKEN": "${user_config.token}" }
}
}
}Placeholders are filled in command, args, cwd, url, headers and env. Sensitive options are allowed here, because server settings are never shown to the model. An unknown ${user_config.KEY} is left as written so a typo is easy to spot. The server is connected under the id plugin:<plugin>:<server>, so it cannot clash with your own servers. Plugin servers connect at startup; after changing them, restart syntic. See MCP servers for server options and approval.
Language servers
.lsp.json declares language servers for the Lsp tool. Code intelligence has the full field list and examples.
Output styles
Markdown files under output-styles/ appear in /output-style next to your own styles. The format is described on Output styles.
Monitors
A monitor is a background command whose output streams into the session, such as a CI watcher. Declare monitors in the manifest:
{
"monitors": [
{ "name": "ci", "command": "${SYNTIC_PLUGIN_ROOT}/bin/watch-ci", "description": "CI status" },
{ "name": "deploy-log", "command": "tail -f deploy.log", "when": "skill:deploy" }
]
}| Field | Meaning |
|---|---|
name | Required. Unique within the plugin. |
command | Required. Runs in the plugin’s environment. |
description | Shown for the running watch. Defaults to <plugin>: <name>. |
when | launch (default) starts with the session; skill:<name> starts the first time that skill runs. |
Each monitor starts at most once per session and runs like a watch started with the Monitor tool, with the same sandbox and time limits. Running monitors are counted in the status line.
Executables
Anything in bin/ is put first on PATH for the plugin’s hooks, inline shell steps, monitors, MCP servers and language servers. Ship a small wrapper script there instead of asking users to install a tool globally. bin/ does not change PATH for the agent’s own Bash calls.
Default settings
A plugin may ship default settings in settings.json at its root, or in the manifest’s settings field (the file wins). Only one key is honoured:
{ "agent": "reviewer" }This starts the main session as the plugin’s reviewer agent (a bare name means one of the plugin’s own agents; other-plugin:name points elsewhere). If several plugins set an agent, the first one loaded wins. All other keys are ignored, so a plugin cannot change permissions, the model or other settings for you.
Options
The manifest’s userConfig block declares options people set with syntic plugin configure or --config at install:
{
"userConfig": {
"base_url": { "type": "string", "default": "https://tickets.example.com" },
"token": { "type": "string", "required": true, "sensitive": true },
"max_results": { "type": "number", "min": 1, "max": 200, "default": 20 },
"labels": { "type": "string", "multiple": true }
}
}| Where you use an option | How |
|---|---|
| Command bodies, skills, agent prompts | ${user_config.KEY} (sensitive values are never substituted here) |
| MCP and language server settings | ${user_config.KEY} (sensitive values included) |
| Hooks, inline shell, monitors | $SYNTIC_USER_CONFIG_KEY, or ${user_config.KEY}, which is turned into a quoted reference to that variable |
The variable name is the key in upper case with every other character replaced by _: base-url becomes SYNTIC_USER_CONFIG_BASE_URL. A multiple option arrives as a comma-separated list. The field types and storage are covered in the manifest reference.
The data folder
${SYNTIC_PLUGIN_DATA} points to ~/.syntic/plugins/data/<plugin-id>/ (the id with @ and other symbols turned into -). It is created when the plugin loads and survives updates and uninstalls, which makes it the right place for caches, downloaded tools or a virtual environment your hooks set up on first run:
#!/bin/sh
# bin/setup-once: install the formatter into the data folder the first time
[ -x "$SYNTIC_PLUGIN_DATA/venv/bin/fmt" ] && exit 0
python3 -m venv "$SYNTIC_PLUGIN_DATA/venv" && "$SYNTIC_PLUGIN_DATA/venv/bin/pip" -q install fmt-toolDo not write into ${SYNTIC_PLUGIN_ROOT}: an update replaces that folder.