Runtime registry of all PhoenixKit modules (internal and external).
External modules are auto-discovered from beam files via PhoenixKit.ModuleDiscovery.
Any dep that depends on :phoenix_kit and uses use PhoenixKit.Module is found
automatically — no config line needed.
External Module Registration
External hex packages are auto-discovered. Just add the dep:
{:phoenix_kit_hello_world, "~> 0.1.0"}For explicit registration (backwards compatible):
config :phoenix_kit, :modules, [PhoenixKitHelloWorld]Runtime Registration
PhoenixKit.ModuleRegistry.register(MyModule)
PhoenixKit.ModuleRegistry.unregister(MyModule)Query API
ModuleRegistry.all_modules() # All registered module atoms
ModuleRegistry.enabled_modules() # Only currently enabled
ModuleRegistry.all_admin_tabs() # Collect admin tabs from all modules
ModuleRegistry.all_settings_tabs() # Collect settings tabs
ModuleRegistry.all_email_settings_sections() # Collect Emails Transactional page sections
ModuleRegistry.all_email_templates() # Collect emails for the admin email preview
ModuleRegistry.all_user_dashboard_tabs() # Collect user dashboard tabs
ModuleRegistry.all_children() # Collect supervisor child specs
ModuleRegistry.all_permission_metadata() # Collect permission metadata
ModuleRegistry.feature_enabled_checks() # Build {mod, :enabled?} map
ModuleRegistry.get_by_key("tickets") # Find module by key
ModuleRegistry.get_module_key_for_namespace("PhoenixKitTickets")
# Resolve top-level namespace → key
Summary
Functions
Collect all admin tabs from all registered modules.
Collect all supervisor child specs from all registered modules.
Collect email settings sections contributed by all enabled modules,
for the core Emails Transactional settings page
(/admin/settings/email-sending).
Collect the emails enabled modules send (PhoenixKit.Module.email_templates/0),
each tagged with :module, the module that declared it.
Returns all feature module key strings from registered modules.
The kinds of job run (PhoenixKit.Jobs.Kind) the enabled modules declare
with PhoenixKit.Module.job_kinds/0. A kind that is not a loaded module is
left out, with one warning.
Collect media reorganizer source modules contributed by all enabled modules.
Returns all registered module atoms.
Collect permission metadata from all registered modules.
Collect top-level route path segments reserved by all installed modules.
Collect route modules from all registered modules.
Collect all settings tabs from all registered modules.
Collect sitemap source modules contributed by all enabled modules.
Collect all user dashboard tabs from all registered modules.
Returns a specification to start this module under a supervisor.
Returns dependency warnings for the Modules page.
Returns all registered modules that are currently enabled.
Build a feature_enabled_checks map from registered modules.
Find a registered module by its key string.
Find a registered module's key by matching a top-level Elixir namespace.
Check if the registry has been initialized.
Returns the full catalog of known external PhoenixKit packages.
Collect modules that have versioned migrations.
Returns known external PhoenixKit packages that are not currently installed.
Returns permission descriptions map from registered modules.
Returns the optional gettext {backend, domain} pairs registered modules
declare for translating their permission labels (see
PhoenixKit.Module.permission_meta/0). Modules without a
gettext_backend are absent from the map.
Returns permission icons map from registered modules.
Returns permission labels map from registered modules.
Register a module that implements PhoenixKit.Module behaviour.
Rescan beam files and absorb any external modules that weren't known at registry init.
Run every enabled module's migrate_legacy/0 callback.
Collect supervisor child specs from the static module list.
Returns the sub-permissions declared by registered modules, keyed by the
parent module key. Each entry carries the COMPOSED dotted key
("calendar.view_others") plus its label and description.
Unregister a module.
Whether a module base permission key is well-formed (lowercase, no dots, within length). A dotted base key would collide with composed sub-keys, so the permission plumbing ignores base keys that fail this check.
Functions
@spec all_admin_tabs() :: [PhoenixKit.Dashboard.Tab.t()]
Collect all admin tabs from all registered modules.
Note: iterates all modules and calls admin_tabs/0 on each call.
For cached access in rendering paths, use PhoenixKit.Dashboard.Registry.get_admin_tabs/0.
@spec all_children() :: [Supervisor.child_spec()]
Collect all supervisor child specs from all registered modules.
@spec all_email_settings_sections() :: [PhoenixKit.Module.email_settings_section()]
Collect email settings sections contributed by all enabled modules,
for the core Emails Transactional settings page
(/admin/settings/email-sending).
Mirrors all_settings_tabs/0's safe_call pattern, but — like
all_sitemap_sources/0 — iterates enabled_modules/0 rather than
all_modules/0: a disabled module must not inject a live_component
that assumes its own supervised state is running.
@spec all_email_templates() :: [map()]
Collect the emails enabled modules send (PhoenixKit.Module.email_templates/0),
each tagged with :module, the module that declared it.
Enabled modules only, like all_email_settings_sections/0: a disabled
module sends nothing, so the preview has none of its emails to show.
@spec all_feature_keys() :: [String.t()]
Returns all feature module key strings from registered modules.
@spec all_job_kinds() :: [module()]
The kinds of job run (PhoenixKit.Jobs.Kind) the enabled modules declare
with PhoenixKit.Module.job_kinds/0. A kind that is not a loaded module is
left out, with one warning.
@spec all_media_reorganizers() :: [module()]
Collect media reorganizer source modules contributed by all enabled modules.
Each entry implements PhoenixKit.Modules.Storage.Reorganizer.Source.
Iterates enabled_modules/0, like all_sitemap_sources/0: a disabled
module's records must not be touched by a reorganizer run.
@spec all_modules() :: [module()]
Returns all registered module atoms.
@spec all_permission_metadata() :: [PhoenixKit.Module.permission_meta()]
Collect permission metadata from all registered modules.
@spec all_reserved_route_prefixes() :: [String.t()]
Collect top-level route path segments reserved by all installed modules.
Each entry is a literal path segment (no slashes, e.g. "legal") a module
owns for its own LiveViews/controllers. A dispatcher that routes requests
based on database-driven path segments (e.g. Publishing's group catch-all)
should treat any segment in this list as NOT its own, even if it happens to
have matching data, so it doesn't swallow a route another module owns.
Declaring a prefix is passive — it changes nothing unless a dispatcher
actively consults this function.
Iterates all_modules/0, not enabled_modules/0 (unlike
all_sitemap_sources/0): the route this guards against is normally
compiled into the host's router (e.g. live "/legal", LegalLive), which
exists independent of the owning module's runtime enabled/disabled
Settings toggle. Gating on enabled_modules/0 would drop the reservation
the moment a module is disabled while its data (and the host's compiled
route) still exist, reopening the exact hijack this is meant to prevent
for as long as the module stays disabled.
Each module's returned segments are trimmed of leading/trailing slashes
and non-list/non-string returns are dropped, so a malformed
reserved_route_prefixes/0 implementation (e.g. returning "/legal" or a
bare string instead of a list) can't silently defeat the reservation or
crash this per-request-hot-path aggregation.
@spec all_route_modules() :: [module()]
Collect route modules from all registered modules.
@spec all_settings_tabs() :: [PhoenixKit.Dashboard.Tab.t()]
Collect all settings tabs from all registered modules.
@spec all_sitemap_sources() :: [module()]
Collect sitemap source modules contributed by all enabled modules.
Each entry implements PhoenixKit.Modules.Sitemap.Sources.Source. The
sitemap Generator appends these to its base source list (deduplicated)
so module-owned content appears in the sitemap with no host config.
Iterates enabled_modules/0, so a disabled module contributes nothing — even
in flat-sitemap mode, where the Generator deliberately force-collects every
source and bypasses each source's own enabled?/0 (force: true, see
Sources.Source.safe_collect/2 and Generator flat-mode generation). Gating
at the module level here keeps the "disabled module emits no URLs" guarantee
in both modes. A contributed source's own enabled?/0 remains a secondary
gate in index mode (see Generator.generate_module/2).
@spec all_user_dashboard_tabs() :: [PhoenixKit.Dashboard.Tab.t()]
Collect all user dashboard tabs from all registered modules.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec dependency_warnings() :: [map()]
Returns dependency warnings for the Modules page.
Each warning is a map: %{module: module(), module_name: String.t(), requires_key: String.t()}
Called on Modules page render — computes live (not hot path).
@spec enabled_modules() :: [module()]
Returns all registered modules that are currently enabled.
Build a feature_enabled_checks map from registered modules.
Returns %{"storage" => {PhoenixKit.Modules.Storage, :enabled?}, ...}
Find a registered module by its key string.
Find a registered module's key by matching a top-level Elixir namespace.
Used by the admin permission layer to resolve a plugin LiveView's namespace
(e.g. "PhoenixKitEntities" from PhoenixKitEntities.Web.Entities) to the
plugin's permission key (e.g. "entities").
Returns the key string or nil when no registered module matches.
@spec initialized?() :: boolean()
Check if the registry has been initialized.
@spec known_external_packages() :: [map()]
Returns the full catalog of known external PhoenixKit packages.
Fetches live from Hex.pm with a 10-minute in-memory cache.
Merged with any entries from config :phoenix_kit, extra_known_packages: [...].
Config entries take precedence over Hex entries when the package field collides.
Collect modules that have versioned migrations.
Returns a list of {module_name, migration_module} tuples for all registered
modules that implement migration_module/0 and return a non-nil value.
@spec not_installed_packages() :: [map()]
Returns known external PhoenixKit packages that are not currently installed.
Used by the admin Modules page to inform users about available packages they can add as dependencies.
Returns permission descriptions map from registered modules.
Returns the optional gettext {backend, domain} pairs registered modules
declare for translating their permission labels (see
PhoenixKit.Module.permission_meta/0). Modules without a
gettext_backend are absent from the map.
Returns permission icons map from registered modules.
Returns permission labels map from registered modules.
@spec register(module()) :: :ok
Register a module that implements PhoenixKit.Module behaviour.
@spec rescan() :: {:ok, [module()]}
Rescan beam files and absorb any external modules that weren't known at registry init.
Returns {:ok, new_modules} — the freshly-absorbed module atoms (often
[] after the first call). Safe to call repeatedly.
Intended use: parent app calls this from Application.start/2 after
Supervisor.start_link/2 so late-loading :phoenix_kit_<x> deps
whose beams are only available after PhoenixKit's own supervision tree
is up get picked up deterministically — without timer-based polling.
mix phoenix_kit.install and mix phoenix_kit.update wire this call
in automatically; existing apps can call it manually.
Also useful in dev for hot-reload recovery after recompiling a module package, or in tests that dynamically load fixture modules.
Run every enabled module's migrate_legacy/0 callback.
Iterates registered modules, calls migrate_legacy/0 on each that
implements it, swallows per-module errors so the host-app boot can't
be taken down by a flaky migration. Each module's implementation is
expected to be idempotent (safe to re-run on every boot).
Activity logging happens inside each module's migrate_legacy/0 —
this orchestrator only logs the per-module pass/fail outcome to the
Logger, not to Activity.
PhoenixKit.boot/1 calls it — pipe the supervisor result into that from
the host's Application.start/2 (mix phoenix_kit.install / update wire
it in). A host that still calls this function itself after
Supervisor.start_link/2 gets the same behaviour.
Does nothing with update_mode on (mix phoenix_kit.update and
mix phoenix_kit.doctor start the host that way) and returns %{}. The
callbacks are one-shot data transitions whose "already done" checks
usually read PhoenixKit.Settings, which answers nil in that mode while
writes still go through — so they redid their work on every update or
doctor run (granting back a permission an Owner had revoked, re-stamping
markers). The update task also starts the host before it migrates, so
they would run against the old schema. The host's next ordinary start
runs them. The check is here, not at a call site, so boot/1, a direct
call and the deprecated PhoenixKit.Integrations.run_legacy_migrations/0
are all covered.
Returns a summary map: %{module_atom => :ok | {:error, term()}}.
@spec static_children() :: [Supervisor.child_spec()]
Collect supervisor child specs from the static module list.
This does NOT require the GenServer to be running, making it safe to call from the PhoenixKit.Supervisor init (before the registry starts).
Returns the sub-permissions declared by registered modules, keyed by the
parent module key. Each entry carries the COMPOSED dotted key
("calendar.view_others") plus its label and description.
Malformed declarations (bad key format, missing fields, key over 50 chars, duplicate keys within one module) are dropped with a logged warning rather than raising — a broken module must not take down permission resolution for everyone else.
@spec unregister(module()) :: :ok
Unregister a module.
Whether a module base permission key is well-formed (lowercase, no dots, within length). A dotted base key would collide with composed sub-keys, so the permission plumbing ignores base keys that fail this check.