kiln_plugin

The compile-time plugin contract for KilnCMS: the behaviours and macros a plugin uses.

Module What a plugin does with it
Kiln.Plugin The entry module: use Kiln.Plugin, override the callbacks you need
Kiln.Block (+ Kiln.Block.Renderer, Kiln.Block.Info, …) A typed content block: use Kiln.Block and its block/field/migrate DSL
Kiln.FieldType (+ Kiln.Tokens) A custom-field value type admins can pick
Kiln.Forms.SpamCheck (+ Kiln.Forms.SpamCheck.Context) A heuristic run on every public form submission
Kiln.ImageBackend An external image service new uploads can go to

These modules used to live inside the KilnCMS core, which made it impossible to compile a plugin as a Mix dependency: Mix compiles a dependency before the project that uses it, so use Kiln.Plugin failed with "module Kiln.Plugin is not loaded" (kiln_cms#1909). The core now builds against this package, under the same module names, so a projects/ overlay sees no difference.

Installation

In the plugin's own mix.exs:

def deps do
  [
    {:kiln_plugin, "~> 1.0"}
  ]
end

For the plugin's own builds and tests (its CI, mix test in its repository), add the one Ash setting a block needs to compile, with the core's value so the block behaves the same in both places. Ash 3.34 refuses to compile a resource with a :string attribute without it, and every block is an embedded Ash resource:

# the plugin's config/config.exs
import Config
config :ash, default_string_length_count: :codepoints

A dependency's config never reaches the project that uses it, so this changes nothing inside a KilnCMS build: there, the core's own config applies.

Require the lowest minor whose additions you use. A KilnCMS build always compiles against the copy of this package it bundles (its mix.exs takes it as a path: dependency with override: true), and Mix does not check a dependency's requirement against an overridden package, so the core checks it instead: mix kiln.plugins.doctor fails when an installed plugin requires a kiln_plugin version the core doesn't bundle.

Then, in the site, the plugin is a dependency of the KilnCMS project and one config line activates it. The dependency line goes in the core's own mix.exs, which edits the pinned core: mix kiln.update refuses a locally patched pin, so for now an update means reapplying that line. A dependency-list hook for overlays is tracked in kiln_cms#1909.

# the KilnCMS mix.exs
{:kiln_ratings, "~> 1.0"}

# config/project.exs
config :kiln_cms, :plugins, [KilnRatings.Plugin]

What works from a dependency, and what doesn't

A dependency compiles before the core and sees only its own dependencies. Anything a plugin builds on that lives in the core, rather than here, still has to be a projects/<name>/ overlay, which is compiled inside the core.

Contribution As a dependency
Blocks (blocks/0) Yes
Field types (field_types/0) Yes, except a colocated client hook (input_hook/1): Phoenix writes a dependency's colocated hooks under its own app name, which the core's app.js does not import
Spam checks (spam_checks/0) Yes
Image backends (image_backends/0) Yes
Nav items, supervision children, Oban queues Yes
Admin, editor and public routes Only for a LiveView that depends on phoenix_live_view directly; one that does use KilnCMSWeb, :live_view or renders Layouts.app needs the core, so it stays an overlay
Content types (use KilnCMS.CMS.Content), Ash domains (domains/0) No: they need the core's repo, domains and policies. Overlay only
Advisory checks (advisories/0, Kiln.Advisory) No: a check matches %Kiln.Advisory.Context{}, whose body is computed by the core. Overlay only, for now

Versioning

See CHANGELOG.md. The contract is covered by the KilnCMS 1.x stability promise (docs/overlay-contract.md in the core), so this package is 1.x too: minors add, and a major comes only with a KilnCMS major.

Releasing

Publishing runs from .github/workflows/release-clients.yml in the core repository, triggered only by a tag named kiln_plugin-vX.Y.Z. The workflow checks the tag equals @version in mix.exs, runs the same gate as CI's plugin_sdk job, builds the package with mix hex.build, and, after approval in the hex environment, attests the tarball, proves a rebuild is byte-identical, and runs mix hex.publish --yes.

  1. Bump @version in mix.exs, add a CHANGELOG.md entry, and merge.
  2. Tag the merge commit kiln_plugin-vX.Y.Z and push the tag.
  3. Approve the run in the hex environment.

Before the first run, a maintainer has to allow kiln_plugin-v* tags on the hex environment (it accepts only kiln_client-v* today). The HEX_API_KEY there needs to be allowed to publish a new package.