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:

elixir
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
end

The CMS passes these options when it fires an event.

OptionMeaning
returnWhen it is set, such as return: false, the plugins run and the call site gets its own payload back. The event is an announcement.
metaRead-only context for the plugins that define call/2. It is never part of the answer.
cannot_runThe 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.

KindMeaning
AnnouncementThe event fires with return: false, or the CMS ignores the answer. Nothing waits for a plugin.
TransformThe 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.
RegistryThe payload carries a list that already holds the core's entries. A plugin appends its own. A repeated key keeps the core entry.
GateA 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.

EventKindPayloadWhen it fires
<prefix>:<resource>:after_<action>AnnouncementThe 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_saveAnnouncementThe 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>TransformThe 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>TransformThe 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_contentTransform%{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.

PrefixResources
cms_corepermission, 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
blogauto_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
documentissue, 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.

EventFires before
cms_core:page:before_createA page is created.
cms_core:user:before_createAn account is created.
blog:blog_post:before_createA blog post is created.
blog:comment:before_create_publicA visitor's blog comment is created.
document:section:before_createA document section is created.

The query_ events exist on these read actions only.

EventFires before
cms_core:page:query_read_anyThe read_any action of pages runs.
cms_core:user:query_tenant_readThe tenant_read action of accounts runs.
blog:blog_post:query_publishedThe published action of blog posts runs.
blog:comment:query_public_listThe public_list action of blog comments runs.
blog:blogger:query_public_listThe public_list action of bloggers runs.
blog:blogger:query_public_by_slugThe public_by_slug action of bloggers runs.
blog:team:query_public_listThe public_list action of teams runs.
blog:team:query_public_by_slugThe public_by_slug action of teams runs.
document:section:query_public_listThe 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.

EventKindPayload keyWhat the list feeds
builder:componentsRegistryentriesThe Components tab of the palette.
builder:blocksRegistryblocksThe sections of the Blocks tab. Contributions come after the core's, in order.
builder:elementsRegistryelementsThe elements of the Blocks tab. Contributions come after the core's, in order.
builder:inspector:sectionsRegistrysectionsThe sections of the Inspector.
builder:railsRegistrytabsThe tabs of the right-hand panel.
builder:studio:tabsRegistrytabsThe tabs of the Studio.
builder:canvas:toolbarRegistryactionsThe buttons of the toolbar above the selected element. Pressing a plugin's button fires builder:canvas:toolbar:invoked.
builder:commandsRegistrycommandsThe Actions list of the command palette.
builder:audit:checksRegistrychecksThe checks of the Audit tab.
builder:page_startersRegistrystructuresThe cards of the starter chooser. Contributions come after the core's, in order.
builder:icon:librariesRegistrylibrariesThe icon sets of the icon picker. Contributions come after the core's, in order.
builder:interactionsRegistryactionsThe actions of the Interactions section.
builder:conditionsRegistryrulesThe rules of the Show-when section.
builder:js_hooksRegistryhooksThe hooks of the hook picker.
builder:mention:sourcesRegistrysourcesThe sources of @-mentions in review notes.

Transforms

EventKindPayloadWhen 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:serializeTransform%{heex, meta}. meta is %{section, stage}. The returned heex is saved.On every save and publish, before the gates.
builder:history:queryTransform%{source_type, source_id, branch_id, query}. The returned Ash query is read.When the history sheet reads the timeline of a record.
builder:containmentTransform%{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:documentTransform%{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:examplesTransform%{examples, entry, site_id}.Before the wizard shows the examples of a component.
builder:preset:choicesTransform%{choices, site_id}.When the editor builds the preset choices for the attributes of a component.
builder:token:before_setTransform%{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_askTransform%{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:roleTransform%{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:submittedTransform%{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.

EventKindPayloadWhen it fires
builder:save:beforeGate%{tree, serialized, resource_id, section, error}Before the document is written at a stage other than published.
builder:publish:beforeGate%{tree, serialized, resource_id, section, error}Before a publish.
builder:draft:before_saveGate%{section, resource_id, draft_id, label, serialized, site_id, actor_id, error}Before a draft is written.
builder:review:before_commentGate%{body, type, section, node_id, parent_comment_id, error}Before a review note is written.
builder:code:before_applyGate%{source, tab, node_id, section, resource_id, site_id, actor_id, error}Before hand-written source is applied to the document.
builder:class:before_saveGate%{name, utilities, site_id, actor_id, error}Before a saved class is written.
builder:element_css:before_saveGate%{node_id, css, owner, site_id, actor_id, error}Before the own CSS of an element is written.

Announcements

EventKindPayloadWhen it fires
builder:session:after_openAnnouncement%{token, section, resource_id, site_id, actor_id}An editing session opens.
builder:session:after_closeAnnouncement%{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:afterAnnouncement%{record_id, section, stage} with stage set to :draftAfter a draft write.
builder:publish:afterAnnouncement%{record_id, section, stage} with stage set to :publishedAfter a publish.
builder:draft:after_saveAnnouncementThe payload of builder:draft:before_save with draft_id set to the saved draftAfter a draft was written.
builder:review:after_commentAnnouncement%{id, body, type, node_id, reply?, section, source_id, site_id, actor_id}After a review note was saved.
builder:element_css:after_saveAnnouncement%{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:invokedAnnouncement%{action, node_id, token}A plugin's toolbar button is pressed. The plugin applies its own changes through the token.
builder:form:after_renameAnnouncement%{node_id, from, to, token, change, submit, section, resource_id, site_id, actor_id}A form is renamed in the Inspector.
builder:form:after_customizeAnnouncement%{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_createAnnouncement%{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_settingAnnouncement%{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.

EventKindPayloadWhen it fires
runtime:compile:before_page and runtime:compile:after_pageAnnouncementThe 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_layoutAnnouncementThe layout's fields. Meta: %{title, site}.Before and after the module of a layout is built.
runtime:compile:before_component and runtime:compile:after_componentAnnouncementThe 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_pageAnnouncementThe error page's fields. Meta: %{title, site}.Before and after the module of an error page is built.
runtime:compile:before_moduleAnnouncement%{file}. Meta: %{file}.Before a built module is compiled and loaded.
runtime:compile:after_moduleAnnouncement%{file}. Meta: %{file, module}.After a module compiled without error.
runtime:compile:cache_hitAnnouncement%{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_missAnnouncement%{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_compileAnnouncement%{module}After a page, layout, component or error page module compiled cleanly.
runtime:compile:offline_page_failedAnnouncement%{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_compileAnnouncement%{resource, action, id, site}The compile worker starts the job for one saved or deleted record.
runtime:after_compileAnnouncement%{args, result}. args is the payload of runtime:before_compile.The job ended. result may be {:error, reason}.
runtime:compile_gate:acquiredAnnouncement%{at: :acquire}A compile takes one of the limited compile slots.
runtime:compile_gate:releasedAnnouncement%{at: :release}A compile gives its slot back.
runtime:compile:before_cssAnnouncement%{site}. Meta: %{site}.Before a site's stylesheet bundle is built.
runtime:compile:cssTransformThe 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_jsAnnouncement%{site}. Meta: %{site}.Before a site's JavaScript bundle is built.
runtime:compile:jsTransformThe 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_jsTransformThe 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_skippedAnnouncement%{hook, site, reason}. Meta: %{site}.A JS hook is left out of a site's bundle.
runtime:compile:content_componentsTransform%{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_sitemapAnnouncement%{site}. Meta: %{site}.Before a site's sitemap is built.
runtime:compile:after_sitemapAnnouncement%{site, urls, files}. Meta: %{site}.After the sitemap is built and stored.
seo:sitemap:urlsTransform%{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_prefetchTransform%{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_prefetchAnnouncement%{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_syncAnnouncement%{scope, kinds}Before and after a bulk change rebuilds components, JS hooks and stylesheets at once.

Site and kit events

EventKindPayloadWhen it fires
runtime:boot:before_compile_site and runtime:boot:after_compile_siteAnnouncement%{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_rebuildAnnouncement%{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_teardownAnnouncement%{site_id, site_name, modules}, where modules is a count.Before a deleted site's runtime is removed.
runtime:site:after_teardownAnnouncement%{site_id, site_name, counts}. counts is %{modules, assets, routes}.After it is removed.
runtime:site:before_purgeAnnouncement%{site_name, remove_templates?}Before a site's modules, cached bundles and routing are freed from memory.
runtime:site:after_purgeAnnouncement%{modules, assets, routes, site_name}After they are freed.
runtime:site:before_auth_pagesAnnouncement%{site_id, site_name}Before a site's sign-in layout, pages and words are written.
runtime:site:after_auth_pagesAnnouncement%{site_id, layout_id, layouts, pages, renamed_pages, skipped_pages, translations}After they are written.
runtime:ui_kit:before_installAnnouncement%{bundle_path, site_id, selection}Before a UI kit is installed.
runtime:ui_kit:after_installAnnouncement%{site_id, bundle, kinds}After a UI kit is installed and the runtime rebuilt.
runtime:ui_kit:before_uninstallAnnouncement%{bundle_path, site_id}Before a UI kit is removed.
runtime:ui_kit:after_uninstallAnnouncement%{site_id, kit}After a UI kit is removed.
installer:languages:savedAnnouncement%{site_id, languages}The install wizard saved a site's languages.
installer:extension:installTransform%{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:manifestTransform%{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_iconsAnnouncement%{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.

EventKindPayloadWhen it fires
runtime:state:site:changedAnnouncement%{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_changedAnnouncement%{key}. key is a site id, or nil for every site.Whether a site is open may have changed.
runtime:state:assets:changedAnnouncement%{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:changedAnnouncement%{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:changedAnnouncement%{op, key}. op is :save or :delete, and key is the rule id.A redirect rule is stored or removed.
runtime:state:viewer:changedAnnouncement%{op, key}. op is :save or :delete, and key is the user id.A signed-in viewer is stored or dropped.
runtime:state:sidebar:changedAnnouncement%{key}. key is a site id, or nil for the global settings.The sidebar settings changed.
runtime:state:language_group:changedAnnouncement%{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.

EventKindPayloadWhen it fires
RuntimeLive.OnMount.BeforeMountAnnouncement[params, session, socket]A database page mounts, once the address is known to be served.
RuntimeLive.OnMount.BeforePathTransformTransform%{path, site}, path a list of segments. The returned path picks the page.After BeforeMount, before the page is looked up.
RuntimeLive.OnMount.AclAfterCompileTransformThe 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.AfterCompileTransformThe socket with the page assigned. The returned socket is used.The visitor is admitted to the page.
RuntimeLive.OnMount.AfterCompileErrorAnnouncementThe reason.Compiling or admitting the page failed. The mount then raises a server error.
RuntimeLive.OnMount.ReturnTransform{:ok, socket, layout: {...}}. The returned tuple is the mount's answer.The last step of mount.
RuntimeLive.OnRender.BeforeRenderTransformThe 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.AfterCompileTransformWhat the page module rendered. The returned value is drawn.After the page module renders.
RuntimeLive.OnRender.AfterCompileErrorAnnouncementThe reason.The page module could not be compiled when it was rendered.
RuntimeLive.HandleParams.BeforeParamsAnnouncement[params, url, socket]A live navigation or patch reaches a database page.
RuntimeLive.HandleParams.BeforePathTransformTransform%{path, site}. The returned path picks the page.After BeforeParams.
RuntimeLive.HandleParams.AclAfterCompileTransformThe compile result. While the plugins cannot run, the answer is {:error, :cannot_run}.As RuntimeLive.OnMount.AclAfterCompile, for a live navigation.
RuntimeLive.HandleParams.AfterCompileTransformThe socket. The returned socket is used.The visitor is admitted to the page the navigation reached.
RuntimeLive.HandleParams.AfterCompileErrorAnnouncementThe reason.Compiling or admitting the page failed during navigation.
RuntimeLive.HandleParams.BeforeAllParamsAnnouncement[params, url, socket]A handle_params call for an address that is not a page path.
RuntimeLive.HandleEvent.BeforeEventTransformThe 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.AfterCallEventTransformWhat the page's handle_event returned.After the page handled the event.
RuntimeLive.HandleEvent.ReturnCallEventTransformThe socket from {:noreply, socket}.When handle_event returned {:noreply, socket}.
RuntimeLive.HandleEvent.AfterCallEventErrorAnnouncementThe invalid return value.handle_event returned something other than {:noreply, socket}.
RuntimeLive.HandleInfo.BeforeInfoTransformThe assigns. The returned map must keep page_module, private.site and page.path.A message reaches a database page.
RuntimeLive.HandleInfo.AfterCallInfoTransformWhat the page's handle_info returned.After the page handled the message.
RuntimeLive.HandleInfo.ReturnCallInfoTransformThe socket from {:noreply, socket}.When handle_info returned {:noreply, socket}.
RuntimeLive.HandleInfo.AfterCallInfoErrorAnnouncementThe invalid return value.handle_info returned something other than {:noreply, socket}.
RuntimeLive.RuntimeLayout.BeforeLayoutAnnouncementThe assigns.Before the layout of a site renders around a page.
RuntimeLive.RuntimeLayout.AfterCompileTransformWhat the layout rendered.After the layout rendered.
RuntimeLive.RuntimeLayout.AfterCompileErrorAnnouncementThe reason or the exception.The layout failed. The default layout is used.
runtime:page:visitors_only_redirectedTransformThe 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_redirectedTransformThe 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.BeforeRenderTransform%{conn, status_code, template}. The returned conn is used.An error page starts to render.
ErrorHTML.Render.BeforeCompileTransform%{path, site, status_code}. The returned map picks the error page.Before the error page module is found.
ErrorHTML.Render.AfterCompileTransform%{conn, content}. The returned content is sent.The error page rendered.
ErrorHTML.Render.CompileErrorTransform%{error, template, fallback}. The returned fallback is sent.The error page could not be compiled.
RootHtml.MetaTagsTransform%{html, assigns}, html starting as an empty string. The returned html goes into the head.The root layout renders the head.
RootHtml.InlineScriptTransform%{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.ExternalAssetsTransform%{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_unavailableTransformAn 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:crashedAnnouncement%{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_componentTransform%{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_paletteTransform%{site_id, components}. Meta: %{current_user}. The returned components are offered.The content editor builds its component palette.
runtime:content:editor_blocksTransform%{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.

EventKindPayloadWhen it fires
runtime:page:sourceTransform%{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:frameTransform%{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

EventKindPayloadWhen it fires
runtime:page:seoTransform%{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:recordsTransform%{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:linesTransform%{site, lines}. The returned lines are written to robots.txt.A request for /robots.txt.
web:request:sitemap_unavailableTransformThe 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:seoTransformThe 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

EventKindPayloadWhen it fires
web:request:before_site_resolveTransformThe conn.A request starts, before the site is found from the host.
web:request:after_site_resolveTransformThe conn. Meta: %{host}.After the site was found or refused.
web:request:site_unavailableTransformThe 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_pageTransformThe 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_refusedTransformThe 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_refusedTransformThe conn. Meta: %{site, user_id}.A session that carries the wrong site is signed out of the request.
web:request:before_permission_gateTransformThe conn.A route that requires a permission starts to check it.
web:request:permission_gateTransformThe conn the check answered with. Meta: %{authorized}.After the permission check.
web:request:before_responseTransformThe conn.The security headers plug starts.
web:response:before_security_headersTransformThe conn, with the headers set.Before the security headers are sent.
web:request:redirectedTransformThe conn, with the status and location set. Meta: %{site, from, to, status}.A redirect rule answers the request.
web:request:admin_language_chosenTransformThe conn. Meta: %{language}.An admin chooses the interface language.
web:request:asset_unavailableTransformThe conn. Meta: %{site, type, reason}.A site's CSS or JS bundle is refused.
web:request:preview_unavailableTransformThe conn. Meta: %{site_id, reason}.A draft preview is refused because its site is closed to the viewer.
web:request:upload_unavailableTransformThe conn. Meta: %{state, site_id}.An upload file is refused because its site is closed to the viewer.
web:live:site_unavailableTransformThe 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_unavailableAnnouncement%{site_id, state, resource}A draft preview LiveView is refused.
AssetsController.Serve.BeforeServeTransform%{conn, site, type}. The returned conn is used.A site's bundle is requested.
AssetsController.Serve.BeforeAssetLookupTransform%{site, key, type, encoding}Before the bundle is read from the cache. encoding is br, gzip or nil.
AssetsController.Serve.AfterAssetLookupTransform%{content, encoding}After the cache was read.
AssetsController.Serve.BeforeResponseTransform%{conn, content, encoding, type, headers}. The returned map is sent. A nil content is refused as missing.Before the bundle is sent.

API requests

EventKindPayloadWhen it fires
api:request:before_corsTransformThe conn.The CORS plug of the API starts.
api:request:cors_headersTransformThe conn. Meta: %{origin}.After the CORS headers were set.
api:request:before_tenant_resolveTransformThe conn.The API looks up the site from the host.
api:request:after_tenant_resolveTransformThe conn. Meta: %{host}.After the site was resolved or refused.
api:request:site_unavailableTransformThe 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_refusedTransformThe 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_refusedTransformThe conn. Meta: %{site, user_id}.A bearer token that belongs to another scope is dropped from the request.
api:socket:site_unavailableAnnouncement%{site, state, actor}A /ws/gql connection is refused because its site is closed.
api:socket:no_tenant_refusedAnnouncement%{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.

EventKindPayloadWhen it fires
AuthController.Success.RequestedTransform%{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.RefusedTransform%{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.BeforeAuthTransform%{conn, user, activity}. The returned conn is used.A sign-in succeeded, before the session is written.
AuthController.Success.EmailConfirmedTransform%{conn, user, message}. The returned message is shown.The success was an email confirmation.
AuthController.Success.PasswordResetTransform%{conn, user, message}The success was a password reset.
AuthController.Success.SignInTransform%{conn, user, message}The success was a password sign-in or a sign-in link.
AuthController.Success.AfterAuthenticationTransform%{conn, user, activity}, activity being :email_confirmation, :password_reset or :sign_in.After the message was chosen.
AuthController.Success.BeforeRedirectTransform%{conn, user, return_to, message}Just before the session is written and the visitor is redirected.
AuthController.Failure.BeforeFailureTransform%{conn, activity, reason}. The returned conn is used.A sign-in or request failed, for any reason except a rate limit.
AuthController.Failure.ClosedTransform%{conn, activity, reason, message}. The returned message is shown.The failure was a closed way in.
AuthController.Failure.UnconfirmedUserTransform%{conn, activity, reason, message}The account is not confirmed.
AuthController.Failure.ProviderTransform%{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.InvalidCredentialsTransform%{conn, activity, reason, message}The email or password was wrong.
AuthController.Failure.BeforeRedirectTransform%{conn, message, redirect_to, failure_type}Just before the visitor goes back to the sign-in page.
AuthController.SignOut.BeforeSignOutTransform%{conn, user_id}A sign-out request, before the session is cleared.
AuthController.SignOut.BeforeRedirectTransform%{conn, user_id, return_to, message}Just before the visitor is redirected home.
auth:request:rate_limitedTransformThe 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_limitedTransformThe 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:checkTransform%{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:refusedAnnouncement%{action, site_id, email}The gate kept someone out.
auth:gate:signed_outAnnouncement%{site_id, user_id, email}A closed sign-in signed an account out of every device.
auth:captcha:refusedAnnouncement%{form, reason, detail, site_id}. reason is :missing or :failed.A form is refused for its captcha.
auth:captcha:unavailableAnnouncement%{form, detail, site_id}The captcha provider could not be asked, so the form went through.
auth:email:heldAnnouncement%{to, tenant}An email to an account is held back because its site is closed to it.
auth:account:confirmedAnnouncement%{user_id, site_id, by}, by the id of the admin.An admin confirmed an account without its confirmation mail.
auth:account:withheldTransformThe 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:enabledAnnouncement%{user_id, site_id}An account turned its authenticator on.
auth:second_factor:disabledAnnouncement%{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:generatedAnnouncement%{user_id, site_id}An account made recovery codes.
auth:recovery_code:usedAnnouncement%{user_id, site_id, left}A recovery code was used to sign in.
auth:second_factor:refusedAnnouncement%{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.

EventKindPayloadWhen it fires
cms_core:user:master_creation_refusedAnnouncement%{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_refusedAnnouncement%{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_refusedAnnouncement%{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_refusedAnnouncement%{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

EventKindPayloadWhen it fires
mail:deliver:beforeTransform%{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:deliveredAnnouncement%{delivery_id, site_id, purpose, provider, message_id}The provider accepted an email.
mail:failedAnnouncement%{delivery_id, site_id, purpose, provider, reason}An email failed for good, with no retry left.
mail:not_configuredAnnouncement%{delivery_id, site_id, purpose, reason}A site that has no usable provider was asked to send an email.
notify:before_deliverTransform%{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.

EventKindPayloadWhat the list feeds
sidebar:menusRegistry%{groups, items}, the core's sidebar groups and items. A plugin appends its own.The admin sidebar.
dashboard:widgetsRegistry%{widgets}, starting empty. A plugin appends a widget descriptor.The cards of the admin dashboard.
settings:registerRegistry%{definitions}, starting empty. A plugin appends setting definitions.The Settings screen.
account:security:cardsRegistry%{cards}, the core's two cards. A plugin appends %{id, priority, module}.The cards of the account security page.
mail:providersRegistry%{providers}, starting empty. A plugin appends its provider module.The providers of the Email tab.
captcha:providersRegistry%{providers}, starting empty. A plugin appends its provider module.The providers of the captcha setting.
auth:providersRegistry%{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_keysRegistry%{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:targetsRegistry%{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:sweptAnnouncement%{site_id, removed}Old share card files were deleted. A plugin forgets those names.

Document extension events

EventKindPayloadWhen it fires
document:page:extrasTransform%{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:clonedAnnouncement%{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:detachedAnnouncement%{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_actionsRegistryThe 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.