# Plug-in API overview `org.vibeerp.api.v1` (`api.v1` for short) is the **only stable contract** in vibe_erp. It is the surface that PF4J plug-ins compile against, and the only surface they are permitted to touch. Everything else in the codebase — `platform.*`, every PBC's internal package, every concrete Spring bean — is internal and may change in any release. For the architectural reasoning behind `api.v1`, see [`../architecture/overview.md`](../architecture/overview.md) and the full spec at [`../superpowers/specs/2026-04-07-vibe-erp-architecture-design.md`](../superpowers/specs/2026-04-07-vibe-erp-architecture-design.md). ## What `api.v1` is - A Kotlin module published to **Maven Central** as `org.vibeerp:api-v1`. - Depends on **only** Kotlin stdlib and `jakarta.validation`. No Spring, no Hibernate, no PF4J types leak through it. - Semver-governed on the `1.x` line. Across a major version, `api.v1` and `api.v2` ship side by side for at least one major release window so plug-ins have time to migrate. - The single import surface for plug-ins. The plug-in linter rejects any import outside `org.vibeerp.api.v1.*` at install time. ## Package layout ``` org.vibeerp.api.v1 ├── core/ Id, Money, Currency, Quantity, UnitOfMeasure, Result ├── entity/ Entity, AuditedEntity, FieldType, CustomField, EntityRegistry ├── persistence/ Repository, Query, Page, Transaction, PersistenceExceptions ├── event/ DomainEvent, EventListener, EventBus (with Subscription) ├── security/ Principal, PrincipalId, Permission, PermissionCheck ├── i18n/ MessageKey, Translator, LocaleProvider ├── http/ @PluginEndpoint, RequestContext ├── plugin/ Plugin, PluginContext, PluginManifest, ExtensionPoint, │ PluginEndpointRegistrar, HttpMethod, PluginRequest, │ PluginResponse, PluginEndpointHandler, PluginJdbc, PluginRow ├── ext/ Typed cross-PBC facades: ext.identity.IdentityApi, │ ext.catalog.CatalogApi ├── workflow/ WorkflowTask, TaskHandler, TaskContext └── form/ FormSchema ``` A short orientation: | Package | What it gives you | |---|---| | `core/` | The primitive value types every plug-in needs: typed `Id`, `Money` + `Currency`, `Quantity` + `UnitOfMeasure`, `Result`. No printing concepts. | | `entity/` | Declarative entity model. `Entity` and `AuditedEntity` markers, `FieldType` sealed hierarchy, `CustomField` and `EntityRegistry` for the metadata-driven custom fields story. | | `persistence/` | `Repository`, `Query`, `Page`, `Transaction`, plus the closed `PersistenceException` hierarchy (`OptimisticLockConflictException`, `UniqueConstraintViolationException`, `EntityValidationException`, `EntityNotFoundException`). Plug-ins never see Hibernate, JPA, or Spring `@Transactional` directly. | | `event/` | `DomainEvent`, `EventListener`, `EventBus` with `Subscription` (closeable) and topic-string + class-keyed subscribe overloads. The primary cross-PBC communication channel. **Live as of P1.7.** | | `security/` | `Principal` (sealed: `User`, `System`, `PluginPrincipal`), `PrincipalId`, `Permission`, `PermissionCheck`. Plug-ins register their own permissions; the role editor auto-discovers them. | | `i18n/` | `MessageKey`, `Translator`, `LocaleProvider`. The only sanctioned way for a plug-in to produce user-facing text. (Real ICU4J `Translator` impl is P1.6 — the api.v1 contract is locked, the host implementation throws `UnsupportedOperationException` until then.) | | `http/` | `@PluginEndpoint` annotation and `RequestContext` for plug-in HTTP handlers. | | `plugin/` | The plug-in lifecycle: `Plugin` interface, `PluginContext` (with live accessors for `logger`, `endpoints`, `eventBus`, `jdbc`), `PluginManifest`, `ExtensionPoint`/`@Extension`, and the endpoint types: `PluginEndpointRegistrar`, `HttpMethod`, `PluginRequest`, `PluginResponse`, `PluginEndpointHandler`, plus the typed-SQL surface `PluginJdbc`/`PluginRow`. | | `ext/` | Typed cross-PBC facades. Two are live today: `ext.identity.IdentityApi` (with `UserRef`) and `ext.catalog.CatalogApi` (with `ItemRef`/`UomRef`). Future PBCs will add their own under the same pattern. | | `workflow/` | `WorkflowTask`, `TaskHandler`, `TaskContext` (with `tenantId` removed — single-tenant — and `principal()`, `locale()`, `correlationId()` accessors). The hooks BPMN service tasks call into when Flowable lands (P2.1). | | `form/` | `FormSchema` — JSON Schema string + UI Schema string + version. Form parsing and rendering happens in the host (server) and the SPA (client). | ## The stability contract | Change | Allowed within 1.x? | |---|---| | Add a class to `api.v1` | yes | | Add a method to an `api.v1` interface (with default impl) | yes | | Remove or rename anything in `api.v1` | no — major bump | | Change behavior of an `api.v1` symbol in a way plug-ins can observe | no — major bump | | Anything in `platform.*` or `pbc.*.internal.*` | yes — that is why it is internal | Practical consequences for plug-in authors: - A plug-in built against `api.v1` version `1.4.0` will load in any vibe_erp `1.x` release. - A symbol that gets deprecated in `1.x` keeps working until `2.0`. The plug-in loader emits a warning when a plug-in uses a deprecated symbol — that is the **Grade C** signal in the extension grading. - A plug-in that reaches into `platform.*` or `pbc.*.internal.*` via reflection is **Grade D**, unsupported, and rejected by the plug-in linter at install time. When in doubt about whether something belongs in `api.v1`: **keep it out**. Growing the API deliberately later is much cheaper than maintaining a regretted symbol forever. ## The A/B/C/D extension grading From CLAUDE.md guardrail #7. Every extension to vibe_erp falls into one of four grades, ordered from safest to least safe. | Grade | What it is | Upgrade safety | |---|---|---| | **A** | Tier 1 metadata only — custom fields, forms, workflows, rules, list views, translations entered through the web UI. Stored as rows in `metadata__*` tables, tagged `source = 'user'`. | Always upgrade-safe across **any** core version. | | **B** | Tier 2 plug-in using only the public `api.v1` surface. | Safe within a major version. Loads cleanly across every `1.x` release. | | **C** | Tier 2 plug-in using deprecated-but-supported `api.v1` symbols. | Safe until the next major. The plug-in loader emits warnings; the plug-in author should migrate before the next major release. | | **D** | Tier 2 plug-in reaching into `platform.*` or `pbc.*.internal.*` via reflection. | UNSUPPORTED. The plug-in linter rejects this at install time. Will break on the next core upgrade. | Two principles follow from the grading: 1. Anything a Tier 2 plug-in does should also become possible as a Tier 1 customization over time. Tier 2 is the escape hatch, not the default. 2. If you find yourself wanting to do Grade D, the seam is wrong and `api.v1` needs to grow — deliberately, with a version bump consideration. ## Reference The full reference for every type, method, and contract in `api.v1` is generated from KDoc by **Dokka** and published alongside each release. This document is the conceptual overview; the generated reference is the authoritative per-symbol documentation. (The Dokka site is wired up as part of the v1.0 documentation deliverable; until then, the source under `api/api-v1/src/main/kotlin/org/vibeerp/api/v1/` is the source of truth, and every public type carries a KDoc block — that is part of the PR checklist in [`../../CONTRIBUTING.md`](../../CONTRIBUTING.md).) ## Where to go next - Build your first plug-in: [`../plugin-author/getting-started.md`](../plugin-author/getting-started.md) - Author a workflow: [`../workflow-authoring/guide.md`](../workflow-authoring/guide.md) - Author a form: [`../form-authoring/guide.md`](../form-authoring/guide.md) - Localize a plug-in: [`../i18n/guide.md`](../i18n/guide.md)