Skip to main content

Plugin Registry

The plugin registry is a JSON manifest (plugins.json) hosted at github.com/TableProApp/plugins. TablePro fetches it to populate Settings > Plugins > Browse and to auto-install a driver when a user selects a database type with no loaded plugin. This page covers the manifest and publishing pipeline; for install and update behavior in the app, see Plugins & Themes. To build a plugin, see Plugin Development.

Manifest Format

The current schema version is 2. The app rejects a manifest with a higher schemaVersion than it supports and falls back to its cached copy.

Entry Fields

Each entry in binaries:
v1 manifests carried top-level downloadURL, sha256, and minPluginKitVersion instead of binaries. The app still decodes them as a fallback: when binaries is absent it synthesizes one entry per architecture, using minPluginKitVersion as the binary’s pluginKitVersion. New entries must use binaries.

Binary Selection

For a database driver, the app filters binaries to the current architecture, keeps those whose pluginKitVersion falls within [minimumCompatiblePluginKitVersion, currentPluginKitVersion] (both declared in PluginManager.swift, both 18 as of 0.57.0), and installs the highest. A driver binary without pluginKitVersion never resolves, and install fails with noCompatibleBinary. Themes carry no native code, so they match on architecture only.

Example Entry

Plugin Metadata

The optional metadata object makes a registry plugin self-describing: the app uses it to render the connection form, sidebar, and editor for a database type before the plugin is installed. Registry driver entries should carry it, or users see a bare form until the download completes. update-registry.py preserves an existing metadata block across releases; edit it by hand in the registry repo. The full field list is RegistryPluginMetadata in TablePro/Core/Plugins/Registry/RegistryModels.swift.

Registry Plugins and databaseTypeIds

databaseTypeIds tells the app which plugin to install when a user picks a database type with no loaded driver. Tag names must match resolve_plugin_info() in .github/workflows/build-plugin.yml. Note the non-obvious tags for Cloudflare D1 and etcd. Bundled plugins (MySQL, PostgreSQL, SQLite, ClickHouse, Redis, and the import/export formats) ship inside the app and are never published to the registry.

Publishing a Plugin

Tag the commit and push that one tag:
Push plugin tags one at a time. A push with more than 3 tags creates no push events on GitHub, so no workflow fires and nothing gets published.
Alternatively, dispatch the workflow directly. The tags input takes comma-separated tag:pluginKitVersion pairs; when :pluginKitVersion is omitted, the workflow reads currentPluginKitVersion from PluginManager.swift:
CI then builds both architectures, signs and notarizes the bundles, verifies each bundle’s TableProPluginKitVersion matches the release label, creates a GitHub release, re-verifies the published assets, and updates plugins.json via .github/scripts/update-registry.py (atomic write with a rebase-and-retry loop against concurrent matrix jobs).

PluginKit Compatibility

TableProPluginKit builds with Swift Library Evolution, so a plugin built against any PluginKit version in the app’s [minimum, current] range loads, and the runtime fills newer requirements from their defaults. What that means for releases:
  • Additive change (new requirement with a default, new field on a non-frozen type): no version bump, no re-publish. Existing binaries keep serving.
  • Breaking change (removed or changed requirement, frozen-layout change, requirement without a default): raise currentPluginKitVersion and minimumCompatiblePluginKitVersion together, then run scripts/release-all-plugins.sh <newVersion>. It bumps each registry plugin’s patch version and fires one workflow_dispatch so all plugins build as a parallel matrix.
  • The app release workflow runs scripts/check-registry-readiness.py --floor <min> --current <current> and fails the release until every database driver has a compatible binary, so the app never ships ahead of its plugins.
  • Retention: update-registry.py keeps binaries for the two newest PluginKit versions per plugin (--keep-kit-versions 2). Older binaries are pruned, so users two or more PluginKit versions behind hit noCompatibleBinary and need to update the app.
When an installed driver predates a breaking bump, the app updates it in the background on next connect. See After an app update.

Caching

The app fetches the manifest from raw.githubusercontent.com/TableProApp/plugins/main/plugins.json, which caches at the edge for about five minutes. Every fetch revalidates with a conditional request, the list refreshes at launch and when the plugin browser opens (throttled to one check per five minutes), and an install prompt forces a fresh fetch before reporting a plugin as missing. CI also purges the jsDelivr cache after each registry push for older app versions that still fetch from there. A newly published plugin is visible in the app within minutes.

Theme Distribution

Themes use the same manifest format with category: "theme". Differences from driver plugins:
  • Pure JSON data, no executable code, no code signing, no .tableplugin bundle
  • ZIP contains .json files (valid ThemeDefinition); theme packs with multiple themes are supported
  • Installed to ~/Library/Application Support/TablePro/Themes/Registry/
  • No pluginKitVersion needed; binaries match on architecture only, and the flat v1 fields still work

Custom Registry URL

For enterprise or private registries:
HTTP caching keys on the full URL, so changing the registry URL takes effect on the next fetch. These commands are also listed in Settings.