Lewati ke konten utama

Plugins

Honeyframe's plugin architecture is scaffolded but not feature-complete as of v0.0.x. The contract (manifest format, discovery routine) is in place; runtime mounting (router auto-attach, frontend asset merging) is the next step.

This page documents what exists today, what the runtime model will look like when the scaffold matures, and how to write a manifest now so your code is ready to plug in.

Today's state

The plugin host is paas/backend/services/plugin_host.py. It defines:

  • A typed PluginManifest dataclass — the contract.
  • A discover() routine that walks <INSTALL_DIR>/plugins/<vertical>/ and returns validated manifests.

What it does not do yet:

  • Mount FastAPI routers from plugins. (Today, vertical apps run as separate sibling apps — each one is its own service that imports PaaS routers and adds vertical-specific ones.)
  • Merge plugin frontend bundles into the main Vite build.
  • Register plugin dbt workspaces with the dbt scheduler.

When those land, plugins/ becomes the canonical extension surface — until then, vertical features go in their own service (saas/) and import PaaS internals.

Plugin layout

A plugin lives at <INSTALL_DIR>/plugins/<vertical-slug>/ with this layout:

plugins/healthcare/
├── plugin.yaml ← required manifest
├── backend/
│ ├── routers/
│ │ ├── patients.py ← FastAPI router
│ │ └── doctors.py
│ ├── services/
│ └── middleware/
├── frontend/
│ ├── src/
│ │ ├── pages/ ← additional Platform pages
│ │ └── blocks/ ← additional dashboard blocks
└── dbt/ ← optional dbt workspace
├── dbt_project.yml
└── models/

The plugin name is its directory slug (healthcare, finance, etc.) — lowercase, hyphenated, must match plugin.yaml's name field.

plugin.yaml

The manifest contract:

name: healthcare # vertical slug, required
version: 0.1.0 # plugin semver, versions independently of PaaS
display_name: Healthcare # human label for the admin UI

# Dotted Python paths to FastAPI routers. PaaS will mount each at startup
# once router auto-attach lands.
routers:
- plugins.healthcare.backend.routers.patients:router
- plugins.healthcare.backend.routers.doctors:router

# Path (relative to plugin root) of the dbt workspace. Registered with
# the dbt scheduler when dbt auto-attach lands.
dbt_workspace: dbt/

# Frontend entry point — bundled into the Platform Vite build when frontend
# auto-attach lands. Plugin pages appear under /plugins/<name>/...
frontend_entry: frontend/src/index.tsx

# Routes contributed by the plugin's frontend. Used to populate the
# Platform sidebar's nav.
nav:
- label: Patient 360
path: /healthcare/patients
icon: users
- label: Doctors
path: /healthcare/doctors
icon: stethoscope

# Permissions the plugin will request. The Platform validates these against
# the catalog at install time.
permissions:
- patient.read
- patient.edit
- doctor.read

# Default seed groups, rolled into the org's group_permissions on install.
seed_groups:
- name: clinical_admin
permissions: [patient.read, patient.edit, doctor.read]
- name: clinical_viewer
permissions: [patient.read, doctor.read]

Fields are validated at discovery; unknown fields are warnings (forward-compat), required fields missing is an error.

Discovery

from services.plugin_host import discover

for manifest in discover():
print(manifest.name, manifest.version, len(manifest.routers))

discover() returns a list of PluginManifest instances, validated against the schema. If a plugin.yaml is malformed, the plugin is skipped and a warning is logged — discovery never raises.

The discovery root is <INSTALL_DIR>/plugins/. INSTALL_DIR is set by the systemd unit; in dev mode without systemd, plugin_host falls back to the repo root.

When auto-attach lands

The next step (tracked under tech-debt #7) is to use the manifest to actually mount the plugin:

  1. Backend routers — at app.startup, iterate discover() and app.include_router(import_module(path)) for every router in every manifest.
  2. dbt workspace — register the plugin's dbt_project.yml with the dbt scheduler so its models build alongside PaaS models.
  3. Frontend — Vite picks up plugin entry points via a generated pluginsManifest.ts listing each enabled plugin's bundle. The Platform shell loads them as code-split chunks.
  4. Permissions — install-time, the manifest's permissions list is added to the catalog and the seed_groups are written into group_permissions.

Once that ships, a vertical-specific install becomes "drop the plugin folder under plugins/ and restart the platform". No separate service, no monorepo branch, no PaaS-internal imports.

Writing a plugin today

Even without runtime auto-attach, you can:

  1. Create the plugins/<your-vertical>/ directory with the layout above.
  2. Write plugin.yaml against the manifest schema.
  3. Run python -c "from services.plugin_host import discover; print(discover())" to validate.
  4. Develop your routers and UI as if they were already mounted (in dev, mount them by hand in paas/backend/main.py for now).
  5. When auto-attach ships, the manual mount is removed and the same code keeps working.

The reference vertical app under saas/ is the de-facto reference plugin — when extraction lands, vertical services move under plugins/<vertical-slug>/ with no functional change.

Plugin packaging (planned)

Long-term, plugins ship as Python wheels with PEP 621 entry points:

[project.entry-points."honeyframe.plugins"]
healthcare = "plugins.healthcare:manifest"

That replaces the directory-walking discovery with importlib.metadata.entry_points(). Both forms will be supported during the transition.

Don't extend by editing PaaS internals

If your goal is "add a new connector" or "add a new dashboard block", you do not need a plugin manifest. Both surfaces have type-level extension points — see Connectors and Dashboards for adding to the registry. Plugins are for vertical-shaped extensions: a coordinated bundle of backend routes, frontend pages, dbt models, and permissions for a domain like healthcare or finance.