Plugin events
v0.0.1
Mishka CMS fires plugin events at the points where an extension or a plugin may watch, change or refuse what the CMS is doing. This page lists every event the core, the host app, the blog and the document extension fire through MishkaInstaller.Event.Hook.call/3, with its payload and the moment it fires. To learn how plugins and extensions fit together, read Extensions and plugins.
How an event fires
A plugin listens on an event by name. It uses MishkaInstaller.Event.Hook and defines call/1, or call/2 to receive the read-only meta. It answers {:reply, payload} to hand the payload to the next plugin, or {:reply, :halt, payload} to stop the chain. The registration below adds an audit check to the editor:
defmodule MyApp.AuditPlugin do
use MishkaInstaller.Event.Hook, event: "builder:audit:checks", initial: %{priority: 50}
def call(%{checks: checks} = payload) do
{:reply, %{payload | checks: checks ++ [MyCheck.descriptor()]}}
end
endThe CMS passes these options when it fires an event.
| Option | Meaning |
|---|---|
return | When it is set, such as return: false, the plugins run and the call site gets its own payload back. The event is an announcement. |
meta | Read-only context for the plugins that define call/2. It is never part of the answer. |
cannot_run | The answer when the event cannot run, because one of its plugins is started but not loaded. Without it the call site gets its payload back. |
An event that no plugin listens on gives the payload back unchanged. A plugin chain that raises is logged, and the call site gets its payload back.
Kinds
The Kind column of the tables below uses these words.
| Kind | Meaning |
|---|---|
| Announcement | The event fires with return: false, or the CMS ignores the answer. Nothing waits for a plugin. |
| Transform | The CMS uses what the chain returns, such as a changed conn, socket, query, string or map. Most call sites keep the original when a plugin returns the wrong shape. |
| Registry | The payload carries a list that already holds the core's entries. A plugin appends its own. A repeated key keeps the core entry. |
| Gate | A plugin refuses the action by putting a message in the error key of the payload it returns. The editor shows the message and writes nothing. |
Resource events
Core, blog and document resources fire events from their Ash actions. The event name is <prefix>:<resource>:<stage>_<name>. The prefix is the extension: cms_core for the core, blog for the blog and document for the document extension. The resource is the last part of the module name in snake case, such as page for MishkaCmsCore.Runtime.Page and blog_post for MishkaBlog.BlogPost. A plugin can build the name with MishkaCmsCore.Runtime.Resources.Notifiers.PluginHook.event_name/2 instead of spelling it.
| Event | Kind | Payload | When it fires |
|---|---|---|---|
<prefix>:<resource>:after_<action> | Announcement | The record the action returned. Meta: %{actor, site_id}. | After a create, update or destroy action committed. Every such action fires its own event, and a master's master_* twin fires its own. |
<prefix>:<resource>:after_save | Announcement | The same record and meta. | After any create or update action, right after its own event. Listen here to react to a record being written however it was written. |
<prefix>:<resource>:before_<action> | Transform | The Ash changeset. Meta: %{actor, site_id}. | Before one of the actions in the first table below runs. Return the changeset, changed or with an error added, to change or reject the action. |
<prefix>:<resource>:query_<action> | Transform | The Ash query. Meta: %{actor, site_id}. | Before one of the read actions in the second table below runs. The query you return is the one that runs. |
<prefix>:<resource>:render_content | Transform | %{html, record}. Meta: %{actor}. The returned html is used. | When the rendered_content calculation of a resource in the third table below is loaded. |
These resources fire the after_ events.
| Prefix | Resources |
|---|---|
cms_core | permission, user, user_permission, notification, component, error_page, error_page_draft, js_hook, layout, layout_draft, media, media_category, page, page_comment, page_draft, redirect, setting, site, snippet, stylesheet, translation |
blog | auto_posting, blog_post, blogger, blogger_follower, collection, comment, comment_vote, post_blogger, post_bookmark, post_collection, post_rating, post_tag, post_view, tag, team, team_follower, team_member, team_permission |
document | issue, issue_comment, note, note_draft, section, section_bookmark, section_draft, section_tag, section_view, tag, workspace, workspace_follower, workspace_version, workspace_version_lineage |
The before_ events exist on these actions only.
| Event | Fires before |
|---|---|
cms_core:page:before_create | A page is created. |
cms_core:user:before_create | An account is created. |
blog:blog_post:before_create | A blog post is created. |
blog:comment:before_create_public | A visitor's blog comment is created. |
document:section:before_create | A document section is created. |
The query_ events exist on these read actions only.
| Event | Fires before |
|---|---|
cms_core:page:query_read_any | The read_any action of pages runs. |
cms_core:user:query_tenant_read | The tenant_read action of accounts runs. |
blog:blog_post:query_published | The published action of blog posts runs. |
blog:comment:query_public_list | The public_list action of blog comments runs. |
blog:blogger:query_public_list | The public_list action of bloggers runs. |
blog:blogger:query_public_by_slug | The public_by_slug action of bloggers runs. |
blog:team:query_public_list | The public_list action of teams runs. |
blog:team:query_public_by_slug | The public_by_slug action of teams runs. |
document:section:query_public_list | The public_list action of document sections runs. |
The render_content events exist on blog:blog_post, blog:comment and document:section, which each have a rendered_content calculation.
Visual editor events
The visual editor is a plugin surface. The core fills each list through the same events an extension uses. The sections below list the registries, the transforms, the gates and the announcements.
Registries
Each payload is a map with one key that holds the list. The core entries come first.
| Event | Kind | Payload key | What the list feeds |
|---|---|---|---|
builder:components | Registry | entries | The Components tab of the palette. |
builder:blocks | Registry | blocks | The sections of the Blocks tab. Contributions come after the core's, in order. |
builder:elements | Registry | elements | The elements of the Blocks tab. Contributions come after the core's, in order. |
builder:inspector:sections | Registry | sections | The sections of the Inspector. |
builder:rails | Registry | tabs | The tabs of the right-hand panel. |
builder:studio:tabs | Registry | tabs | The tabs of the Studio. |
builder:canvas:toolbar | Registry | actions | The buttons of the toolbar above the selected element. Pressing a plugin's button fires builder:canvas:toolbar:invoked. |
builder:commands | Registry | commands | The Actions list of the command palette. |
builder:audit:checks | Registry | checks | The checks of the Audit tab. |
builder:page_starters | Registry | structures | The cards of the starter chooser. Contributions come after the core's, in order. |
builder:icon:libraries | Registry | libraries | The icon sets of the icon picker. Contributions come after the core's, in order. |
builder:interactions | Registry | actions | The actions of the Interactions section. |
builder:conditions | Registry | rules | The rules of the Show-when section. |
builder:js_hooks | Registry | hooks | The hooks of the hook picker. |
builder:mention:sources | Registry | sources | The sources of @-mentions in review notes. |
Transforms
| Event | Kind | Payload | When it fires |
|---|---|---|---|
builder:node:before_<kind> | Transform | %{op, meta, error}. meta is %{token, section, resource_id, site_id, actor_id}. Return a changed op, or set error to a message to refuse it. | Before every change to the document. <kind> is one of insert, move, delete, duplicate, set_attr, delete_attr, set_text, set_style, reset_styles, wrap, wrap_many and unwrap. |
builder:serialize | Transform | %{heex, meta}. meta is %{section, stage}. The returned heex is saved. | On every save and publish, before the gates. |
builder:history:query | Transform | %{source_type, source_id, branch_id, query}. The returned Ash query is read. | When the history sheet reads the timeline of a record. |
builder:containment | Transform | %{allowed?, reason, parent, child, tree, components}. A plugin can set allowed? to false with a reason. It cannot allow what the core refused. | When the editor checks whether an element may go inside another. |
builder:preview:document | Transform | %{html, resource, record_id, site_id, note}. The returned html string is shown. | Whenever a preview document is built, in the editor and in the Preview tab of the runtime modals. |
builder:examples | Transform | %{examples, entry, site_id}. | Before the wizard shows the examples of a component. |
builder:preset:choices | Transform | %{choices, site_id}. | When the editor builds the preset choices for the attributes of a component. |
builder:token:before_set | Transform | %{key, value, site_id, site_name, actor_id, error}. Return a changed value, or set error to refuse it. | Before a design token is set. |
builder:ai:before_ask | Transform | %{prompt, scope, backend, section, resource_id, site_id, actor_id, error}. Return a changed prompt, or set error to refuse it. | Before the Assistant sends a prompt to the model. |
builder:form:role | Transform | %{role, entry}. Answer another role to change the inferred part of a component in a form. | When the editor reads the part a component plays in a form. |
page:form:submitted | Transform | %{form, values, page, site_id, handled}. A plugin that stored the submission answers handled: true. | A visitor submits a form on a published page. A form with no destination is reported by the Forms audit. |
Gates
A gate is a transform whose payload carries error, which starts as nil. Set it to a message to refuse. While the plugins of a gate cannot run, the gate refuses with a message that says an extension is not loaded.
| Event | Kind | Payload | When it fires |
|---|---|---|---|
builder:save:before | Gate | %{tree, serialized, resource_id, section, error} | Before the document is written at a stage other than published. |
builder:publish:before | Gate | %{tree, serialized, resource_id, section, error} | Before a publish. |
builder:draft:before_save | Gate | %{section, resource_id, draft_id, label, serialized, site_id, actor_id, error} | Before a draft is written. |
builder:review:before_comment | Gate | %{body, type, section, node_id, parent_comment_id, error} | Before a review note is written. |
builder:code:before_apply | Gate | %{source, tab, node_id, section, resource_id, site_id, actor_id, error} | Before hand-written source is applied to the document. |
builder:class:before_save | Gate | %{name, utilities, site_id, actor_id, error} | Before a saved class is written. |
builder:element_css:before_save | Gate | %{node_id, css, owner, site_id, actor_id, error} | Before the own CSS of an element is written. |
Announcements
| Event | Kind | Payload | When it fires |
|---|---|---|---|
builder:session:after_open | Announcement | %{token, section, resource_id, site_id, actor_id} | An editing session opens. |
builder:session:after_close | Announcement | %{token, section, resource_id, site_id, actor_id} | An editing session closes. |
builder:node:after_<kind> | Announcement | %{token, section, resource_id, site_id, actor_id, kind, node_id} | After a change to the document went through. <kind> is the same list as for builder:node:before_<kind>. |
builder:save:after | Announcement | %{record_id, section, stage} with stage set to :draft | After a draft write. |
builder:publish:after | Announcement | %{record_id, section, stage} with stage set to :published | After a publish. |
builder:draft:after_save | Announcement | The payload of builder:draft:before_save with draft_id set to the saved draft | After a draft was written. |
builder:review:after_comment | Announcement | %{id, body, type, node_id, reply?, section, source_id, site_id, actor_id} | After a review note was saved. |
builder:element_css:after_save | Announcement | %{node_id, css, owner, site_id, actor_id, stage} | After the own CSS of an element was written. Draft stage rules are not on the live page yet. |
builder:canvas:toolbar:invoked | Announcement | %{action, node_id, token} | A plugin's toolbar button is pressed. The plugin applies its own changes through the token. |
builder:form:after_rename | Announcement | %{node_id, from, to, token, change, submit, section, resource_id, site_id, actor_id} | A form is renamed in the Inspector. |
builder:form:after_customize | Announcement | %{node_id, event, kind, section, resource_id, site_id, actor_id} | The default code of a form event is added to the Handlers tab. kind is :validate or :submit. |
builder:form:after_create | Announcement | %{node_id, seed, token, how, section, resource_id, site_id, actor_id} | A form is made. how is :started, :wired, :moved or :fixed. |
builder:form:after_setting | Announcement | %{node_id, seed, setting, value, section, resource_id, site_id, actor_id} | A setting of a working form changes. setting is :how or :address. |
Compile and build events
These events fire while the CMS turns saved pages, layouts, components and styles into running modules and bundles. Most are announcements. The two that carry a bundle are transforms.
| Event | Kind | Payload | When it fires |
|---|---|---|---|
runtime:compile:before_page and runtime:compile:after_page | Announcement | The page's fields, such as title, site and template. Meta: %{title, site}. | Before and after the module of a page is built. |
runtime:compile:before_layout and runtime:compile:after_layout | Announcement | The layout's fields. Meta: %{title, site}. | Before and after the module of a layout is built. |
runtime:compile:before_component and runtime:compile:after_component | Announcement | The component's fields. Meta: %{title, site}. | Before and after the module of a component is built. |
runtime:compile:before_error_page and runtime:compile:after_error_page | Announcement | The error page's fields. Meta: %{title, site}. | Before and after the module of an error page is built. |
runtime:compile:before_module | Announcement | %{file}. Meta: %{file}. | Before a built module is compiled and loaded. |
runtime:compile:after_module | Announcement | %{file}. Meta: %{file, module}. | After a module compiled without error. |
runtime:compile:cache_hit | Announcement | %{module, key}. Meta: %{module}. key is the key the file is filed under, in lowercase hexadecimal. | A built module was loaded from its file in the compiled-module cache instead of being compiled. |
runtime:compile:cache_miss | Announcement | %{module, key, reason}. Meta: %{module, reason}. reason is :absent, :unreadable, :corrupt, :wrong_module or :load_failed. | The cache held no usable file for a module, so it is compiled. Any file that could not be used, which is every reason but :absent, is deleted first. |
runtime:after_module_compile | Announcement | %{module} | After a page, layout, component or error page module compiled cleanly. |
runtime:compile:offline_page_failed | Announcement | %{module, site, reason}. Meta: %{module, site}. | A site's Offline error page raised when it was drawn once after its compile. Visitors get the built-in maintenance page. |
runtime:before_compile | Announcement | %{resource, action, id, site} | The compile worker starts the job for one saved or deleted record. |
runtime:after_compile | Announcement | %{args, result}. args is the payload of runtime:before_compile. | The job ended. result may be {:error, reason}. |
runtime:compile_gate:acquired | Announcement | %{at: :acquire} | A compile takes one of the limited compile slots. |
runtime:compile_gate:released | Announcement | %{at: :release} | A compile gives its slot back. |
runtime:compile:before_css | Announcement | %{site}. Meta: %{site}. | Before a site's stylesheet bundle is built. |
runtime:compile:css | Transform | The compiled CSS string. Meta: %{site}. The returned string is compressed and stored. | After the stylesheet bundle is built, before it is stored. |
runtime:compile:before_js | Announcement | %{site}. Meta: %{site}. | Before a site's JavaScript bundle is built. |
runtime:compile:js | Transform | The compiled JavaScript string. Meta: %{site}. The returned string is compressed and stored. | After the JavaScript bundle is built, before it is stored. |
runtime:compile:lazy_js | Transform | The JavaScript string of a hook marked lazy, which is built as a module of its own and is not in the site's bundle. Meta: %{site, hook}. The returned string is hashed and stored. | After a lazy hook is built, before its content is hashed. |
runtime:compile:js_hook_skipped | Announcement | %{hook, site, reason}. Meta: %{site}. | A JS hook is left out of a site's bundle. |
runtime:compile:content_components | Transform | %{site, site_id, components, icons, classes}, each list starting empty. Meta: %{site}. | While a site's stylesheet is built. A plugin adds the components, icons and classes that its stored content names, so the stylesheet includes them. It also adds the classes that only the section bar under the header draws, such as those of a function component. A component name reaches the stylesheet only when a template of the site draws stored content. See Page frame. |
runtime:compile:before_sitemap | Announcement | %{site}. Meta: %{site}. | Before a site's sitemap is built. |
runtime:compile:after_sitemap | Announcement | %{site, urls, files}. Meta: %{site}. | After the sitemap is built and stored. |
seo:sitemap:urls | Transform | %{site, pages, urls}. A plugin returns urls with more entries, each with a path that starts with /. | While a site's sitemap collects its addresses. |
runtime:compile:before_page_prefetch | Transform | %{module, site, site_id, components}, each component %{name, site}. module is the page or layout, and site and site_id are those of the site it renders for. Meta: %{module, site, site_id}. A plugin returns other components to build more or fewer. | Before a page or layout builds the components it is about to draw. It fires only when one of them is not built yet, and only components that are not built, and are global or belong to that site, are built. |
runtime:compile:after_page_prefetch | Announcement | %{module, site, site_id, built, failed}. built lists components as %{name, site}, and failed lists %{name, site, reason}. Meta: %{module, site, site_id}. | After those components are built. |
runtime:before_bulk_sync and runtime:after_bulk_sync | Announcement | %{scope, kinds} | Before and after a bulk change rebuilds components, JS hooks and stylesheets at once. |
Site and kit events
| Event | Kind | Payload | When it fires |
|---|---|---|---|
runtime:boot:before_compile_site and runtime:boot:after_compile_site | Announcement | %{site_id, site_name} | Before and after every part of one site is compiled again and its asset jobs are queued. |
runtime:site:before_rebuild and runtime:site:after_rebuild | Announcement | %{site_id, site_name, transition}. transition is recompile, rehost, purge or rename. | Before and after a site is rebuilt or purged after it was activated, deactivated, rehosted, renamed, archived or unarchived. |
runtime:site:before_teardown | Announcement | %{site_id, site_name, modules}, where modules is a count. | Before a deleted site's runtime is removed. |
runtime:site:after_teardown | Announcement | %{site_id, site_name, counts}. counts is %{modules, assets, routes}. | After it is removed. |
runtime:site:before_purge | Announcement | %{site_name, remove_templates?} | Before a site's modules, cached bundles and routing are freed from memory. |
runtime:site:after_purge | Announcement | %{modules, assets, routes, site_name} | After they are freed. |
runtime:site:before_auth_pages | Announcement | %{site_id, site_name} | Before a site's sign-in layout, pages and words are written. |
runtime:site:after_auth_pages | Announcement | %{site_id, layout_id, layouts, pages, renamed_pages, skipped_pages, translations} | After they are written. |
runtime:ui_kit:before_install | Announcement | %{bundle_path, site_id, selection} | Before a UI kit is installed. |
runtime:ui_kit:after_install | Announcement | %{site_id, bundle, kinds} | After a UI kit is installed and the runtime rebuilt. |
runtime:ui_kit:before_uninstall | Announcement | %{bundle_path, site_id} | Before a UI kit is removed. |
runtime:ui_kit:after_uninstall | Announcement | %{site_id, kit} | After a UI kit is removed. |
installer:languages:saved | Announcement | %{site_id, languages} | The install wizard saved a site's languages. |
installer:extension:install | Transform | %{site_id, key, answers, reports, ask, actor}. ask is always :install. A plugin whose key matches its app adds a report to reports. | An extension is installed on a site. |
runtime:site:manifest | Transform | %{site, manifest}. A plugin returns the map with a changed manifest, such as another display or a lang. | The site's /site.webmanifest is built, for a site that has home-screen icons. |
runtime:site:after_icons | Announcement | %{site_id, hash, files}. files maps each icon size to its path. | A site's home-screen icons were drawn from its App icon or Favicon. |
State events
The CMS keeps sites, pages, assets and more in memory. These announcements say that an entry changed on this node.
| Event | Kind | Payload | When it fires |
|---|---|---|---|
runtime:state:site:changed | Announcement | %{op, key}. op is :save or :delete, and key is the site id. | A site is stored or removed in memory. |
runtime:state:site:availability_changed | Announcement | %{key}. key is a site id, or nil for every site. | Whether a site is open may have changed. |
runtime:state:assets:changed | Announcement | %{op, key}. op is :save or :delete, and key is {site name, type}. | A cached bundle or sitemap is stored or removed. |
runtime:state:metadata:changed | Announcement | %{op, key}. op is :save or :delete, and key is the page id. | The routing entry of a page is stored or removed. |
runtime:state:redirect:changed | Announcement | %{op, key}. op is :save or :delete, and key is the rule id. | A redirect rule is stored or removed. |
runtime:state:viewer:changed | Announcement | %{op, key}. op is :save or :delete, and key is the user id. | A signed-in viewer is stored or dropped. |
runtime:state:sidebar:changed | Announcement | %{key}. key is a site id, or nil for the global settings. | The sidebar settings changed. |
runtime:state:language_group:changed | Announcement | %{op, key, resource, site_id, translations, languages, actor_id}. op is :translations_deleted or :translations_archived, and key is the id of the original. | The translations of a record were deleted or archived with it. |
Page rendering events
The host app serves database pages through MishkaCmsWeb.RuntimeLive. These events follow a page through mount, live navigation, events, messages and render. The names start with a capital letter, as the older events do.
| Event | Kind | Payload | When it fires |
|---|---|---|---|
RuntimeLive.OnMount.BeforeMount | Announcement | [params, session, socket] | A database page mounts, once the address is known to be served. |
RuntimeLive.OnMount.BeforePathTransform | Transform | %{path, site}, path a list of segments. The returned path picks the page. | After BeforeMount, before the page is looked up. |
RuntimeLive.OnMount.AclAfterCompile | Transform | The compile result, {:ok, _} or {:error, reason}. While the plugins cannot run, the answer is {:error, :cannot_run} and the page is not served. | After the page module is found or compiled, before the visitor is admitted. |
RuntimeLive.OnMount.AfterCompile | Transform | The socket with the page assigned. The returned socket is used. | The visitor is admitted to the page. |
RuntimeLive.OnMount.AfterCompileError | Announcement | The reason. | Compiling or admitting the page failed. The mount then raises a server error. |
RuntimeLive.OnMount.Return | Transform | {:ok, socket, layout: {...}}. The returned tuple is the mount's answer. | The last step of mount. |
RuntimeLive.OnRender.BeforeRender | Transform | The assigns. The returned map must keep page_module, page.path and private.site and site_id. | The start of every render of a database page. |
RuntimeLive.OnRender.AfterCompile | Transform | What the page module rendered. The returned value is drawn. | After the page module renders. |
RuntimeLive.OnRender.AfterCompileError | Announcement | The reason. | The page module could not be compiled when it was rendered. |
RuntimeLive.HandleParams.BeforeParams | Announcement | [params, url, socket] | A live navigation or patch reaches a database page. |
RuntimeLive.HandleParams.BeforePathTransform | Transform | %{path, site}. The returned path picks the page. | After BeforeParams. |
RuntimeLive.HandleParams.AclAfterCompile | Transform | The compile result. While the plugins cannot run, the answer is {:error, :cannot_run}. | As RuntimeLive.OnMount.AclAfterCompile, for a live navigation. |
RuntimeLive.HandleParams.AfterCompile | Transform | The socket. The returned socket is used. | The visitor is admitted to the page the navigation reached. |
RuntimeLive.HandleParams.AfterCompileError | Announcement | The reason. | Compiling or admitting the page failed during navigation. |
RuntimeLive.HandleParams.BeforeAllParams | Announcement | [params, url, socket] | A handle_params call for an address that is not a page path. |
RuntimeLive.HandleEvent.BeforeEvent | Transform | The assigns. The returned map must keep page_module, private.site and page.path. | A visitor event reaches a database page, before it runs. |
RuntimeLive.HandleEvent.AfterCallEvent | Transform | What the page's handle_event returned. | After the page handled the event. |
RuntimeLive.HandleEvent.ReturnCallEvent | Transform | The socket from {:noreply, socket}. | When handle_event returned {:noreply, socket}. |
RuntimeLive.HandleEvent.AfterCallEventError | Announcement | The invalid return value. | handle_event returned something other than {:noreply, socket}. |
RuntimeLive.HandleInfo.BeforeInfo | Transform | The assigns. The returned map must keep page_module, private.site and page.path. | A message reaches a database page. |
RuntimeLive.HandleInfo.AfterCallInfo | Transform | What the page's handle_info returned. | After the page handled the message. |
RuntimeLive.HandleInfo.ReturnCallInfo | Transform | The socket from {:noreply, socket}. | When handle_info returned {:noreply, socket}. |
RuntimeLive.HandleInfo.AfterCallInfoError | Announcement | The invalid return value. | handle_info returned something other than {:noreply, socket}. |
RuntimeLive.RuntimeLayout.BeforeLayout | Announcement | The assigns. | Before the layout of a site renders around a page. |
RuntimeLive.RuntimeLayout.AfterCompile | Transform | What the layout rendered. | After the layout rendered. |
RuntimeLive.RuntimeLayout.AfterCompileError | Announcement | The reason or the exception. | The layout failed. The default layout is used. |
runtime:page:visitors_only_redirected | Transform | The socket. Meta: %{site_id, path}. A plugin that redirects the socket sends the visitor elsewhere. | A signed-in visitor opens a visitors-only page. Without a redirect, the visitor goes to the site root. |
runtime:page:signed_in_only_redirected | Transform | The socket. Meta: %{site_id, path}. A plugin that redirects the socket sends the visitor elsewhere. | A visitor who is not signed in opens a signed-in-only page. Without a redirect, the visitor goes to the sign-in page. |
ErrorHTML.Render.BeforeRender | Transform | %{conn, status_code, template}. The returned conn is used. | An error page starts to render. |
ErrorHTML.Render.BeforeCompile | Transform | %{path, site, status_code}. The returned map picks the error page. | Before the error page module is found. |
ErrorHTML.Render.AfterCompile | Transform | %{conn, content}. The returned content is sent. | The error page rendered. |
ErrorHTML.Render.CompileError | Transform | %{error, template, fallback}. The returned fallback is sent. | The error page could not be compiled. |
RootHtml.MetaTags | Transform | %{html, assigns}, html starting as an empty string. The returned html goes into the head. | The root layout renders the head. |
RootHtml.InlineScript | Transform | %{js, assigns}, js starting as nil. A returned string is written as an inline script. | The root layout renders the end of the head. |
RootHtml.ExternalAssets | Transform | %{html, assigns}, html starting as an empty string. The returned html goes into the head. | The root layout renders the end of the head. |
runtime:render:component_unavailable | Transform | An empty render. Meta: %{component_name, site, site_id}. The returned render is drawn. | A page names a component of another site, so nothing is drawn. |
runtime:component:crashed | Announcement | %{component_name, site, site_id, error_id, error, stacktrace} | A component that a marker in stored content names raised while it was drawn, and a small box took its place. |
runtime:render:before_content_component | Transform | %{component_name, site, attrs, inner}. Meta: %{site_id, index, marker_attrs, slot_arg, slot_args}. A plugin returns suppress: true to draw nothing, or changed attrs and inner. | Stored content draws a component that one of its markers names. |
runtime:content:editor_palette | Transform | %{site_id, components}. Meta: %{current_user}. The returned components are offered. | The content editor builds its component palette. |
runtime:content:editor_blocks | Transform | %{site_id, blocks}. Meta: %{current_user}. The returned blocks are offered. | The content editor builds its blocks. |
Page source and frame events
MishkaCmsWeb.RuntimeLive fires these two events once the page, its language, the query parameters and the visitor are known. They run in this order: runtime:page:source, runtime:page:frame, then runtime:page:seo in the next section. The frame and the head are handed the source, and the head reads the frame's languages. A plugin answers for the pages it owns and hands every other page back untouched. {:reply, :halt, payload} keeps later plugins from answering for a page this one owns.
| Event | Kind | Payload | When it fires |
|---|---|---|---|
runtime:page:source | Transform | %{page, section, record, extra, params, language, site_id, current_user, held, source}. Everything but source is read only, and source starts as nil. current_user is the signed-in visitor, or nil for a guest. held is what the previous address of this LiveView read for the same visitor, or nil. A plugin answers with the rows it read, as that visitor, under source. The CMS keeps only source of the answer. | In every handle_params/3, before the frame and the head. The CMS assigns the answer as @page_source. The plugins on runtime:page:frame and runtime:page:seo are handed it as source, and the page's blocks are handed it as page_source. See Page source. |
runtime:page:frame | Transform | %{page, section, record, extra, params, language, site_id, current_user, source, translations, section_bar}. source is @page_source. translations holds the page's own languages, each %{language, path, name, title}, and section_bar is nil. A plugin changes those two keys, and the CMS keeps only those two of the answer. A translations list a plugin returns keeps only the site's languages, in the site's order, and fewer than two give []. | In every handle_params/3, after the source and before the head. The CMS assigns @page_languages and @page_section_bar. The header's language switcher, the head's hreflang, og:locale and og:locale:alternate tags, and the bar under the header read them. See Page frame. |
SEO events
| Event | Kind | Payload | When it fires |
|---|---|---|---|
runtime:page:seo | Transform | %{title, description, image, type, url, language, tags, schema, vars, var_docs, permissions, card, page, site_id, params, record, redraw, source}. A plugin fills what the page leaves empty. source is @page_source, the rows the plugin on runtime:page:source read for this address, or nil. It is read only, like page, site_id, params, record and redraw, so a plugin takes its record from there rather than reading it again. | Once per live navigation, after the source and the frame, when the head of a database page is resolved, and when the editor previews it. The editor's preview and a share card hand it no source, so a plugin reads for itself there. |
runtime:page:records | Transform | %{site_id, records}, records starting empty. A plugin appends %{id, group, label, captures, samples} for each kind of record its pages show. | The editor lists the kinds of record a page can show. |
seo:robots:lines | Transform | %{site, lines}. The returned lines are written to robots.txt. | A request for /robots.txt. |
web:request:sitemap_unavailable | Transform | The conn. Meta: %{site, reason}. A plugin that halts the conn answers instead. | A request for a sitemap is refused, because it is not built yet or the site is not served. |
document:page:seo | Transform | The SEO answer for a documentation page, with the doc.* and workspace.* variables in vars. Meta: %{site_id, document, version, page, url, contents}. | The document extension answers runtime:page:seo for one of its pages and hands its answer to this event. |
Request events
These events fire from plugs and controllers. Most hand a conn to the plugins. A plugin that halts the conn takes over the answer.
Web requests
| Event | Kind | Payload | When it fires |
|---|---|---|---|
web:request:before_site_resolve | Transform | The conn. | A request starts, before the site is found from the host. |
web:request:after_site_resolve | Transform | The conn. Meta: %{host}. | After the site was found or refused. |
web:request:site_unavailable | Transform | The conn. Meta: %{state, site}, state being :inactive or :offline. | A switched-off site is requested, or an offline site is requested by someone who cannot open it. The CMS sends its own page unless a plugin halts. |
web:request:offline_page | Transform | The conn, with status 503. Meta: %{source, site}, source being :error_page or :built_in, plus reason when it is :built_in. | The maintenance page is about to be sent. |
web:request:master_sign_in_refused | Transform | The conn. Meta: %{site}. | Someone asks a site other than the master site to sign in masters, with ?master_user=true in the address. See the documentation. |
web:request:session_refused | Transform | The conn. Meta: %{site, user_id}. | A session that carries the wrong site is signed out of the request. |
web:request:before_permission_gate | Transform | The conn. | A route that requires a permission starts to check it. |
web:request:permission_gate | Transform | The conn the check answered with. Meta: %{authorized}. | After the permission check. |
web:request:before_response | Transform | The conn. | The security headers plug starts. |
web:response:before_security_headers | Transform | The conn, with the headers set. | Before the security headers are sent. |
web:request:redirected | Transform | The conn, with the status and location set. Meta: %{site, from, to, status}. | A redirect rule answers the request. |
web:request:admin_language_chosen | Transform | The conn. Meta: %{language}. | An admin chooses the interface language. |
web:request:asset_unavailable | Transform | The conn. Meta: %{site, type, reason}. | A site's CSS or JS bundle is refused. |
web:request:preview_unavailable | Transform | The conn. Meta: %{site_id, reason}. | A draft preview is refused because its site is closed to the viewer. |
web:request:upload_unavailable | Transform | The conn. Meta: %{state, site_id}. | An upload file is refused because its site is closed to the viewer. |
web:live:site_unavailable | Transform | The socket. Meta: %{site_id, path}. | An open LiveView is about to be sent away because its site closed. A plugin can redirect it elsewhere. |
web:live:preview_unavailable | Announcement | %{site_id, state, resource} | A draft preview LiveView is refused. |
AssetsController.Serve.BeforeServe | Transform | %{conn, site, type}. The returned conn is used. | A site's bundle is requested. |
AssetsController.Serve.BeforeAssetLookup | Transform | %{site, key, type, encoding} | Before the bundle is read from the cache. encoding is br, gzip or nil. |
AssetsController.Serve.AfterAssetLookup | Transform | %{content, encoding} | After the cache was read. |
AssetsController.Serve.BeforeResponse | Transform | %{conn, content, encoding, type, headers}. The returned map is sent. A nil content is refused as missing. | Before the bundle is sent. |
API requests
| Event | Kind | Payload | When it fires |
|---|---|---|---|
api:request:before_cors | Transform | The conn. | The CORS plug of the API starts. |
api:request:cors_headers | Transform | The conn. Meta: %{origin}. | After the CORS headers were set. |
api:request:before_tenant_resolve | Transform | The conn. | The API looks up the site from the host. |
api:request:after_tenant_resolve | Transform | The conn. Meta: %{host}. | After the site was resolved or refused. |
api:request:site_unavailable | Transform | The conn. Meta: %{state, site}, state being :inactive or :offline. | An API request reaches a switched-off or offline site. A plugin that halts answers instead of the 503. |
api:request:no_tenant_refused | Transform | The conn. Meta: %{site}. | A request asks for no tenant on a site other than the master site. It is scoped to the site. |
api:request:token_refused | Transform | The conn. Meta: %{site, user_id}. | A bearer token that belongs to another scope is dropped from the request. |
api:socket:site_unavailable | Announcement | %{site, state, actor} | A /ws/gql connection is refused because its site is closed. |
api:socket:no_tenant_refused | Announcement | %{site} | A socket asks for no tenant on a site other than the master site. |
Sign-in and account events
The controller events keep the same capitalised names as the page events. Their payloads are maps that carry the conn, and a plugin that halts the conn takes over where the table says so.
| Event | Kind | Payload | When it fires |
|---|---|---|---|
AuthController.Success.Requested | Transform | %{conn, activity, message}. A halted conn is returned as it is. | A password reset or a sign-in link was asked for over its route with nobody signed in. |
AuthController.Success.Refused | Transform | %{conn, user, message}. A halted conn is returned as it is. | A sign-in succeeded but the site is offline for that account. |
AuthController.Success.BeforeAuth | Transform | %{conn, user, activity}. The returned conn is used. | A sign-in succeeded, before the session is written. |
AuthController.Success.EmailConfirmed | Transform | %{conn, user, message}. The returned message is shown. | The success was an email confirmation. |
AuthController.Success.PasswordReset | Transform | %{conn, user, message} | The success was a password reset. |
AuthController.Success.SignIn | Transform | %{conn, user, message} | The success was a password sign-in or a sign-in link. |
AuthController.Success.AfterAuthentication | Transform | %{conn, user, activity}, activity being :email_confirmation, :password_reset or :sign_in. | After the message was chosen. |
AuthController.Success.BeforeRedirect | Transform | %{conn, user, return_to, message} | Just before the session is written and the visitor is redirected. |
AuthController.Failure.BeforeFailure | Transform | %{conn, activity, reason}. The returned conn is used. | A sign-in or request failed, for any reason except a rate limit. |
AuthController.Failure.Closed | Transform | %{conn, activity, reason, message}. The returned message is shown. | The failure was a closed way in. |
AuthController.Failure.UnconfirmedUser | Transform | %{conn, activity, reason, message} | The account is not confirmed. |
AuthController.Failure.Provider | Transform | %{conn, activity, reason, message}. The returned message is shown. | A sign-in with another service's account did not finish: the visitor cancelled it there, it expired, or the site does not offer it. |
AuthController.Failure.InvalidCredentials | Transform | %{conn, activity, reason, message} | The email or password was wrong. |
AuthController.Failure.BeforeRedirect | Transform | %{conn, message, redirect_to, failure_type} | Just before the visitor goes back to the sign-in page. |
AuthController.SignOut.BeforeSignOut | Transform | %{conn, user_id} | A sign-out request, before the session is cleared. |
AuthController.SignOut.BeforeRedirect | Transform | %{conn, user_id, return_to, message} | Just before the visitor is redirected home. |
auth:request:rate_limited | Transform | The conn. Meta: %{action, key, retry_after, site}. A plugin that halts the conn answers instead of the 429 page. | A request to the auth routes is refused for its rate. |
auth:live:rate_limited | Transform | The socket. Meta: %{action, key, retry_after, site_id}. A plugin that redirects the socket sends the visitor elsewhere. | A sign-in page of a site refuses a visitor for the rate of their tries. |
auth:gate:check | Transform | %{action, site_id, user, email, allowed?}. action is :sign_in, :register, :reset or :magic_link. A plugin sets allowed?. While the plugins cannot run, allowed? is false. | The CMS decides whether a way in is open to someone. |
auth:gate:refused | Announcement | %{action, site_id, email} | The gate kept someone out. |
auth:gate:signed_out | Announcement | %{site_id, user_id, email} | A closed sign-in signed an account out of every device. |
auth:captcha:refused | Announcement | %{form, reason, detail, site_id}. reason is :missing or :failed. | A form is refused for its captcha. |
auth:captcha:unavailable | Announcement | %{form, detail, site_id} | The captcha provider could not be asked, so the form went through. |
auth:email:held | Announcement | %{to, tenant} | An email to an account is held back because its site is closed to it. |
auth:account:confirmed | Announcement | %{user_id, site_id, by}, by the id of the admin. | An admin confirmed an account without its confirmation mail. |
auth:account:withheld | Transform | The Ash query, marked. Meta: %{actor, site_id, withheld}. The query you return is the one that runs. | Someone who is not an admin reads an account through another resource, such as a comment's author. The account's relationships are withheld. |
auth:second_factor:enabled | Announcement | %{user_id, site_id} | An account turned its authenticator on. |
auth:second_factor:disabled | Announcement | %{user_id, site_id, by}, by the id of the admin who turned it off, or nil when the account did. | An authenticator was turned off. |
auth:recovery_codes:generated | Announcement | %{user_id, site_id} | An account made recovery codes. |
auth:recovery_code:used | Announcement | %{user_id, site_id, left} | A recovery code was used to sign in. |
auth:second_factor:refused | Announcement | %{user_id, site_id, reason} | A sign-in was refused for its authenticator code. |
Refusal events
These announcements report a write that the CMS refused. A plugin can log them or raise an alert.
| Event | Kind | Payload | When it fires |
|---|---|---|---|
cms_core:user:master_creation_refused | Announcement | %{action, actor_id, email} | An account was created with no site, which would make a master. Signing up never makes a master. |
runtime:tenant:cross_site_refused | Announcement | %{resource, field, id, tenant, actor_id}. The answer is ignored. | A write names a record of another site, or a record that does not exist. |
runtime:language:delete_refused | Announcement | %{resource, id, site_id, language, translations, message} | Destroying the original edition of a translated record is refused while its translations are live. |
cms_core:media:path_refused | Announcement | %{op, relative_path, site_id}. op is :save, :replace, :archive, :unarchive, :delete, :read or :source. | A media file path falls outside the site's folder, so the file is left where it is. |
Email and notification events
| Event | Kind | Payload | When it fires |
|---|---|---|---|
mail:deliver:before | Transform | %{email, site_id, purpose}. email is a Swoosh.Email. A plugin returns a changed email, or suppress: true to send nothing. | Before an email is recorded and queued. |
mail:delivered | Announcement | %{delivery_id, site_id, purpose, provider, message_id} | The provider accepted an email. |
mail:failed | Announcement | %{delivery_id, site_id, purpose, provider, reason} | An email failed for good, with no retry left. |
mail:not_configured | Announcement | %{delivery_id, site_id, purpose, reason} | A site that has no usable provider was asked to send an email. |
notify:before_deliver | Transform | %{attrs, audience}. A plugin returns a changed attrs, or halts the chain with suppress: true in the payload to stop the delivery. | Before a notification is written for its audience. |
Registry events outside the editor
These events collect lists from extensions. The payload carries the list, and a plugin returns it with its own entries added.
| Event | Kind | Payload | What the list feeds |
|---|---|---|---|
sidebar:menus | Registry | %{groups, items}, the core's sidebar groups and items. A plugin appends its own. | The admin sidebar. |
dashboard:widgets | Registry | %{widgets}, starting empty. A plugin appends a widget descriptor. | The cards of the admin dashboard. |
settings:register | Registry | %{definitions}, starting empty. A plugin appends setting definitions. | The Settings screen. |
account:security:cards | Registry | %{cards}, the core's two cards. A plugin appends %{id, priority, module}. | The cards of the account security page. |
mail:providers | Registry | %{providers}, starting empty. A plugin appends its provider module. | The providers of the Email tab. |
captcha:providers | Registry | %{providers}, starting empty. A plugin appends its provider module. | The providers of the captcha setting. |
auth:providers | Registry | %{providers}, starting empty. A plugin appends its provider module. | The providers of the Sign-in methods table on the Security tab of Settings. The core names none: each provider ships as a package of its own. |
runtime:translations:used_keys | Registry | %{site_id, keys}, keys starting empty. site_id is a site's id, or nil when the global words are checked. A plugin appends the words it looks up. An entry that is not a string is dropped. | The Used column of the Translations screen and Remove unused. A word named here is in use, so Remove unused keeps it. See Translate an extension's words. |
runtime:share_card:targets | Registry | %{site_id, force, producer, names_only, targets}, targets a map starting empty. A plugin adds its own entry under its app name. | The share cards that the Redraw buttons and the sweep look at. |
runtime:share_card:swept | Announcement | %{site_id, removed} | Old share card files were deleted. A plugin forgets those names. |
Document extension events
| Event | Kind | Payload | When it fires |
|---|---|---|---|
document:page:extras | Transform | %{site_id, document, version, page, header, tabs}, header and tabs starting empty. document is %{slug, path, language}, and page is nil on a document's landing. A plugin appends facts to header and tabs to tabs. | The doc-section block reads a page of a document or a document's landing. doc-section-cards asks once for each card, with card: true added to the payload. |
document:version:cloned | Announcement | %{version_id, source_version_id, workspace_id, site_id, pages, notes, tags}. Meta: %{actor, site_id}. | A version of a document was cloned from another. |
document:version:detached | Announcement | %{version_id, source_version_id, workspace_id, site_id, pages}. Meta: %{actor, site_id}. | A cloned version was made independent of its source. |
document:workspace:admin_actions | Registry | The context %{site_id, workspace, version} plus actor and actions, starting empty. A plugin appends %{id, label, component}, with an optional icon and assigns. | The document screen draws its buttons. Each action opens a modal with the component. |