# `PhoenixKit.Module`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.102/lib/phoenix_kit/module.ex#L1)

Behaviour for PhoenixKit feature modules (internal and external).

Any module that implements this behaviour can register itself with PhoenixKit's
tab registry, permission system, supervisor tree, and route system.

## Usage

    defmodule PhoenixKitHelloWorld do
      use PhoenixKit.Module

      @impl true
      def module_key, do: "hello_world"

      @impl true
      def module_name, do: "Hello World"

      @impl true
      def enabled?, do: PhoenixKit.Settings.get_boolean_setting("hello_world_enabled", false)

      @impl true
      def enable_system do
        PhoenixKit.Settings.update_boolean_setting_with_module("hello_world_enabled", true, "hello_world")
      end

      @impl true
      def disable_system do
        PhoenixKit.Settings.update_boolean_setting_with_module("hello_world_enabled", false, "hello_world")
      end

      @impl true
      def permission_metadata do
        %{
          key: "hello_world",
          label: "Hello World",
          icon: "hero-hand-raised",
          description: "A demo module"
        }
      end

      @impl true
      def admin_tabs do
        [%PhoenixKit.Dashboard.Tab{
          id: :admin_hello_world,
          label: "Hello World",
          icon: "hero-hand-raised",
          path: "hello-world",
          priority: 640,
          level: :admin,
          permission: "hello_world",
          match: :prefix,
          group: :admin_modules
        }]
      end
    end

## Required Callbacks

- `module_key/0` - Unique string key (e.g., `"tickets"`, `"billing"`)
- `module_name/0` - Human-readable display name
- `enabled?/0` - Whether the module is currently enabled
- `enable_system/0` - Enable the module system-wide
- `disable_system/0` - Disable the module system-wide

## Optional Callbacks

All optional callbacks have sensible defaults provided by `use PhoenixKit.Module`:

- `get_config/0` - Module stats/config map (default: `%{enabled: enabled?()}`).
  The default calls `enabled?()` which may hit the database. External modules
  with expensive config should override this with a cached implementation.
- `permission_metadata/0` - Permission key, label, icon, description (default: `nil`)
- `admin_tabs/0` - Admin navigation tabs (default: `[]`)
- `settings_tabs/0` - Settings subtabs (default: `[]`)
- `user_dashboard_tabs/0` - User-facing dashboard tabs (default: `[]`)
- `children/0` - Supervisor child specs (default: `[]`)
- `route_module/0` - Module providing route macros (default: `nil`)
- `version/0` - Module version string (default: `"0.0.0"`)
- `migration_module/0` - Module implementing versioned migrations (default: `nil`).
  When set, `mix phoenix_kit.update` will automatically run this module's migrations
  alongside the core PhoenixKit migrations.
- `required_integrations/0` - Integration provider keys this module needs (default: `[]`).
  Used by the Integrations settings page to show relevant providers.
- `integration_providers/0` - Additional provider definitions this module contributes (default: `[]`).

# `permission_meta`

```elixir
@type permission_meta() :: %{
  key: String.t(),
  label: String.t(),
  icon: String.t(),
  description: String.t()
}
```

Permission metadata for the module

# `admin_tabs`
*optional* 

```elixir
@callback admin_tabs() :: [PhoenixKit.Dashboard.Tab.t()]
```

# `children`
*optional* 

```elixir
@callback children() :: [Supervisor.child_spec() | module() | {module(), term()}]
```

# `css_sources`
*optional* 

```elixir
@callback css_sources() :: [atom() | String.t()]
```

Returns Tailwind CSS source roots for scanning.

Each entry is either:

  * an atom — the OTP app name. The compiler resolves it via the parent
    app's `mix.exs` deps (`deps/<app>` for Hex, `path:` value for path deps).
  * a string — a literal path. Absolute paths (starting with `/`) emit as
    `@source "<abs>";` verbatim; relative paths emit as `@source "../../<path>";`
    (relative to `assets/css/_phoenix_kit_sources.css`). Useful when a module
    wants to add a path-dep absolute fallback alongside the OTP-app entry,
    so both Hex and path-dep installs work without parent-app toggles.

## Examples

    def css_sources, do: [:phoenix_kit_publishing]

    # Path-dep friendly:
    @source_root Path.expand(Path.join(__DIR__, "../.."))
    def css_sources, do: [:phoenix_kit_publishing, @source_root]

Headless modules (no templates) can skip this callback — the default is `[]`.

# `disable_system`

```elixir
@callback disable_system() :: :ok | {:ok, term()} | {:error, term()}
```

# `enable_system`

```elixir
@callback enable_system() :: :ok | {:ok, term()} | {:error, term()}
```

# `enabled?`

```elixir
@callback enabled?() :: boolean()
```

# `get_config`
*optional* 

```elixir
@callback get_config() :: map()
```

# `integration_providers`
*optional* 

```elixir
@callback integration_providers() :: [map()]
```

# `migration_module`
*optional* 

```elixir
@callback migration_module() :: module() | nil
```

# `module_key`

```elixir
@callback module_key() :: String.t()
```

# `module_name`

```elixir
@callback module_name() :: String.t()
```

# `notification_types`
*optional* 

```elixir
@callback notification_types() :: [map()]
```

Returns a list of notification types this module contributes.

Each type is a map with:
  * `:key` — binary, stable identifier used in user prefs (e.g. `"posts"`)
  * `:label` — binary, user-facing display (e.g. `"Posts"`)
  * `:description` — binary, short explainer shown under the toggle
  * `:actions` — list of dotted action strings (`["post.liked", "post.commented"]`)
  * `:default` — boolean, the toggle's default state for users who haven't
    set a preference

Types merge with core PhoenixKit types (`account`, `posts`, `comments`) and
show up automatically in the UserSettings "Notifications" section. The
filter in `PhoenixKit.Notifications.maybe_create_from_activity/1` resolves
each action to a type via `:actions` and skips the fan-out when the user
has muted that type.

Headless modules (no user-facing actions) can skip this callback — the
default is `[]`.

## Example

    def notification_types do
      [
        %{
          key: "reviews",
          label: "Reviews",
          description: "When someone leaves you a review",
          actions: ["review.submitted", "review.edited"],
          default: true
        }
      ]
    end

# `permission_metadata`
*optional* 

```elixir
@callback permission_metadata() :: permission_meta() | nil
```

# `required_integrations`
*optional* 

```elixir
@callback required_integrations() :: [String.t()]
```

# `required_modules`
*optional* 

```elixir
@callback required_modules() :: [String.t()]
```

# `route_module`
*optional* 

```elixir
@callback route_module() :: module() | nil
```

# `settings_tabs`
*optional* 

```elixir
@callback settings_tabs() :: [PhoenixKit.Dashboard.Tab.t()]
```

# `user_dashboard_tabs`
*optional* 

```elixir
@callback user_dashboard_tabs() :: [PhoenixKit.Dashboard.Tab.t()]
```

# `version`
*optional* 

```elixir
@callback version() :: String.t()
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
