Build apps. Earn revenue.

Create Heaven plugins, publish to the marketplace, and keep 80% of every dollar.

Get started
Build

Ship a Heaven plugin with manifest, handlers, and templates. Standard (req, res, ctx) signature.

Publish

Submit to the marketplace. We review for quality, security, and compliance. Set your own pricing model.

Earn

You keep 80% of revenue. We handle billing, metering, and distribution. Monthly payouts.

Quickstart

1. App structure
my-app/
├── manifest.py      # Identity, events, pricing
├── plugin.py        # Heaven plugin class
├── handlers/        # (req, res, ctx) handlers
├── templates/       # Jinja2 templates
│   └── partials/    # HTMX partials
├── static/          # CSS (no build step)
├── schemas.py       # pytastic TypedDict schemas
└── events.py        # Event listeners
2. Manifest
# manifest.py
manifest = {
    "name": "Invoices",
    "publisher": "acme",
    "subdomain": "invoices",
    "version": "1.0.0",
    "requires": ["core.people"],
    "events": {
        "emits": ["ext.acme.invoices.invoice.created"],
        "listens": ["core.people.member.created"],
    },
}
3. Plugin
# plugin.py
from heaven import App

app = App()
app.GET("/", "handlers.index")
app.POST("/invoices", "handlers.create")
app.TEMPLATES("templates", relative_to=__file__)

class InvoicesPlugin:
    def install(self, host_app):
        sub = host_app.subdomain("invoices")
        sub.mount(app)
4. Event naming
Core apps
core.{app}.{resource}.{action}
e.g. core.people.member.created
Your apps
ext.{publisher}.{app}.{resource}.{action}
e.g. ext.acme.invoices.invoice.paid
Platform events
system.*
e.g. system.app.installed

Developer Contract

  • Ship as a Heaven plugin with manifest.py and an installable plugin class
  • Use the event bus for all cross-app communication — no direct database access
  • Namespace events: ext.{publisher}.{app}.{resource}.{action}
  • Server-rendered HTML via Jinja2 + HTMX — no client-side frameworks
  • No build step — no webpack, no bundler, no node_modules
  • Workspace isolation is automatic — ctx.workspace_id is set by the platform