2026-10-09 18:45:14 +02:00
2026-10-06 22:32:36 +02:00
2026-10-09 10:35:16 +02:00

SummerCMS (Go)

SummerCMS is a content management framework for Go, inspired by WinterCMS. An application built on it compiles into a single binary: the framework modules, the application's plugins, the embedded admin SPA and the console commands all ship as one executable.

Key concepts

  • Compiled plugins. A plugin is a Go module that implements the capability interfaces in pact. The summer build command reads the application's summer.yaml manifest, generates the plugin import list and builds the binary. Plugins are registered at build time and never loaded at runtime. party orders them by dependency and runs their register and boot lifecycle.
  • YAML-driven admin. Plugins describe admin lists, forms, filters and relations in WinterCMS-style YAML (columns.yaml, fields.yaml). cabana compiles those definitions at boot and serves them as a JSON admin API, and boardwalk serves the embedded Vue admin SPA that renders them.
  • Headless API. Plugins declare HTTP routes and middleware. surf assembles them onto a standard library net/http ServeMux, and wire keeps JSON responses byte-compatible with a PHP (WinterCMS/Laravel) backend.
  • Scaffolding CLI. The summer tool creates, builds and watches applications and scaffolds plugins, models, migrations, console commands, jobs and admin controllers. Every application binary also carries runtime commands such as migrate, serve and route:list.

Requirements

  • Go 1.27.
  • PostgreSQL 15 or newer for any application that uses the data layer (lagoon).
  • Node.js 22.6 or newer, only when working on the admin SPA in admin/.
  • Docker, to run the PostgreSQL 16 database of an application created by summer new (its compose.yaml), and for the integration tests that start PostgreSQL or Mailpit containers through testcontainers-go.

Quick start

Create an application with one command. The script checks for Go 1.27 and Docker, clones the framework at its latest release tag into ./summercms, installs the summer CLI and runs summer new demo:

curl -fsSL https://summercms.io/new | bash -s -- demo
cd sm-demo-app
docker compose up -d --wait   # PostgreSQL 16 on 127.0.0.1:54320
summer build
summer migrate
summer serve                  # then: curl http://127.0.0.1:8080/

The sunrise module holds the script and documents its SUMMERCMS_REPO, SUMMERCMS_DIR and SUMMERCMS_REF overrides. Later, curl -fsSL https://summercms.io/update | bash run inside the application moves the framework checkout forward, reinstalls summer and rebuilds that application.

With a framework checkout already in place, install the CLI from it and run summer new in the directory next to the checkout; it finds a sibling summercms or summercms.go checkout, or takes --framework <path>:

go -C summercms install ./cmd/summer
summer new demo

The example application

examples/hello is a small application with three compiled plugins. Install the summer CLI from the repository root, then build the example:

go install ./cmd/summer
cd examples/hello
summer build          # generates plugins.gen.go and main.go, writes bin/hello
./bin/hello --help
./bin/hello greeter:hello

greeter:hello prints the example's layered configuration and needs no database. key:generate also runs without a database.

The migrate, migrate:status, migrate:rollback, serve and admin commands open PostgreSQL, so they need more configuration. Configuration comes from YAML files in config/, and any key can be overridden with a SUMMER_-prefixed environment variable in which a double underscore separates path segments (see compass):

  1. Create a database on PostgreSQL 15 or newer:

    CREATE DATABASE hello;
    
  2. Point the binary at the database, give it an application key and an uploads bucket, then migrate and serve:

    ./bin/hello key:generate      # prints a base64 key; copy the value
    export SUMMER_DATABASE__DSN='postgres://acme:<secret>@127.0.0.1:5432/hello?sslmode=disable'
    export SUMMER_APP__KEY='<value printed by key:generate>'
    export SUMMER_STORAGE__UPLOADS__BUCKET_URL='mem://'
    ./bin/hello migrate
    ./bin/hello route:list
    ./bin/hello serve --addr 127.0.0.1:8080
    curl http://127.0.0.1:8080/items/1
    

summer migrate, summer migrate:status, summer migrate:rollback, summer serve, summer queue:work, summer queue:clear, summer schedule:run, summer route:list, summer key:generate, summer admin:create and summer admin:reset-password, run from the application directory, delegate to the built binary in bin/ and pass their flags to it unchanged.

Repository layout

Path Contents
admin/ The Vue 3 and TypeScript admin SPA; its build output is embedded by boardwalk.
cmd/ The summer CLI (cmd/summer).
docs/ Documentation source; summer docs:build renders it into a static site.
examples/ Example applications; examples/hello is the reference application and workspace.
internal/ CLI internals: the build and scaffolding generator, the watch loop and an OpenAPI conversion tool.
modules/ The framework modules, one Go package each, listed below.
scripts/ Verification scripts used as phase gates.

Modules

Module Purpose
backpack Per-instance application container that holds the configuration, a typed service registry, the event bus and the set of activated plugins.
beachcomber Search index sync for GORM models: after-commit upserts and deletes through a pluggable engine, gated by an application kill-switch.
boardwalk HTTP handler that serves the embedded admin SPA build under a configurable path prefix.
bonfire Declarative console commands for the summer tool and application binaries, adapted to Cobra with typed input, prompts and styled output.
bouncer Authentication for SummerCMS: HS256 JWT minting, verification and refresh, request guards, a revoked-token blacklist and bcrypt password helpers.
cabana Schema-driven admin backend that compiles WinterCMS-style YAML list, form, filter and relation definitions at boot and serves them as a JSON admin API next to the embedded admin SPA.
compass Layered YAML configuration with per-environment directories, SUMMER_ environment overrides, embedded plugin defaults and dot-path access.
conga Background jobs on River over the shared Postgres pool: transactional dispatch, a summer_jobs progress record, in-process or dedicated workers, and a wall-clock scheduler.
festival Typed, synchronous event bus with listener priorities, payload collection and stop-when-handled dispatch.
fetchguard Guarded outbound HTTP client and fetcher that blocks private and reserved addresses, enforces host, size and timeout limits, and offers an explicit trusted mode for operator-configured endpoints.
flare Web Push delivery with VAPID (RFC 8292) and aes128gcm payload encryption (RFC 8291) behind a small Pusher interface.
lagoon Postgres data layer: the shared GORM connection, per-plugin migrations, model helpers and file attachments.
lighthouse Transport-neutral realtime: a publisher interface with pluggable drivers, subscribe-time channel authorization, and model broadcasts enqueued in the write transaction.
pact Capability interfaces that compiled plugins implement to contribute routes, config, migrations, middleware, commands, admin screens, translations, mail templates and jobs.
party Compiled plugin registry that orders plugins by their dependencies and runs their Register and Boot lifecycle.
phrasebook Namespaced translation catalogs loaded from plugin YAML, with locale fallback, placeholder interpolation and CLDR pluralization.
postcard Transactional mail from plugin-owned Markdown templates and layouts, delivered through a memory, log or SMTP driver.
sunrise Bootstrap script that installs the summer CLI and creates a new SummerCMS application, or updates an existing one, in one command, embedded so it is versioned with the framework.
sunscreen Credential-redacting slog handler that keeps API keys, tokens and passwords out of application logs.
surf HTTP routing for SummerCMS: collects plugin routes and named middleware into a net/http ServeMux with constraints, rate limiting, body limits, CORS and panic recovery, and provides the serve and route:list commands.
tide HTTP parity toolkit that records request and response fixtures from a reference backend, replays them against a new one and reports normalized differences.
towel Request-scoped actor, organization, collection and locale values carried through context.Context.
wire JSON response helpers and value types that keep API bodies byte-compatible with a PHP (WinterCMS/Laravel) backend.
wristband An OAuth authorization server for MCP clients: RFC 8414 metadata, RFC 7591 dynamic client registration, the authorization-code flow with PKCE, consent operations and refresh-token rotation over application-supplied storage.

Using SummerCMS in an application

The framework is a single Go module, git.golem15.com/golem15/summercms; each framework package is imported from git.golem15.com/golem15/summercms/modules/<name>. An application requires that module and, during development, points it at a local checkout with a replace directive. summer new writes this go.mod for you, with a relative replace to the framework checkout and a committed go.work (see Go modules and workspaces). Written by hand, for an application module acme in a sibling directory:

module git.golem15.com/acme/acme

go 1.27.0

require git.golem15.com/golem15/summercms v0.0.0

replace git.golem15.com/golem15/summercms => ../summercms.go

The application lists its plugins in summer.yaml (module path, binary name, plugin ids and module paths); summer plugin:add registers a local plugin module in that manifest and in the workspace, and summer make:plugin scaffolds a new one. examples/hello/go.mod and examples/hello/summer.yaml are the working reference: the example replaces the framework with ../.. and its plugin modules with local paths.

Development

Run these from the repository root:

go vet ./...
go test ./...          # full suite; the Docker-backed integration tests start containers
go test -short ./...   # fast loop without Docker; skips the testcontainers-backed tests

go test ./... at the root does not include examples/hello, which is a separate workspace module; run its tests with cd examples/hello && go test ./....

The admin SPA lives in admin/. Install its dependencies with npm --prefix admin ci, then use its scripts:

npm --prefix admin run dev         # Vite dev server
npm --prefix admin run build       # type-check, then build into modules/boardwalk/dist
npm --prefix admin run typecheck   # vue-tsc only
npm --prefix admin run test        # Vitest
npm --prefix admin run gen:api     # regenerate TypeScript types from admin/openapi/admin.json

summer dev watches an application's sources and rebuilds its binary on change.

summer docs:build renders docs/ and every module README into a static site under site/ (use --out for another directory, --check to validate without writing). Code blocks with a src= reference are copies of real source, and src= works on top-level code blocks of docs/ pages only; after changing that source, run summer docs:sync to refresh the copies.

summer docs:serve builds the same site into a temporary directory and previews it at http://127.0.0.1:8088 (--addr to change it), rebuilding when the docs, a module or a src= source changes; a failed rebuild prints its problems and keeps serving the last good build. It listens only on a loopback address unless you pass --allow-remote. Search needs this server: browsers block the search index over file://.

Design notes

Decisions and background live in .planning/notes/, including:

Description
Content management framework for Go,
https://summercms.io
Readme 24 MiB
Languages
Go 68.4%
TypeScript 14.2%
Shell 7.7%
Vue 6.3%
JavaScript 1.7%
Other 1.7%