Installable applications
An installable app is a Go package that returns a cicadel.App. It brings
routes, commands, components and, optionally, database migrations. You register
it in project/apps.go.
First-party packages are core defaults. They use the same public contracts as your own packages. The standard scaffolding registers accounts and formsets and supplies the migration and query-generation commands.
Good to know
Running go get on a third-party module does not activate it. An app is active
only after you register it in InstalledApps.
#How apps are composed
cicadel.New takes the list of apps and builds one Application:
- It validates each app: name, commands, checks, routes, middleware and manifest.
- It checks declared dependencies and orders apps so dependencies come first.
- It resolves each app’s typed settings.
- It builds the event dispatcher, component registry, static manifest and routes.
Any error stops startup.
#Ask your agent
Read https://cicadel.fauzaanu.com/en/0.2.8/topics/installable-apps/index.md.
Add [package and version] to my existing Cicadel application.
Inspect its constructor and required configuration, then register it in
project/apps.go without replacing existing apps. Check its dependencies and routes.
Review any database migrations before applying them. Run app checks and tests.#Install a published package
Use the module path and version the package author gives you:
go get example.com/your-org/greetings@v0.1.0example.com/your-org/greetings is a placeholder, not a published Cicadel
package. The next section shows how to create a local package with this API.
Import the package in project/apps.go and add its constructor to your app list:
package project
import (
"os"
greetings "example.com/your-org/greetings"
"github.com/fauzaanu/cicadel"
"github.com/fauzaanu/cicadel/contrib/formsets"
"gorm.io/gorm"
)
func InstalledApps(_ *gorm.DB) ([]cicadel.App, error) {
return []cicadel.App{
formsets.App(),
greetings.New(greetings.Options{
Greeting: "Welcome",
Output: os.Stdout,
}),
}, nil
}- Keep the standard
formsets.App()entry and your other apps. - You do not list accounts here. The project builder composes it.
- Database-backed factories can use the supplied
*gorm.DB. Follow the package’s constructor signature.
#Create a local package
From an existing Cicadel project, scaffold a separate module beside it:
go tool cicadel startpackage ../greetings example.com/your-org/greetings greetings
cd ../greetings
go get github.com/fauzaanu/cicadel@main
go mod tidy
go test -race ./...
cd ../myapp
go mod edit -replace=example.com/your-org/greetings=../greetings
go get example.com/your-org/greetings@v0.1.0Register it as shown above, then run:
go run manage.go apps
go run manage.go check
go run manage.go greetings:hello
# Welcome
go run manage.go runserverGET /greetings/ returns {"greeting":"Welcome"}. The scaffold includes this
route, a command, typed options and tests.
Tip
Need an app inside one project only? Use startapp notes instead.
#What a package returns
This small app exports one named route:
package hello
import (
"net/http"
"github.com/fauzaanu/cicadel"
"github.com/fauzaanu/cicadel/response"
"github.com/fauzaanu/cicadel/urls"
)
func New() cicadel.App {
return cicadel.App{
Name: "hello",
Manifest: &cicadel.Manifest{
Module: "example.com/hello",
Version: "v0.1.0",
API: cicadel.AppAPIVersion,
},
Routes: []urls.Route{
urls.Path("GET", "/hello/", "hello:index",
func(w http.ResponseWriter, r *http.Request) {
if err := response.JSON(w, http.StatusOK, map[string]string{"message": "Hello"}); err != nil {
response.Error(w, http.StatusInternalServerError)
}
}),
},
}
}Nameowns the route and command namespace. Keep it stable across upgrades, especially when the app owns migration history.- Go selects the module version.
Manifest.Versionis the package’s compatibility metadata.
#Declare settings
- Give the package a typed
Optionsstruct. - Tag the fields a deployment may override with
setting:"NAME"orsetting:"NAME,secret". - Return
Settings: settings.NewPackage(options)on the app.
The project passes values in Go. CICADEL_<APP>_<NAME> overrides them.
manage.go apps lists them with secrets redacted, and manage.go check reports
invalid keys. The startpackage scaffold does this for its greeting. See
package settings.
#Emit and handle events
Events let apps react to each other without direct calls.
- Declare each event with
events.New[Payload]("<app name>", "name"). - List it in
App.Eventsand callEvent.Emit(ctx, payload). - Subscribe to other apps’ events with
Event.Handle(fn)inApp.Subscriptions.
cicadel.New rejects subscriptions to events no installed app declares.
Handlers run synchronously in dependency order. The startpackage scaffold emits
a greeted event. See events.
#Publish static files
Put public CSS, images and scripts in an embedded directory and return it as
App.Static.
- The host serves each file at
/static/<app name>/<path>with a content-hashed URL. go run manage.go collectstatic DIRcollects them.- Resolve URLs in handlers with
static.URL(r.Context(), "greetings", "site.css").
Do not add your own asset routes. App.Resources stays private. The
startpackage scaffold publishes assets/static/. See
static files.
#Add migrations
For a package with PostgreSQL tables, embed recorded SQL migrations:
//go:embed migrations/*.sql
var migrations embed.FS
func migrationFiles() (fs.FS, error) {
return fs.Sub(migrations, "migrations")
}- Import
embedandio/fs. - Use the returned filesystem as
App.Migrations. Handle its error in your factory. - Set
Manifest.Providersto[]string{"postgres"}. - Run the host’s
go run manage.go migratebefore serving requests.
Follow the migration guide for generated model migrations.
#Depend on another app
If your package needs another installed app, declare it in Manifest.Requires:
Requires: []cicadel.Requirement{
{Name: "accounts", Version: cicadel.VersionRange{
Min: "v1.0.0", Before: "v2.0.0",
}},
},These bounds are an example. They do not claim an accounts v1 release exists. The host must register the dependency too. Missing or incompatible dependencies fail startup checks.
#Remove or disable an app
To remove an app:
- Delete its constructor from
InstalledApps. - Remove unused imports.
- Run
go mod tidy.
To disable an app, omit its registration but keep its Go module available.
Either way, its routes, commands, static files and owned resources disappear from the application. Existing PostgreSQL data and recorded migration history remain. Deleting data is a separate, deliberate operation.
Warning
Restore the registration before running migrations. The migration runner rejects an incomplete catalog that contains previously applied package migrations.
Re-enabling the package restores its surfaces and lets its recorded history be
checked again. TestInstallUpgradeRemoveExternalPackage exercises this sequence
after a version upgrade, before removing the module entirely.
#External translation helpers
Localization is a third-party responsibility. An installable can:
- supply request middleware through
App.Middleware; - store its language selection under an unexported context key;
- expose a plain-text helper that accepts
context.Context.
Render the result with normal .templ text expressions or ui.Text. Translated
strings must not become trusted HTML because they came from a catalog. Keep the
language selection in the request context, not in package globals, so concurrent
requests cannot change each other’s output.
App middleware wraps only that app’s routes. It does not translate host routes or other installed apps. Hosts that want translated components on their own routes must compose the extension’s middleware there. The extension decides fallback languages, missing-key handling and catalog ownership. Cicadel does not prescribe translation catalogs or language negotiation.
internal/scaffold/translation_test.go checks this boundary. It installs a
separately versioned module through a local Go proxy into a generated consumer.
It covers Unicode, fallback text, escaped untrusted keys, concurrent requests,
isolation from host routes, unregistration, and go mod tidy removal followed by
a rebuild. Its tiny two-language lookup is test data, not a production
translation implementation.
#Related
- Component exports: component libraries.
- Package lifecycle and compatibility: the package authoring reference.