---
title: "Install plugin"
method: POST
path: "/api/plugin"
tags: ["plugin"]
---

# Install plugin

`POST /api/plugin`

Install a new plugin. The request body is a `pluginInfo.json` structure with `branch` and `sha` fields added to specify which branch and commit to install.

`privacyAccepted` (optional, null allowed) is the privacy block the caller showed the operator, the same field `POST /plugin/{RepoName}/upgrade` takes. After the clone and before any dependency or the plugin's own install script it is compared with the cloned copy's block (sends, collects, sensors, remoteAccess, systemChanges, closedCode, other): when they differ -- the listing is behind the repository, or the selected version pins an older commit -- the install is refused, the clone removed, and the reply is `{Status: "Error", Code: "PrivacyMismatch", privacyChanged: true, plugin, pending}` with `pending` the cloned block, so the caller can show it and post again with it as `privacyAccepted` (streaming: the same object follows on one line starting `@@PRIVACY-PENDING@@`). When they match, the block is recorded as accepted in config/pluginPrivacyAccepted.json once its dependencies are in place, before the plugin's own install script runs. A body without the field is compared the same way with the block recorded for that plugin by an earlier install, if any (the record survives an uninstall); with no record either, the install goes ahead and nothing is recorded, so the next update check reports `privacyChanged`. A dependency plugin named in `dependencyPrivacyAccepted` is gated the same way; with that map present, a dependency plugin not in it (declared only in the cloned copy) is refused before it is cloned, `pending` being its listed block. Without the map, dependencies are installed as before.

## Request body

- object — A pluginInfo.json structure plus `branch`, `sha` and optionally `privacyAccepted`.
  - `repoName` string
  - `srcURL` string
  - `branch` string
  - `sha` string
  - `privacyAccepted` object, nullable — The privacy block the caller showed the operator (the `privacy` object of pluginInfo.json), or null when the dialog said "no disclosure". The install is refused (Code PrivacyMismatch) unless it matches the cloned copy's block in the material keys (sends, collects, sensors, remoteAccess, systemChanges, closedCode, other); recorded as accepted once its dependencies are in place. Omit to compare with the earlier record for this plugin, if any.
  - `dependencyPrivacyAccepted` object — repoName -> the privacy block the dialog showed for each dependency plugin this install pulls in (null = no disclosure). Each is gated and recorded the same way as `privacyAccepted` when that plugin is installed as a dependency; a dependency plugin not in the map is refused before it is cloned. Omit to install dependencies ungated, as before.

## Response `200`

Plugin installed, or refused. Refused on privacy grounds: `{"Status": "Error", "Code": "PrivacyMismatch", "Message": "...", "privacyChanged": true, "plugin": "fpp-x", "pending": {...}}` -- `plugin` is the plugin (or dependency plugin) whose block must be reviewed and `pending` that block (null = no disclosure). The same shape `POST /api/plugin/{RepoName}/upgrade` returns for its refusal.

- object
  - `Status` 'OK' | 'Error'
  - `Message` string
  - `Code` 'PrivacyMismatch' — Present only on a privacy refusal, so a script can tell it from any other error without parsing `Message`. The same field on `POST /api/plugin` and `POST /api/plugin/{RepoName}/upgrade`.
  - `privacyChanged` boolean — true on a privacy refusal.
  - `plugin` string — On a privacy refusal: the plugin (for an install, possibly a dependency plugin) whose block must be reviewed.
  - `pending` object, nullable — On a privacy refusal: the block to review (null = no disclosure); post it back as `privacyAccepted` to proceed.

## Changes

- **2026-09-14** `b6816e82545e` — 6 info
  - added the optional property `Code` to the response with the `200` status
  - added the optional property `Message` to the response with the `200` status
  - added the optional property `Status` to the response with the `200` status
  - added the optional property `pending` to the response with the `200` status
  - …2 more
- **2026-09-13** `cce5c6b943c8` — 6 info
  - added the new optional request property `branch`
  - added the new optional request property `dependencyPrivacyAccepted`
  - added the new optional request property `privacyAccepted`
  - added the new optional request property `repoName`
  - …2 more

[Change history](https://skmtc.dev/falconchristmas/apis/fpp-api/changes/api/plugin/post.md)

---

[API](https://skmtc.dev/falconchristmas/apis/fpp-api.md) · [All operations](https://skmtc.dev/falconchristmas/apis/fpp-api/llms.txt) · [OpenAPI document](https://skmtc.dev/falconchristmas/apis/fpp-api/revisions/b6816e82545e?raw)
