Getting started
App Manifest
Every Saaslivery app is defined by a manifest.py file. The manifest declares the app's identity, dependencies, events, and pricing. The platform uses it for installation, dependency resolution, marketplace listing, and runtime enforcement.
Full example
manifest = {
# Required
"name": "Invoices",
"slug": "invoices",
"publisher": "acme",
"version": "1.0.0",
"description": "Invoice management and payment tracking",
# Dependencies
"requires": ["core.people"],
"enhances": ["core.calendar"],
# Events
"events": {
"emits": [
"ext.acme.invoices.invoice.created",
"ext.acme.invoices.invoice.paid",
],
"listens": [
"core.people.member.created",
"core.people.member.deactivated",
],
},
# Entry point
"plugin": "plugins.acme_invoices.plugin.InvoicesPlugin",
# PWA
"pwa": {
"theme_color": "#1a1a2e",
"background_color": "#ffffff",
},
# Pricing
"pricing": {
"model": "usage",
"trial_days": 14,
"dimensions": [
{
"type": "seat",
"label": "User access",
"unit_price": 4.00,
"currency": "USD",
"period": "month",
"free_allowance": 5,
},
{
"type": "document",
"label": "Invoices created",
"unit_price": 0.50,
"currency": "USD",
"free_allowance": 10,
},
],
},
# Marketplace
"category": "finance",
"tags": ["invoicing", "billing", "payments"],
"homepage": "https://acme.dev/invoices",
"support": "https://acme.dev/support",
}
Required fields
| Field |
Type |
Description |
name |
str |
Human-readable name. Max 50 characters. |
slug |
str |
URL-safe identifier; becomes the /app/{slug}/ path prefix and the API namespace. Lowercase, alphanumeric + hyphens. Globally unique. |
publisher |
str |
Publisher ID. microcessor is reserved for core apps. |
version |
str |
Semantic versioning: MAJOR.MINOR.PATCH |
description |
str |
One-line description. Max 200 characters. |
plugin |
str |
Dotted Python path to the plugin class. |
Slug rules
- Lowercase alphanumeric and hyphens only:
[a-z0-9-]+
- Must start with a letter:
^[a-z]
- Max 30 characters
- Cannot collide with reserved platform names, including
www, api, apps, admin, auth, mail, my, rtc, mx, and saaslivery
- Cannot collide with an existing app's slug
Dependencies
| Field |
Type |
Description |
requires |
list[str] |
Hard dependencies — installation fails without them. Format: core.{slug} or ext.{publisher}.{slug} |
enhances |
list[str] |
Soft dependencies — app works without them but gains features when present |
Checking soft dependencies at runtime
if req.app.has_app("core.calendar"):
await req.app.emit("ext.acme.invoices.calendar.event_requested", {...})
Events
| Field |
Type |
Description |
events.emits |
list[str] |
Events this app will emit. Enforced at runtime — undeclared events are rejected. |
events.listens |
list[str] |
Events this app subscribes to. Used for dependency resolution. |
Naming convention
- Core apps:
core.{app}.{resource}.{action}
- Third-party:
ext.{publisher}.{app}.{resource}.{action}
See Event System for the full spec.
PWA
| Field |
Type |
Description |
pwa.theme_color |
str |
Hex color for the PWA theme |
pwa.background_color |
str |
Hex color for the PWA splash screen |
The platform auto-generates the PWA manifest.json for your app from these values.
Pricing
| Field |
Type |
Description |
pricing.model |
str |
"free" or "usage" |
pricing.trial_days |
int |
Optional free trial period in days |
pricing.dimensions |
list[dict] |
Billing dimensions (see below) |
Dimension schema
| Field |
Type |
Required |
Description |
type |
str |
yes |
seat, minute, document, storage, message, active_resource, or flat |
label |
str |
yes |
Shown on invoices |
description |
str |
no |
What counts as a unit |
unit_price |
float |
yes |
Price per unit |
currency |
str |
yes |
ISO 4217 code (e.g., USD) |
period |
str |
for recurring |
month or year — required for seat, storage, active_resource, flat |
free_allowance |
int/float |
no |
Units included free per workspace per period |
Free app shorthand
"pricing": "free"
See Pricing & Billing for the full billing spec.
Marketplace fields
| Field |
Type |
Description |
category |
str |
productivity, communication, finance, hr, development, analytics, or other |
tags |
list[str] |
Searchable tags. Max 10. |
homepage |
str |
Publisher's homepage for this app |
support |
str |
Support URL or email |
Search integration
Apps can declare searchable types for the MicroSearch command palette:
"searchables": [
{"type": "task", "label": "Tasks", "icon": "assignment", "color": "#F39C12"},
{"type": "board", "label": "Boards", "icon": "view_kanban", "color": "#3B82F6"},
]
Each searchable requires a matching search API endpoint.
Core app manifest example
manifest = {
"name": "People",
"slug": "people",
"publisher": "microcessor",
"version": "1.0.0",
"description": "Shared directory, org chart, and identity provider",
"requires": [],
"enhances": [],
"events": {
"emits": [
"core.people.member.created",
"core.people.member.updated",
"core.people.member.deactivated",
],
"listens": [],
},
"category": "hr",
"tags": ["directory", "org-chart", "identity"],
"pricing": "free",
}
Plugin class contract
class MyPlugin:
def install(self, host_app):
"""Called during installation. Mount the app and register listeners."""
host_app.mount(self.app, isolated=False)
host_app.on("core.people.member.created", self.on_member_created)
def uninstall(self, host_app): # optional
"""Called during removal. Clean up listeners and resources."""
def upgrade(self, host_app, from_version): # optional
"""Called when upgrading. Run migrations."""
Route registration itself lives in plugin.py on the module-level app object: UI routes on the wildcard subdomain under /app/{slug}/, API routes on the api subdomain. See Architecture & routing.
Version rules
| Change |
Bump |
| Breaking changes to event payloads |
Major |
| New events or payload fields |
Minor |
| Bug fixes |
Patch |