Files
summercms/docs/setup/coming-from-wintercms.md
Jakub Zych 44bd1446f5 feat(11.1-04): add the Backend section, the remaining Services pages and the concept map links
- docs/backend: admin controllers, forms, lists and filters, relation
  manager, users and permissions, settings, partials and widgets, admin SPA
- docs/services: storage, outbound HTTP, realtime, Web Push, search, parity
  testing and the Frontend and AJAX (not provided) page
- Examples for cabana (with testdata/docs YAML), fetchguard, lighthouse and
  its centrifugo driver, flare, beachcomber and typesense, tide; lighthouse
  and beachcomber TestDocs* regions run on their Postgres harnesses
- concept map rows link their guide pages and the not-provided rows the
  Frontend and AJAX page; index lists Backend, Database and Services
- TestDocsRequiredPages asserts the D-08 section order
2026-09-30 23:18:35 +02:00

9.9 KiB

title, description, section, order
title description section order
Coming from WinterCMS Map WinterCMS plugins, models, backend controllers, routes, events and commands to their SummerCMS equivalents, and see what is not provided. setup 40

Coming from WinterCMS

SummerCMS keeps the parts of WinterCMS that make a plugin developer productive. Plugins still declare what they add to the application and extend each other through events and shared services. Backend lists and forms are still described in fields.yaml and columns.yaml. Models, migrations, console commands and scaffolding keep their WinterCMS shape, so a plugin ports file by file.

What changes is everything that depends on PHP at runtime. Plugins are Go packages compiled into one binary, so there is no plugin directory scanned at boot and no runtime autoloading. Magic methods, dynamic properties and behaviours give way to Go interfaces and composition. SummerCMS is headless: it serves a JSON API and the admin SPA, and the frontend is a separate application that calls that API.

Concept map

Each row names the WinterCMS concept, the SummerCMS identifiers that replace it, and where to read more: the guide page first, then the module reference.

WinterCMS SummerCMS Where
Plugin.php with pluginDetails, register and boot A type implementing party.Plugin (party.Plugin.ID, party.Plugin.Requires, party.Plugin.Register, party.Plugin.Boot), registered from init with party.Register Plugin registration, party
$require plugin dependencies party.Plugin.Requires; party.Activate orders plugins so each comes after the ones it requires Plugin registration, party
registerPermissions, registerNavigation pact.HasPermissions returning pact.Permission values, pact.HasNavigation returning pact.NavigationItem values Users and permissions, pact
version.yaml and the updates/ directory pact.HasMigrations returning an ordered gormigrate set; lagoon.Migrate runs every plugin's set and lagoon.RollbackLast undoes the last one Migrations, lagoon
Eloquent models GORM structs with lagoon helpers: lagoon.Fill for mass assignment, lagoon.Validate for rules, lagoon.Jsonable for JSON columns, lagoon.Page for pagination Models, Casts and validation, lagoon
fields.yaml and columns.yaml The same YAML, embedded in the plugin through pact.AdminAssets and compiled at boot into a cabana.CompiledController Forms, Lists and filters, cabana
Backend controllers with the Form, List and Relation behaviours A pact.AdminController returned from pact.HasAdminControllers; the generic admin API replaces the behaviours, and hooks such as pact.FormBeforeCreate and pact.ListExtendQuery replace behaviour overrides Admin controllers, cabana
routes.php pact.HasRoutes, declaring routes on a pact.Router with groups, middleware names and pact.Router.Where constraints Routing, surf
Route middleware Named middleware of type pact.Middleware, registered through pact.HasMiddleware Routing, surf
config/*.php, .env and Config::get compass.Config with per-environment directories and SUMMER_ overrides; plugin defaults through pact.HasConfig Configuration, compass
lang/ files and Lang::get pact.HasLang for plugin catalogs, read through phrasebook.Translator.Get and phrasebook.Translator.Choice Localization, phrasebook
Event::listen and Event::fire festival.Bus.Listen and festival.Bus.Fire on backpack.App.Events, keyed by the event's Go type Events, festival
App::make and singleton bindings backpack.App.Publish and backpack.App.Lookup, keyed by type Extending plugins, backpack
Artisan commands and registerConsoleCommand bonfire.Command values returned from pact.HasCommands Writing commands, bonfire
Queued jobs pact.HasJobs with jobs built by conga.Job, dispatched with conga.Manager.Dispatch inside the caller's transaction Queued jobs, conga
registerSchedule and the scheduler pact.HasSchedule returning pact.ScheduledCommand entries with a pact.Cadence Task scheduling, pact
Mail templates in views/mail pact.HasMailTemplates, sent through postcard.Mailer Mail, postcard
Settings models and registerSettings pact.HasSettings returning pact.SettingsItem entries Settings, cabana
Laravel broadcasting lighthouse.Publisher drivers and models that implement lighthouse.Broadcastable Realtime, lighthouse
Laravel Scout search Models that implement beachcomber.Searchable, synced after commit Search, beachcomber
The Laravel HTTP client fetchguard.Fetch with a fetchguard.Policy that blocks private addresses and limits size and time Outbound HTTP, fetchguard

What is not provided

SummerCMS does not port the WinterCMS frontend or the PHP helpers that Go already covers. Do not look for these when you port a plugin:

WinterCMS SummerCMS
CMS pages, themes, layouts and partials Not provided. The frontend is a separate application that calls the JSON API; see Frontend and AJAX.
Components Not provided. Write an HTTP handler and declare its route through pact.HasRoutes; see Frontend and AJAX.
The AJAX framework and Snowboard Not provided. The frontend calls the JSON API and subscribes to realtime channels; see Frontend and AJAX.
The media manager Not provided. Store uploads as model attachments; see Attachments.
Import and export in backend lists Not provided.
Record sorting (the Reorder behaviour) Not provided.
Collections Not provided. Use Go slices and the slices and maps packages.
Behaviours and dynamic class extension Not provided. Use Go interfaces and composition.
Cache Not provided. Use the Go standard library or a service another plugin publishes.
Session Not provided. The API is stateless and authenticates with tokens; see Frontend and AJAX.

Plugin.php in Go

A WinterCMS plugin registration class for Acme\Blog looks like this:

<?php namespace Acme\Blog;

use System\Classes\PluginBase;

class Plugin extends PluginBase
{
    public $require = ['Acme.User'];

    public function pluginDetails()
    {
        return ['name' => 'Blog', 'author' => 'Acme'];
    }

    public function registerPermissions()
    {
        return [
            'acme.blog.access_posts' => ['tab' => 'Blog', 'label' => 'Manage posts'],
        ];
    }
}

The SummerCMS plugin is a Go type. The four party.Plugin methods replace the plugin details, $require, register and boot, and each extra capability is one more interface, here pact.HasPermissions:

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 type satisfies party.Plugin, which this example checks every time go test ./... runs:

var p party.Plugin = &BlogPlugin{}
fmt.Println(p.ID())
// Output: acme.blog

Plugin IDs are lower case in vendor.plugin form, so Acme.User becomes acme.user. The application does not scan for plugins: it lists their IDs in its summer.yaml manifest, and summer build compiles them in. See Installation to build your first application.