---
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. The structure is NOT validated against a schema here, on purpose. FPP reads the keys it knows (repoName, srcURL, versions, dependencies, privacy, ...) and carries everything else through untouched, so a pluginInfo.json written for a newer FPP -- a key this release has never heard of, an unknown key inside `privacy` -- installs on this one. The Plugins page renders the parts it understands and ignores the rest (the full block is still shown as written). Strict validation lives in the fpp-data listing check (`.github/schema/pluginInfo.schema.json`), where a mistake is a rejected pull request rather than a plugin nobody can install. Keep it that way: any check added here must be on a key FPP acts on, never on the set of keys. `privacyAccepted` (optional, null allowed) is the privacy block the caller showed the operator, the same field `POST /plugin/{RepoName}/upgrade` takes. After the clone, 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): 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 carries `Code: "PrivacyMismatch"` and `pending` (the cloned block) so the caller can show it and post again with it as `privacyAccepted`. A body without the field is compared with the block recorded for that plugin from an earlier install, if there is one (the record survives an uninstall), and refused the same way when they differ; with no record either, it installs and records nothing, so the next update check reports `privacyChanged`. The block is recorded once the dependencies are in place, just before the plugin's own install script runs. `dependencyPrivacyAccepted` (optional) is a map repoName -> block or null for the dependency plugins the dialog also showed; each is gated and recorded the same way when that plugin is installed as a dependency of this one, and a dependency not in the map -- declared only in the cloned copy -- is refused before it is cloned (`pending` is then its listed block). Without the map, dependencies are installed as before.

## Request body

- object

## Response `200`

Plugin installed

- object

## Changes

- **2026-09-15** `a923e87eff94` — 12 warning
  - removed the request property `branch`
  - removed the request property `dependencyPrivacyAccepted`
  - removed the request property `privacyAccepted`
  - removed the request property `repoName`
  - …8 more
- **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/5650d7c96687?raw)
