Files
summercms/docs/plugins/registration.md
Jakub Zych 1f8f5e1b51 feat(11.1-03): add the Architecture and Plugins docs sections
- architecture: introduction, Go modules and workspaces, application
  lifecycle, request lifecycle
- plugins: registration, scheduling, extending, testing
- verified Examples for backpack services, towel request context,
  pact schedules and festival events
- TestDocsRequiredPages lists the eight new pages
2026-09-30 22:12:08 +02:00

119 lines
6.5 KiB
Markdown

---
title: Plugin registration
description: "Declare a plugin: its ID, the party.Plugin lifecycle, the pact capability interfaces it opts into, its embedded files and the scaffolded layout."
section: plugins
order: 10
---
# Plugin registration
Plugins are the foundation of every SummerCMS application. A plugin adds models, routes, admin screens, console commands, jobs and translations, and it can extend other plugins. This page covers how a plugin tells the framework what it contributes.
## Plugin identifiers
Every plugin has an ID in `vendor.plugin` form: two lower-case parts, each starting with a letter and containing only letters and digits, such as `acme.blog`. The ID is how the manifest lists the plugin, how other plugins require it and how its configuration is namespaced (`acme.blog.posts_per_page`). WinterCMS writes the same identifier as `Acme.Blog`; in SummerCMS it is always lower case.
The scaffolder derives the package and directory name from the second part, so `summer make:plugin acme.blog` creates `plugins/blog` with `package blog`.
## The plugin type
A plugin is a Go type that implements `party.Plugin`. Its package registers it from `init` with `party.Register`, so importing the package is enough to make the plugin available; the generated `plugins.gen.go` does that import for every plugin in `summer.yaml`.
| Method | Purpose |
|--------|---------|
| `party.Plugin.ID` | Returns the plugin ID. |
| `party.Plugin.Requires` | Lists the IDs of plugins that must register and boot before this one, like `$require` in WinterCMS. |
| `party.Plugin.Register` | Runs before any plugin boots. Publish services on the container here. |
| `party.Plugin.Boot` | Runs after every plugin registered. Listen to events and use other plugins' services here. |
Here is a complete plugin that also declares a backend permission:
```go src=modules/party/example_plugin_test.go
package party_test
import (
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/pact"
)
// BlogPlugin is the acme.blog plugin: the Go form of a WinterCMS Plugin.php.
// A real plugin package also registers it from init with
// party.Register(&BlogPlugin{}).
type BlogPlugin struct{}
// The optional capabilities the plugin opts into, checked at compile time.
var _ pact.HasPermissions = (*BlogPlugin)(nil)
// ID is the plugin identifier in vendor.plugin form.
func (p *BlogPlugin) ID() string { return "acme.blog" }
// Requires lists the plugins that must register and boot first ($require).
func (p *BlogPlugin) Requires() []string { return []string{"acme.user"} }
// Register runs before any plugin boots: publish services here.
func (p *BlogPlugin) Register(app *backpack.App) error { return nil }
// Boot runs after every plugin registered: listen to events and look up
// services other plugins published.
func (p *BlogPlugin) Boot(app *backpack.App) error { return nil }
// Permissions replaces registerPermissions().
func (p *BlogPlugin) Permissions() []pact.Permission {
return []pact.Permission{
{Code: "acme.blog.access_posts", Tab: "Blog", Label: "Manage posts"},
}
}
```
The `var _ pact.HasPermissions = (*BlogPlugin)(nil)` line is a compile-time check: if a method is missing or has the wrong signature, the build fails instead of the capability being silently ignored. Add one such line for every capability your plugin implements.
## Capability interfaces
A WinterCMS plugin overrides `register*` methods of `PluginBase`. A SummerCMS plugin implements small interfaces from [pact](../../modules/pact/README.md) instead, and the framework discovers each one with a type assertion.
| Interface | Contributes |
|-----------|-------------|
| `pact.HasRoutes` | HTTP routes, declared on a `pact.Router`. |
| `pact.HasMiddleware`, `pact.HasMiddlewareFactories` | Named and parameterized route middleware. |
| `pact.HasConfig` | Default configuration, merged under the plugin ID. |
| `pact.HasMigrations` | An ordered set of database migrations. |
| `pact.HasCommands` | Console commands for the application binary. |
| `pact.HasJobs` | Background jobs. |
| `pact.HasSchedule` | Console commands that run on a schedule; see [Scheduling](scheduling.md). |
| `pact.HasLang`, `pact.HasLangOverrides` | Translations, and overrides of other namespaces. |
| `pact.HasMailTemplates` | Mail templates and layouts. |
| `pact.HasPermissions`, `pact.HasNavigation`, `pact.HasSettings` | Backend permissions, navigation and settings screens. |
| `pact.HasAdminControllers` | Admin controllers built from `fields.yaml` and `columns.yaml`. |
| `pact.HasModels` | The plugin's GORM models. No framework package reads it yet. |
## Embedded files
Configuration defaults, translations and mail templates ship inside the binary through Go's `embed` package. The plugin returns an `fs.FS` for each:
- `pact.HasConfig.ConfigFS` returns a tree with `config/config.yaml`. Its keys become `<plugin id>.<key>`, and any other `config/<name>.yaml` becomes `<plugin id>.<name>.<key>`. The application's own `config/` directory and `SUMMER_` variables override them.
- `pact.HasLang.LangFS` returns `lang/<locale>/<group>.yaml` files.
- `pact.HasMailTemplates.MailTemplatesFS` returns the `views/mail` templates, and `pact.HasMailTemplates.MailTemplates` lists their names.
## The scaffolded layout
`summer make:plugin acme.blog` writes a plugin that compiles and follows the WinterCMS directory layout, with each directory as a Go subpackage:
```text
plugins/blog/
├── go.mod the plugin module, requiring the framework
├── plugin.go the Plugin type, its capabilities and init registration
├── routes.go the Routes method
├── registry.gen.go generated lists of models, migrations, commands, jobs and admin controllers
├── classes/ services and hooks
├── config/config.yaml default configuration
├── console/ console commands
├── controllers/ HTTP handlers and admin controllers
├── jobs/ background jobs
├── lang/en/lang.yaml translations
├── middleware/ named middleware
├── models/ GORM models
├── updates/ migrations
└── views/mail/ mail templates
```
The `make:` commands, such as `summer make:model acme.blog Post`, add files to these directories and regenerate `registry.gen.go`, so you do not edit that file by hand. The capability methods in `plugin.go` return the generated lists, so a new model, migration, command, job or admin controller is picked up without editing the plugin type.