Installation
Orbit lives in its own package — almasix-orbit — so you can add an admin panel without dragging it into every Almasix app by default.
Requirements
Section titled “Requirements”- Almasix
almasix-conduitalmasix-permission(for the default auth/permission middleware story)
Install
Section titled “Install”pip install almasix-orbitsmith orbit:installThat command:
- Publishes Orbit CSS/JS and writes
config/orbit.py - Creates the default panel package at
app/orbit/admin/(id/path overridable with--panel/--path) - Writes a thin
app/providers/orbit_panel_provider.pywhose only job is discovery - Lists that provider in
config/app.py
After install, open /admin (or whatever --path you chose). Restart smith serve if the process was already running.
Layout: panels vs provider
Section titled “Layout: panels vs provider”Orbit expects this split (v0.3+):
| Path | Responsibility |
|---|---|
app/orbit/{id}/panel.py |
Define the panel — brand, path, resources, pages, plugins. Export register_{id}_panel(registry). |
app/orbit/{id}/resources|pages|widgets|themes/ |
Components owned by that panel. Resources/pages/widgets + theme CSS via .discover_panel_dirs(). |
app/orbit/shared/ |
Shared code (fields/, resources/, …) — never auto-discovered; import and register explicitly. |
app/providers/orbit_panel_provider.py |
Register panels — call register_app_orbit_panels(...). Do not put Panel.make(...) here. |
config/app.py |
List OrbitPanelProvider under providers so discovery runs on boot. |
app/ orbit/ __init__.py admin/ panel.py ← Panel.make("admin") … resources/ pages/ widgets/ themes/ ← *.css inlined by discover_panel_dirs app/ ← optional second panel panel.py resources/ shared/ ← optional; explicit register only fields/ ← make:orbit-field writes here resources/ providers/ orbit_panel_provider.py ← thin: discover onlyPanel file (where you configure)
Section titled “Panel file (where you configure)”from almasix.orbit import Panel, PanelRegistry
def register_admin_panel(registry: PanelRegistry) -> Panel: panel = ( Panel.make("admin") .path("admin") .brand_name("Orbit") .navigation_layout("apps") .login() .discover_panel_dirs() ) registry.register(panel) return panelProvider (discovery only)
Section titled “Provider (discovery only)”from almasix.orbit import PanelRegistryfrom almasix.orbit.panels.discover import register_app_orbit_panelsfrom almasix.providers import ServiceProvider
class OrbitPanelProvider(ServiceProvider): def boot(self) -> None: register_app_orbit_panels(self.app.make(PanelRegistry))config/app.py
Section titled “config/app.py”"providers": [ "app.providers.app_service_provider.AppServiceProvider", "app.providers.orbit_panel_provider.OrbitPanelProvider",],OrbitServiceProvider (package entry-point) mounts whatever is already in PanelRegistry. Your app provider is what fills the registry from app/orbit.
Discovery rules
Section titled “Discovery rules”register_app_orbit_panels loads every app.orbit.<id>.panel module and calls register_{id}_panel(registry):
| Path | Function called |
|---|---|
app/orbit/admin/panel.py |
register_admin_panel |
app/orbit/app/panel.py |
register_app_panel |
app/orbit/shop/panel.py |
register_shop_panel |
Orbit 0.4 dropped app/orbit/{id}_panel.py. Panels live at app/orbit/{id}/panel.py only.
.discover_panel_dirs() (emitted by scaffolding) points discovery at {panel_pkg}.resources / .pages / .widgets and loads *.css from {panel_pkg}.themes. You can still call .resources([...]) explicitly or pass custom paths to .discover_resources(...).
Plugins are never auto-discovered — only .plugin(...) / .plugins([...]) in panel.py.
Cross-panel resources: put the class under app/orbit/shared/resources/ (or anywhere) and register it explicitly on each panel that needs it (.resources([SharedPostResource])). Autodiscovery only covers the panel’s own package tree.
Add another panel
Section titled “Add another panel”smith make:orbit-panel app --path=app # alias: smith orbit:panel …That writes app/orbit/app/panel.py plus empty component dirs. The thin provider already discovers it — no provider edit. Restart smith serve, then open /app.
Other generators (panel-scoped):
smith make:orbit-resource Post --panel=adminsmith make:orbit-resource Post --panel=admin --model=Post --generatesmith make:orbit-page Settings --panel=adminsmith make:orbit-widget StatsOverview --panel=adminsmith make:orbit-field MoneyInput # app/orbit/shared/fields/smith make:orbit-plugin AuditLog --vendor=acme --author=jane # third-party package + listing YAML--generate inspects the model’s database columns (when the table exists) and stubs matching form fields and table columns for you.
With more than one panel, omit --panel in an interactive terminal and Smith asks which panel to use. Under --no-interaction / CI it picks admin when present, otherwise the first panel id.
Troubleshooting 404s
Section titled “Troubleshooting 404s”If /admin or /app 404s:
config/app.pylistsOrbitPanelProviderapp/orbit/{id}/panel.pyexists and definesregister_{id}_panel- You restarted after scaffolding
Panels mount on a path prefix only — existing host routes (for example /) stay put unless you set .path("/").
Migrating from 0.2.x
Section titled “Migrating from 0.2.x”- Move
admin_panel.py→admin/panel.py(keepregister_admin_panel) - Move panel-owned resources/pages/widgets under
admin/{resources,pages,widgets}/ - Put truly shared classes under
shared/and register them explicitly - Add
.discover_panel_dirs()(or rely on auto-wire when discover paths are empty) - Keep the thin provider; restart
Imports
Section titled “Imports”The meta-package pulls panels plus orbit siblings (forms, tables, actions, …). Import paths always start with almasix.orbit:
from almasix.orbit import Panel, PanelRegistry, Resource, Pagefrom almasix.orbit.forms import Form, TextInputfrom almasix.orbit.tables import Table, TextColumnSource: almasix-dev/almasix-orbit.
Optional assets
Section titled “Optional assets”Want a local copy of the CSS/JS to poke at?
smith vendor:publish --tag=orbit-assetsThat drops public/vendor/orbit/orbit.css and orbit.js. The panel shell already links those paths — publish when you’re ready to vendor or tweak. orbit:install can run this for you.
Prism layouts that host Orbit UI can also use:
@orbitStyles@orbitScriptsThose directives set __orbit_styles / __orbit_scripts in the Prism context when the engine is bound.
Defaults
Section titled “Defaults”On register, the provider seeds in-memory config if nothing is set yet:
{ "path": "/admin", "font": "Outfit", "brand": "Orbit",}Tune brand, path, and colors on the Panel instance in app/orbit/{id}/panel.py — see Panel configuration. Interactive pages are Conduit hosts; SDUI forms/tables paint into those hosts.
Next up: Quick start.