Plugins/SurrealDBDriverPlugin/ is the compact reference: no C bridge, an HTTP transport, the schema-aware query hooks filled in, and an editor language of its own.
Two protocols do the work. DriverPlugin describes the database (name, port, capabilities, SQL dialect) and creates driver instances; PluginDatabaseDriver is the connection itself. Implement both in a .tableplugin bundle linked against TableProPluginKit, and PluginDriverAdapter bridges your driver to the app’s internal DatabaseDriver. A driver that connects and lists tables gets the whole UI without asking: connection form, sidebar, data grid, editor, import, export.
Bundle layout
A plugin is a macOS loadable bundle target withWRAPPER_EXTENSION = tableplugin that links TableProPluginKit. Set INFOPLIST_KEY_NSPrincipalClass to your DriverPlugin class.
Leave
TableProProvidesDatabaseTypeIds out and the plugin loads eagerly at startup, blocking launch, and PluginManager logs a warning naming the key. With it, the app registers your metadata from Info.plist and loads the binary on first use.
Implementing DriverPlugin
Your principal class conforms toTableProPlugin and DriverPlugin. Nine members have no default, and this is all of them:
DriverConnectionConfig carries host, port, username, password, database, SSL settings, and an additionalFields dictionary filled from whatever additionalConnectionFields you declared.
The remaining fifty-odd statics all have defaults: connection mode, URL schemes, brand color, editor language, sqlDialect (keywords, functions, completions), navigation model, system database names, and two dozen supports* capability flags. Override the ones that differ. Plugins/TableProPluginKit/DriverPlugin.swift is the full list.
Implementing PluginDatabaseDriver
Twelve requirements have no default. Everything else on the protocol does.
Four defaults are worth a second look before you accept them:
ping()runsSELECT 1, and the transaction methods runBEGIN/COMMIT/ROLLBACKthroughexecute(query:). An engine without those keywords overrides all four.fetchAllColumns(schema:)andfetchAllForeignKeys(schema:)loop one round-trip per table. Any SQL driver should replace them with a single catalog query.quoteIdentifier,escapeStringLiteral,executeParameterized, andstreamRowsassume generic SQL.- A non-SQL database implements
buildBrowseQuery,buildFilteredQuery, andgenerateStatementsinstead, which is what makes browsing and editing work without SQL. Implement theschema:-aware overloads if your database has schemas; the schema-less defaults throw the schema away.
ABI compatibility
TableProPluginKit builds with Swift Library Evolution, so a plugin built against an older PluginKit keeps loading under a newer app: the runtime fills requirements it never implemented from their defaults. Adding a requirement that has a default costs nothing. Two changes look additive and are not. Both have shipped, and both surface the same way: every registry plugin fails to load with “Bundle failed to load executable”.- Removing a published requirement, even one that defaulted to
nil. Library Evolution rescues a requirement added after a plugin was built, never one removed out from under it. Removing one deletes its method descriptor and its default-implementation symbol, and every shipped plugin hard-references both in its witness table. If the app stops using a requirement, leave it in place with its default. (0.58, #1917: MongoDB, Oracle, Cassandra, Elasticsearch.) - Adding a parameter to an existing public initializer or function, even with a default value. It replaces the mangled symbol. Add a new overload for the new field and mark the old one
@_disfavoredOverload, so new code gets the full initializer and old binaries keep theirs.
CLAUDE.md carries the full additive-versus-breaking list and the checklist a breaking bump obliges you to run, including re-releasing every registry plugin before the app ships.
Plugins/TableProPluginKit/. It builds the framework at your tree and at the base ref with one toolchain and diffs the two public interfaces, so a Swift version difference between machines can never fake a diff. The base ref defaults to origin/main; pass the merge base when reviewing a branch. Commit or stash first, since it refuses a dirty working tree.
Building outside this repository
A plugin needs TableProPluginKit, not this repository. Every driver happens to live underPlugins/ here. A new one does not have to.
pluginkit-v<version> release, and link it from your own Xcode project. Your plugin target then needs nothing else from this repository.
Link it as Do Not Embed. The app supplies TableProPluginKit at runtime, so a copy inside your bundle is a second, conflicting one. The script refuses to emit a framework with no .swiftinterface, which is what a build without Library Evolution looks like: it links today and breaks every consumer on the next release.
Publishing
Two steps put a plugin in TablePro’s own registry, and both happen in this repository:1
Add a manifest entry
Add your plugin to
.github/plugin-registry.json, keyed by the slug its tag will use. Without an entry the release workflow stops with Unknown plugin. icon and databaseTypeIds there have to match what your DriverPlugin class declares, and scripts/ci/check-plugin-manifest.py fails PR CI and the release workflow when they drift.2
Push one tag
plugin-<slug>-v<version>. CI builds both architectures, signs, notarizes, and updates plugins.json.metadata block that renders your connection form before the download finishes, and how to point the app at a private registry.
