Plugins
Plugin registration
Declare a plugin: its ID, the party.Plugin lifecycle, the pact capability interfaces it opts into, its embedded files and the scaffolded layout.
On this page
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:
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 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. |
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.ConfigFSreturns a tree withconfig/config.yaml. Its keys become<plugin id>.<key>, and any otherconfig/<name>.yamlbecomes<plugin id>.<name>.<key>. The application's ownconfig/directory andSUMMER_variables override them.pact.HasLang.LangFSreturnslang/<locale>/<group>.yamlfiles.pact.HasMailTemplates.MailTemplatesFSreturns theviews/mailtemplates, andpact.HasMailTemplates.MailTemplateslists 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:
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.