Plugin Repositories
Publish plugins to the in-app catalog from a Telegram channel or a Git repository.
Plugin repositories
The plugin catalog lists plugins from repositories. A repository is either a public Telegram channel or a Git repository with an index file. Users add repositories in the catalog, and the catalog shows the newest version of every plugin in each of them.
Catalog cards and update checks use the plugin metadata, so fill in at least
__id__, __name__, __version__, and __category__. See
Plugin Class for .plugin files and
Metadata for Elyx archives.
Telegram channels
Any public channel works as a repository. Post plugin files as documents.
- The catalog reads the latest 2000 documents in the channel and keeps the files with a supported plugin extension.
- Metadata is read from the files themselves. The app downloads them in the
background, so
.pluginfiles must be at most 2 MB and archives at most 10 MB. Larger files are not listed. - Posts that share a plugin
idare versions of one plugin. The catalog shows the newest one, by post date or by the date of the last edit. - Editing a post and replacing its file publishes an update.
Share a channel repository with this link:
Git repositories
A Git repository is read over plain HTTPS, without cloning and without a
hosting API. Everything the catalog needs is in one index file,
extera-plugins.json. You can use GitHub or any host that serves raw files.
Layout
Only extera-plugins.json has a fixed name and place: it sits in the
repository root, or in the directory the repository URL points to. Plugin files
can live anywhere, because the index references each one by path.
Repository URL
Users add a repository by pasting one of these URLs:
| URL | Index location |
|---|---|
https://github.com/{owner}/{repo} | extera-plugins.json on the default branch |
https://github.com/{owner}/{repo}/tree/{ref} | extera-plugins.json on branch or tag {ref} |
https://github.com/{owner}/{repo}/tree/{ref}/{dir} | {dir}/extera-plugins.json on {ref} |
https://{host}/.../extera-plugins.json | That file, on any host |
GitHub URLs are loaded from raw.githubusercontent.com. For GitLab, Codeberg,
or your own server, give the direct raw URL of the index. Only https URLs are
accepted. A {ref} must not contain /; for branches like release/v2, use the
direct raw URL instead.
Share a Git repository with this link, where the parameter is the URL-encoded repository URL:
Index file
The file is UTF-8 JSON and must not exceed 1 MiB.
Repository fields
| Field | Required | Meaning |
|---|---|---|
schema | yes | Format version, currently 1 |
name | yes | Repository title shown in the catalog |
description | no | Short description of the repository |
author | no | Maintainer name or Telegram handle |
homepage | no | Page opened from the repository menu |
plugins | yes | List of plugin entries, may be empty |
Plugin fields
| Field | Required | Meaning |
|---|---|---|
id | yes | Plugin id, unique within the index |
name | yes | Display name |
version | yes | Version of the referenced file |
file | yes | Path to the plugin file, or an https URL |
sha256 | yes | Lowercase hex SHA-256 of the file |
size | yes | File size in bytes |
description | no | Markdown-capable description |
author | no | Author name or Telegram handle |
icon | no | Icon reference, for example exteraPlugins/1 |
app_version | no | App compatibility expression |
sdk_version | no | SDK compatibility expression |
requirements | no | PIP requirements as a comma-separated string or a list |
category | no | One category as a string, or up to three as a list |
updated_at | no | Time of the last file change, RFC 3339 in UTC |
Every field except file, sha256, size, and updated_at matches the
metadata in the plugin file, with the same meaning and rules. For example,
category uses the categories listed in Plugin Class.
The index lists one entry per plugin, for its current version. It keeps no
version history.
Rules
- Relative paths. A relative
fileis resolved against the URL the index was loaded from, so the repository works from any branch, tag, or mirror. Use absolute URLs only for files hosted elsewhere, such as GitHub Releases assets. - Verification. When the app downloads a file, its size and SHA-256 must
match the index. The app then reads the metadata from the file, and
idandversionmust match the entry. If any check fails, the plugin is not installed. - Versions. The app compares the leading numeric part of
versioncomponent by component, so1.10.0is newer than1.9.2and suffixes like-betaare ignored. An entry with a newer version than the installed plugin is offered as an update. - Compatibility. Unknown fields are ignored, so newer tools can add fields
without breaking older apps. If
schemais higher than the app supports, the app does not load the repository and asks the user to update the app. - Refreshing. The app reloads the index at most once every 10 minutes, or
when the user refreshes the catalog. It sends conditional requests, so an
unchanged index costs one
304 Not Modifiedresponse.
Trust
Plugins from Git repositories install as plugins from an unknown source. Installing them requires Unknown sources in plugin settings, and the install sheet shows the security analysis result before the user confirms.
Mirrors
When raw.githubusercontent.com is unreachable, the app may load a GitHub
repository through jsDelivr (cdn.jsdelivr.net/gh/{owner}/{repo}@{ref}).
jsDelivr caches branches, so it can lag behind recent pushes. It loads both the
index and the files from the same mirror, so a lagging mirror shows an older
version rather than a broken one. The fallback only works when the repository
URL names a {ref}.
Generating the index
Write the index with a script, not by hand: hashes and sizes go stale on every
change. This script reads metadata from every plugin file under plugins/ and
writes extera-plugins.json next to itself. It needs
PyYAML only for Elyx archives with YAML
metadata.
To regenerate the index on every push, add a GitHub Actions workflow. Commits made with the workflow's own token do not trigger the workflow again.
Checklist
- Every plugin has a unique, stable
idthat never changes between versions. versiongoes up with every release you want offered as an update.categoryis set, so the plugin shows up under catalog filters.- For Git repositories,
extera-plugins.jsonis regenerated after every change to a plugin file, and itsfilepaths are relative.