cicadel

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

How cicadel.New composes installed appsEach installed cicadel.App contributes routes and middleware, models and migrations, commands and checks, settings, static files and components, and events and subscriptions. cicadel.New validates the apps and their manifests, orders them by dependency, resolves package settings, and builds the event dispatcher, component registry, static manifest and routes. The result is one Application with a router, commands, events, static files and components.cicadel.AppRoutes · MiddlewareModels · MigrationsCommands · ChecksSettingsStatic · ComponentsEventsSubscriptionsone per installed appcicadel.New1 validate apps, manifestsand dependencies2 order by dependency3 resolve settings4 build events, components,static files and routesany error stops startupApplicationRouterCommandsEvent dispatcherStatic manifestComponent registrySettings

cicadel.New takes the list of apps and builds one Application:

  1. It validates each app: name, commands, checks, routes, middleware and manifest.
  2. It checks declared dependencies and orders apps so dependencies come first.
  3. It resolves each app’s typed settings.
  4. It builds the event dispatcher, component registry, static manifest and routes.

Any error stops startup.

#Ask your agent

Text
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:

Terminal
go get example.com/your-org/greetings@v0.1.0

example.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:

Go
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:

Terminal
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.0

Register it as shown above, then run:

Terminal
go run manage.go apps
go run manage.go check
go run manage.go greetings:hello
# Welcome
go run manage.go runserver

GET /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:

Go
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)
                    }
                }),
        },
    }
}
  • Name owns the route and command namespace. Keep it stable across upgrades, especially when the app owns migration history.
  • Go selects the module version. Manifest.Version is the package’s compatibility metadata.

#Declare settings

  1. Give the package a typed Options struct.
  2. Tag the fields a deployment may override with setting:"NAME" or setting:"NAME,secret".
  3. 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.Events and call Event.Emit(ctx, payload).
  • Subscribe to other apps’ events with Event.Handle(fn) in App.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 DIR collects 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
//go:embed migrations/*.sql
var migrations embed.FS

func migrationFiles() (fs.FS, error) {
    return fs.Sub(migrations, "migrations")
}
  1. Import embed and io/fs.
  2. Use the returned filesystem as App.Migrations. Handle its error in your factory.
  3. Set Manifest.Providers to []string{"postgres"}.
  4. Run the host’s go run manage.go migrate before 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:

Go
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:

  1. Delete its constructor from InstalledApps.
  2. Remove unused imports.
  3. 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.

Type to search this version.