Reusable Spring Boot starters for building onno-style business applications in Java.
The repository is a Gradle multi-module build. Applications usually consume one or more published artifacts rather than including this repository as a composite build.
For teams or AI agents using these libraries to build an ERP application, start with Building ERPs With onno-framework And AI Agents.
Building a headless front end or integration against the generic API? See the Headless Read API for the JSON response contract (column-name keys, reference/enum expansion, secret redaction, list vs get) and how to react to changes.
The published docs site — hand-written guides plus reference generated from the source of truth —
lives at https://docs.onno.su/ (built with VitePress
by .github/workflows/docs.yml).
| Doc | What it covers |
|---|---|
| AGENTS.md | Operating rules for AI agents + the playbook for modeling a business into framework concepts. |
| BUILDING_ERPS_WITH_AGENTS.md | Handoff guide for building an ERP in a separate project on the published libraries. |
| docs/ARCHITECTURE.md | How the framework fits together: boot pipeline, each subsystem, the full endpoint catalog, open-core boundary. |
| docs/CONFIGURATION.md | Every onno.* configuration property, by module, with defaults. Generated from the @ConfigurationProperties Javadoc — see below. |
| docs/HEADLESS_READ_API.md | JSON response contract for the generic read API. |
| docs/MEDIA_UPLOADS.md | Binary upload endpoint and the MediaStorage SPI. |
| docs/EXTENDING.md | How to build a community extension (connector, SPI, UI, skill), the naming/namespace conventions, and how to get it listed. |
| CONTRIBUTING.md | How to contribute code and how to list a community integration. |
| INTEGRATIONS.md | Catalog of community-built integrations (generated from community/registry.json). |
| Java API (Javadoc) | Aggregated API reference at /api on the docs site (./gradlew aggregateJavadoc). |
onno skill |
A hands-on expert playbook + cheat sheet that makes Claude Code good at this framework. Auto-loaded for anyone working in this repo; installable by downstream apps (see below). |
Each module also has its own README.md with integration-specific setup.
docs/CONFIGURATION.md is generated, not hand-maintained — its tables come from each starter's
spring-configuration-metadata.json, which Spring's annotation processor builds from the
@ConfigurationProperties field Javadoc. This keeps the config reference from drifting:
./gradlew generateConfigDocs # rewrite docs/CONFIGURATION.md from the @ConfigurationProperties Javadoc
./gradlew checkConfigDocs # fail if it drifted (also runs as part of `./gradlew check`)To change a property's docs, edit its Javadoc and regenerate — never edit the table by hand.
Preview the full site locally:
./gradlew generateConfigDocs aggregateJavadoc # refresh generated reference
mkdir -p docs/public/api && cp -R build/docs/javadoc/. docs/public/api/ # stage Javadoc under /api
cd docs && npm install && npm run docs:dev # http://localhost:5173/onno-framework/The onno skill teaches Claude Code to model a business into framework concepts, get posting /
validation / migration right, and use the runtime API — the canonical copy lives in
onno-plugin/skills/onno/.
-
Working in this repo? It's auto-discovered —
.claude/skills/onnois a symlink to the canonical copy, so Claude Code loads it with no setup. -
Building a separate app on the published artifacts? Install it as a plugin (this repo doubles as a plugin marketplace via
.claude-plugin/marketplace.json):/plugin marketplace add onno-erp/onno-framework /plugin install onno@onno-frameworkClaude then auto-invokes it when relevant (or run
/onno:onnoexplicitly).
| Module | Purpose |
|---|---|
onno-framework |
Core annotations, metadata scanners, repository contracts, schema generation, posting, UI layout model, and shared types. |
onno-framework-starter |
Spring Boot auto-configuration for the core framework and repositories. |
onno-ui-starter |
Generic web UI controllers and packaged frontend assets. |
onno-auth-starter |
Basic Spring Security auto-configuration and auth API endpoints. |
onno-print-starter |
Thymeleaf-based document rendering and PDF output support. |
onno-mail-starter |
Mail templates, dispatchers, suppression, preview endpoints, and outbox relay support. |
onno-kafka-starter |
Kafka event publishing, inbox routing, service registry, and remote reference helpers. |
onno-import-starter |
CSV import services and endpoints for catalogs. |
onno-cluster-starter |
Cross-node delivery of entity-change events for horizontal scale-out (pluggable bus; default Postgres LISTEN/NOTIFY). |
onno-mcp-starter |
MCP server exposing metadata, CRUD, register queries, and posting as AI-agent tools. |
onno-desktop-starter |
Desktop runtime support and packaged Tauri shell resources. |
onno-desktop-gradle-plugin |
Gradle plugin for packaging a Spring Boot app as a native desktop bundle. |
example |
Local example application. It is not intended to be published as a library. |
Commercial vertical connectors — onno-guesty-starter (Guesty Open API) and
onno-hospedajes-starter (Spanish SES.HOSPEDAJES) — live in the separate, commercially licensed
onno-enterprise repository. See the
License section.
The framework is built to be extended without forking — you ship a separate artifact the host
app opts into. Four extension surfaces: connectors (wrap an external system), SPI
implementations (MediaStorage, MailDispatcher, custom auth, …), UI (widgets/pages/actions),
and Claude skills/plugins. The full how-to — the starter shape, the naming/namespace conventions
that keep the su.onno and su.onno.* namespaces reserved, and a "definition of done"
checklist — is in docs/EXTENDING.md.
Built one? Add it to the community catalog in INTEGRATIONS.md: append an entry to
community/registry.json, run ./gradlew generateIntegrationsDoc, and
open a PR (see CONTRIBUTING.md).
- Java 21
- Gradle wrapper from this repository
- Spring Boot 3.4.x in consuming applications
- Node 20 is downloaded automatically when building
onno-ui-starter
Build and test all modules:
./gradlew clean checkPublish artifacts to the local Maven repository:
./gradlew publishToMavenLocalThen consume them from another local project:
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation("su.onno:onno-framework-starter:0.1.0-SNAPSHOT")
implementation("su.onno:onno-ui-starter:0.1.0-SNAPSHOT")
}Maven coordinates use the
su.onnogroup; the Java packages are stillsu.onno.*, so your imports don't change.
Released modules are published to Maven Central
under the su.onno group. Consumers need no credentials and no custom repository —
just mavenCentral():
val onnoVersion = "<latest>" // pick the latest release from the link below
repositories {
mavenCentral()
}
dependencies {
implementation("su.onno:onno-framework-starter:$onnoVersion")
}Replace
<latest>with the latest released version from Maven Central.
CI publishes from version tags via the vanniktech maven-publish
plugin. Push a tag named vX.Y.Z to publish artifacts with version X.Y.Z:
git tag v1.2.3
git push origin v1.2.3Release candidates work the same way — a vX.Y.Z-rc1 tag publishes X.Y.Z-rc1. The workflow also
supports manual dispatch with an explicit version. In both cases CI runs clean check, signs the
artifacts, and uploads a deployment to the Central Portal. Because gradle.properties sets
SONATYPE_AUTOMATIC_RELEASE=true, the deployment auto-releases once the Portal finishes
validation — no manual Publish click. A GitHub Release with generated notes is created too
(suffixed versions like X.Y.Z-rc1 are marked pre-release). Maven Central does not allow replacing a
released version, so only push a v* tag when you mean it.
Publishing requires these repository secrets (see the publish workflow):
| Secret | What it is |
|---|---|
MAVEN_CENTRAL_USERNAME / MAVEN_CENTRAL_PASSWORD |
Central Portal user token (Account → Generate User Token). |
SIGNING_IN_MEMORY_KEY |
ASCII-armored GPG private key (gpg --armor --export-secret-keys). |
SIGNING_IN_MEMORY_KEY_PASSWORD |
Passphrase for that GPG key. |
To publish locally to Maven Central instead, set the matching ORG_GRADLE_PROJECT_* properties and
run ./gradlew publishToMavenCentral. A bare ./gradlew publishToMavenLocal needs no signing (it is
skipped for -SNAPSHOT versions).
Inside this repository, settings.gradle.kts uses includeBuild("onno-desktop-gradle-plugin"), so a project in this build can apply:
plugins {
id("su.onno.desktop")
}External projects should either resolve the plugin from the published package repository or include the plugin build locally during development:
pluginManagement {
includeBuild("../onno-framework/onno-desktop-gradle-plugin")
repositories {
gradlePluginPortal()
mavenCentral()
}
}Each starter exposes its auto-configuration through Spring Boot 3's META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports mechanism. In a consuming app, adding the starter dependency is enough to make its conditional beans available.
Most integration starters are disabled by default and are enabled through onno.* configuration properties. See the module READMEs for integration-specific setup.
The database schema is derived from the metadata model (@Catalog, @Document, registers, …). At startup the framework diffs that model against the live database and brings it up to date — no migration files for structural changes.
onno.schema.mode=apply # apply | plan | validate | off (default: apply)
onno.schema.allow-destructive=false # gate for drops and narrowing type changes- apply (default) — execute safe changes: new tables/columns, renames, widening type
changes (e.g.
VARCHAR(100)→VARCHAR(200),INTEGER→BIGINT). Destructive changes (dropped tables/columns, narrowing types) are logged and skipped unlessonno.schema.allow-destructive=true. - plan — log the full migration plan with its SQL and change nothing. Review in CI, then apply.
- validate — fail startup if the database does not match the metadata or migrations are
unapplied. Use in production with
applyrunning as a deploy step. - off — schema is managed externally.
Every applied change-set is recorded in onno_schema_history together with a snapshot of the
metadata model; the snapshot is how type changes and removed entities are detected on later boots.
Renames keep data. Without a hint, a renamed field would look like “drop + add”. Declare the former name and the upgrader renames the column (or table) in place:
@Catalog(name = "Counterparties", previousNames = "Suppliers")
public class Counterparty extends CatalogObject {
@Attribute(length = 50, previousNames = "phone")
private String phoneNumber;
}Data migrations (backfills, reshaping, seeding) are versioned Java beans, run exactly once
per database — in version order, inside a transaction, recorded in onno_schema_history:
@Component
public class BackfillWarehouseCodes implements AppMigration {
public String version() { return "2026.06.001"; }
public void migrate(MigrationContext context) {
context.execute("UPDATE catalog_warehouses SET code = 'WH-' || _code WHERE code IS NULL");
}
}The framework follows an open-core model.
- The modules in this repository (published to Maven Central under the
su.onnogroup) are open source under the Apache License 2.0. SeeNOTICEfor attribution. - Separately licensed commercial modules (published under the
su.onno.enterprisegroup from a private repository — the vertical connectors Guesty and SES.HOSPEDAJES) are governed by the onno Commercial License and are not part of this distribution. Authentication, including OIDC / single sign-on, is part of the open-source core (onno-auth-starter).
The boundary, and the plan for extracting the commercial modules into their own repository, is documented in docs/licensing/MODULE-SPLIT-PLAN.md.