Package Manager
Endpoints for managing Grav packages (plugins and themes) via the GPM, including installation, removal, updates, and repository search, plus the endpoints that serve plugin admin pages and web component scripts to Admin2.
Endpoints for managing Grav packages (plugins and themes) via the GPM, including installation, removal, updates, and repository search, plus the endpoints that serve plugin admin pages and web component scripts to Admin2.
List Plugins
/gpm/plugins
{"data": [{"slug": "simplesearch", "name": "SimpleSearch", "version": "3.0.0", "type": "plugin", "description": "A simple search plugin for Grav.", "description_html": "<p>A simple search plugin for Grav.</p>", "author": {"name": "Team Grav", "email": "[email protected]", "url": "https://getgrav.org"}, "homepage": "https://github.com/getgrav/grav-plugin-simplesearch", "enabled": true, "is_symlink": false, "available_version": "3.0.1", "updatable": true}]}
Response Codes
Each entry is a package object. version is the installed version; available_version appears only when updatable is true. Optional blueprint fields (docs, bugs, dependencies, compatibility, keywords, icon) appear only when the plugin sets them, and premium plugins add premium, licensed, and when known vendor and purchase_url. A plugin whose settings are drawn on an admin page adds settings_route (a hash route) and, when that page belongs to another plugin, settings_page. A name or description written as a translation key is translated into the caller's admin language.
Get Plugin
/gpm/plugins/{slug}
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | The plugin slug. |
{"data": {"slug": "simplesearch", "name": "SimpleSearch", "version": "3.0.0", "type": "plugin", "description": "A simple search plugin for Grav.", "description_html": "<p>A simple search plugin for Grav.</p>", "author": {"name": "Team Grav", "email": "[email protected]", "url": "https://getgrav.org"}, "homepage": "https://github.com/getgrav/grav-plugin-simplesearch", "enabled": true, "is_symlink": false, "available_version": "3.0.1", "updatable": true, "custom_fields": {"simplesearch-preview": "simplesearch-preview"}}}
Response Codes
The response carries an ETag header. Send it back as If-None-Match and the API answers 304 Not Modified with an empty body when nothing changed. custom_fields maps each field type the plugin ships under admin-next/fields/ to itself.
Install Package
/gpm/install
Parameters
| Name | Type | Description |
|---|---|---|
| package required | string | Package slug to install |
| type optional | string | Package type: `plugin` (default) or `theme` |
| license optional | string | Licence key for a premium package. It is saved only when the slug names a real package, and removed again if the install fails. |
{"package": "sitemap", "type": "plugin"}
{"data": {"message": "Plugin 'sitemap' installed successfully.", "package": "sitemap", "type": "plugin", "dependencies": []}}
Response Codes
Remove Package
/gpm/remove
Parameters
| Name | Type | Description |
|---|---|---|
| package required | string | Package slug to remove |
{"package": "sitemap"}
Response Codes
Check Updates
/gpm/updates
Parameters
| Name | Type | Description |
|---|---|---|
| flush optional | boolean | Refresh the cached GPM repository data before checking. |
{"data": {"grav": {"current": "2.1.0", "available": "2.1.1", "updatable": true, "date": "2026-09-18", "is_symlink": false}, "plugins": [{"slug": "form", "name": "Form", "version": "8.1.0", "type": "plugin", "description": "Enables forms handling and processing.", "description_html": "<p>Enables forms handling and processing.</p>", "author": {"name": "Team Grav", "email": "[email protected]", "url": "https://getgrav.org"}, "homepage": "https://github.com/getgrav/grav-plugin-form", "enabled": true, "is_symlink": false, "available_version": "8.1.1", "updatable": true}], "themes": [], "total": 2, "installed": 14}}
Response Codes
In grav, current is the installed version and available the latest in the repository (null when the repository could not be read). In plugins and themes, each entry is a package object where version is the installed version and available_version the new one. installed is the number of installed plugins and themes combined.
Search Repository
/gpm/search
Parameters
| Name | Type | Description |
|---|---|---|
| q required | string | Search query string, matched against name, slug and description |
| page optional | integer | Page number for pagination (default: 1) |
| per_page optional | integer | Number of results per page (default: 20, max: 1000) |
{"data": [{"slug": "simplesearch", "name": "SimpleSearch", "version": "3.0.1", "type": "plugin", "description": "A simple search plugin for Grav.", "description_html": "<p>A simple search plugin for Grav.</p>", "author": {"name": "Team Grav", "email": "[email protected]", "url": "https://getgrav.org"}, "homepage": "https://github.com/getgrav/grav-plugin-simplesearch", "installed": true}], "meta": {"pagination": {"page": 1, "per_page": 20, "total": 1, "total_pages": 1}}, "links": {"self": "/api/v1/gpm/search?page=1&per_page=20"}}
Response Codes
List Themes
/gpm/themes
{"data": [{"slug": "quark", "name": "Quark", "version": "2.0.4", "type": "theme", "description": "Quark is the default theme for Grav.", "description_html": "<p>Quark is the default theme for Grav.</p>", "author": {"name": "Team Grav", "email": "[email protected]", "url": "https://getgrav.org"}, "homepage": "https://github.com/getgrav/grav-theme-quark", "enabled": true, "is_symlink": false, "updatable": false, "thumbnail": "/api/v1/thumbnails/8c1f2a9e4b.jpg", "screenshot": "/api/v1/thumbnails/0d3e7b61fa.jpg"}]}
Response Codes
thumbnail (capped at 500px) and screenshot (capped at 2000px) are /thumbnails/{file} URLs made from the theme's own thumbnail and screenshot images. When a theme has only one of the two, both fields use it; with neither, both are null.
Get Theme
/gpm/themes/{slug}
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | The theme slug. |
{"data": {"slug": "quark", "name": "Quark", "version": "2.0.4", "type": "theme", "description": "Quark is the default theme for Grav.", "description_html": "<p>Quark is the default theme for Grav.</p>", "author": {"name": "Team Grav", "email": "[email protected]", "url": "https://getgrav.org"}, "homepage": "https://github.com/getgrav/grav-theme-quark", "enabled": true, "is_symlink": false, "updatable": false, "thumbnail": "/api/v1/thumbnails/8c1f2a9e4b.jpg", "screenshot": "/api/v1/thumbnails/0d3e7b61fa.jpg"}}
Response Codes
The response carries an ETag header. Send it back as If-None-Match and the API answers 304 Not Modified with an empty body when nothing changed.
Update Package
/gpm/update
Parameters
| Name | Type | Description |
|---|---|---|
| package required | string | Package slug to update |
{"package": "form"}
{"data": {"message": "Package 'form' updated successfully.", "package": "form", "type": "plugin", "dependencies": []}}
Response Codes
Update All Packages
/gpm/update-all
{"data": {"updated": ["simplesearch", "seo-magic"], "failed": [{"package": "broken-plugin", "error": "Dependency unmet"}], "skipped": [{"package": "form", "reason": "already up to date (installed as a dependency)"}], "cascaded_dependencies": ["form"]}}
Response Codes
The response has four arrays:
updated: slugs that were updated.failed: objects withpackageanderrorfor packages whose requirements weren't met or whose update failed.skipped: objects withpackageandreasonfor packages already brought up to date as a dependency of an earlier package in the batch.cascaded_dependencies: slugs installed or updated as dependencies along the way.
Browse Repository Themes
/gpm/repository/themes
Parameters
| Name | Type | Description |
|---|---|---|
| q optional | string | Search query to filter themes (matches name / slug / description). |
| page optional | integer | Page number (default 1). |
| per_page optional | integer | Items per page (default from config; max 2000 so the install modal can fetch the full list). |
{"data": [{"slug": "quark", "name": "Quark", "version": "2.0.4", "type": "theme", "description": "Quark is the default theme for Grav.", "description_html": "<p>Quark is the default theme for Grav.</p>", "author": {"name": "Team Grav", "email": "[email protected]", "url": "https://getgrav.org"}, "homepage": "https://github.com/getgrav/grav-theme-quark", "screenshot": "https://getgrav.org/images/quark-screenshot.jpg", "installed": true}], "meta": {"pagination": {"page": 1, "per_page": 20, "total": 120, "total_pages": 6}}, "links": {"self": "/api/v1/gpm/repository/themes?page=1&per_page=20", "next": "/api/v1/gpm/repository/themes?page=2&per_page=20", "last": "/api/v1/gpm/repository/themes?page=6&per_page=20"}}
Response Codes
In repository results version is the latest version in the repository and screenshot is the full URL of the repository screenshot. Premium themes add premium, licensed, and when known vendor and purchase_url.
Get README
/gpm/plugins/{slug}/readme
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | The package slug |
{"data": {"content": "# My Plugin\n\nThis plugin does..."}}
Response Codes
Get Changelog
/gpm/plugins/{slug}/changelog
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | The package slug |
{"data": {"content": "# v1.2.0\n## 01/15/2025\n\n* Added feature X..."}}
Response Codes
Get Plugin Page
/gpm/plugins/{slug}/page
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | The plugin slug |
{"data": {"id": "license-manager", "plugin": "license-manager", "title": "License Manager", "icon": "fa-key", "page_type": "blueprint", "blueprint": "licenses", "data_endpoint": "/licenses/form-data", "save_endpoint": "/licenses", "actions": [{"id": "save", "label": "Save", "icon": "fa-check", "primary": true}], "has_custom_component": false}}
Response Codes
A definition may also carry settings_route, a hash route inside the page where the plugin's settings are drawn, and settings_page, the slug of another installed plugin whose admin page draws them. settings_page is kept only together with a valid settings_route.
Get Plugin Page Script
/gpm/plugins/{slug}/page-script
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | Plugin slug. |
Response Codes
Requires only api.access, so any admin who can reach the plugin's screens can load them without package manager rights. The script is served as application/javascript with Cache-Control: private, no-cache and an ETag built from the file's modification time and size, so a repeat request with If-None-Match gets an empty 304 until the file changes.
Get Grav Changelog
/gpm/grav/changelog
Parameters
| Name | Type | Description |
|---|---|---|
| flush optional | boolean | Refresh the cached GPM repository data first. |
{"data": {"content": "# v2.1.1 (09/18/2026)\n\n1. [](#bugfix)\n * Fixed ..."}}
Response Codes
Upgrade Grav Core
/gpm/upgrade
Parameters
| Name | Type | Description |
|---|---|---|
| override optional | boolean | Upgrade even when the preflight checks report blocking problems. |
{"override": false}
{"data": {"message": "Grav upgraded successfully.", "previous_version": "2.1.0", "new_version": "2.1.1"}}
Response Codes
When the preflight checks block the upgrade, the 409 response is a normal data payload rather than an error:
{
"data": {
"status": "preflight_failed",
"message": "Upgrade blocked by preflight checks.",
"blocking": [
"Some enabled plugins/themes have not been marked as compatible with Grav 2.2. Disable them before continuing."
],
"warnings": [],
"incompatible_packages": { "target": "2.2" },
"can_override": true
}
}
Direct Install
/gpm/direct-install
Parameters
| Name | Type | Description |
|---|---|---|
| url optional | string | An `http://` or `https://` URL of the package zip (mutually exclusive with `file`). Paths on the server are refused; upload a local zip as `file` instead. |
| file optional | file | Uploaded zip file (multipart; mutually exclusive with `url`). |
{"url": "https://downloads.example.com/builds/my-plugin-1.0.0.zip"}
{"data": {"message": "Package installed successfully via direct install."}}
Response Codes
List Repository Plugins
/gpm/repository/plugins
Parameters
| Name | Type | Description |
|---|---|---|
| page optional | integer | Page number (default 1). |
| per_page optional | integer | Items per page (default from config; max 2000). |
| q optional | string | Search filter matched against name / slug / description. |
{"data": [{"slug": "simplesearch", "name": "SimpleSearch", "version": "3.0.1", "type": "plugin", "description": "A simple search plugin for Grav.", "description_html": "<p>A simple search plugin for Grav.</p>", "author": {"name": "Team Grav", "email": "[email protected]", "url": "https://getgrav.org"}, "homepage": "https://github.com/getgrav/grav-plugin-simplesearch", "keywords": ["search", "plugin"], "installed": true}], "meta": {"pagination": {"page": 1, "per_page": 50, "total": 250, "total_pages": 5}}, "links": {"self": "/api/v1/gpm/repository/plugins?page=1&per_page=50", "next": "/api/v1/gpm/repository/plugins?page=2&per_page=50", "last": "/api/v1/gpm/repository/plugins?page=5&per_page=50"}}
Response Codes
In repository results version is the latest version in the repository. To compare with what is installed, use List Plugins or Check Updates. Premium plugins add premium, licensed, and when known vendor and purchase_url.
Get Repository Package
/gpm/repository/{slug}
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | Package slug. |
{"data": {"slug": "simplesearch", "name": "SimpleSearch", "version": "3.0.1", "type": "plugin", "description": "A simple search plugin for Grav.", "description_html": "<p>A simple search plugin for Grav.</p>", "author": {"name": "Team Grav", "email": "[email protected]", "url": "https://getgrav.org"}, "homepage": "https://github.com/getgrav/grav-plugin-simplesearch", "keywords": ["search", "plugin"], "installed": true}}
Response Codes
Get Custom Field Script
/gpm/plugins/{slug}/field/{type}
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | Plugin (or theme) slug. |
| type required | string | Field type identifier (matches the `type:` value in the blueprint). |
Response Codes
Requires only api.access, so any admin who can reach the plugin's screens can load them without package manager rights. The script is served as application/javascript with Cache-Control: private, no-cache and an ETag built from the file's modification time and size, so a repeat request with If-None-Match gets an empty 304 until the file changes.
Get Report Script
/gpm/plugins/{slug}/report-script/{reportId}
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | Plugin slug. |
| reportId required | string | Report identifier (matches the `id` from `onApiGenerateReports`). |
Response Codes
Requires only api.access, so any admin who can reach the plugin's screens can load them without package manager rights. The script is served as application/javascript with Cache-Control: private, no-cache and an ETag built from the file's modification time and size, so a repeat request with If-None-Match gets an empty 304 until the file changes.
Get Floating Widget Script
/gpm/plugins/{slug}/widget-script
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | Plugin slug. |
Response Codes
Requires only api.access, so any admin who can reach the plugin's screens can load them without package manager rights. The script is served as application/javascript with Cache-Control: private, no-cache and an ETag built from the file's modification time and size, so a repeat request with If-None-Match gets an empty 304 until the file changes.
Get Context Panel Script
/gpm/plugins/{slug}/panel-script
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | Plugin slug. |
Response Codes
Requires only api.access, so any admin who can reach the plugin's screens can load them without package manager rights. The script is served as application/javascript with Cache-Control: private, no-cache and an ETag built from the file's modification time and size, so a repeat request with If-None-Match gets an empty 304 until the file changes.
Get Modal Script
/gpm/plugins/{slug}/modal-script/{modalId}
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | Plugin slug. |
| modalId required | string | Modal component id — the basename of the file under `admin-next/modals/`. |
Response Codes
Requires only api.access, so any admin who can reach the plugin's screens can load them without package manager rights. The script is served as application/javascript with Cache-Control: private, no-cache and an ETag built from the file's modification time and size, so a repeat request with If-None-Match gets an empty 304 until the file changes.
Get Custom Field Bundle
/gpm/plugins/{slug}/fields
Parameters
| Name | Type | Description |
|---|---|---|
| slug required | string | Plugin (or theme) slug. |
{"seo-magic-preview": "class SeoMagicPreview extends HTMLElement { ... }", "seo-magic-keywords": "class SeoMagicKeywords extends HTMLElement { ... }"}
Response Codes
The body is the bare JSON map, not wrapped in the usual data envelope. It is served with Cache-Control: private, no-cache and an ETag built from each file's name, modification time and size, so revalidating costs only a 304 until a field file changes. Requires only api.access.