Library mode renders application-owned admin modules inside the host Phoenix endpoint. It is Incant's default mode: the Incant OTP application starts no children when serve?: false.
The host owns:
- endpoint, sessions, and authentication;
- Ecto repos and application supervision;
- route placement and network access;
- CSS tokens and browser assets;
- admin modules, policies, queries, and side effects.
Admin root
Register resources, dashboards, and datasets on one root:
defmodule MyApp.Admin do
use Incant.Admin,
repo: MyApp.Repo,
theme: MyApp.Admin.Themes.Default,
policy: MyApp.Admin.Policy,
actor_assign: :current_scope,
naming: [terms: %{openai: "OpenAI", api: "API"}]
resource MyApp.Admin.Resources.Product
resource MyApp.Admin.Resources.Order
dashboard MyApp.Admin.Dashboards.Operations
dataset MyApp.Admin.Datasets.Sales
endRoot options become defaults for registered surfaces. A resource or dashboard can override its repo or policy when ownership differs.
Router and authentication
defmodule MyAppWeb.Router do
use MyAppWeb, :router
use Incant.Router
scope "/", MyAppWeb do
pipe_through [:browser, :require_authenticated_user, :require_operator]
incant "/admin", MyApp.Admin
end
endUse the same session and authentication pipeline as the rest of the Phoenix application. Incant reads the configured actor assign from LiveView assigns and passes it into policy callbacks.
If actor_assign is omitted, Incant checks common assign names in this order:
current_scope
current_user
current_admin
actor
userSee Authorization for explicit extraction and row scoping.
Assets
Add Incant to Tailwind scanning:
@source "../deps/incant/lib";Let Volt resolve the Hex dependency as a frontend package:
config :volt,
resolve_dirs: ["deps"]Then import and mount browser behavior from the host entry:
import { mountIncant } from "incant"
mountIncant()The regular package entry contains UI behavior only. It deliberately does not connect a LiveSocket, so the host keeps its existing Phoenix socket setup.
Schema-backed resources
An Ecto resource can infer fields and use standard query operations:
defmodule MyApp.Admin.Resources.Order do
use Incant.Resource,
schema: MyApp.Sales.Order,
repo: MyApp.Repo
table do
column :number, link: true
column :status, as: :badge
column :total, format: :money
column :inserted_at, format: :datetime
filter :status, :multi_select, options: [:pending, :paid, :shipped]
filter :inserted_at, :date_range
search [:number, :email]
end
form do
field :status, :select, options: [:pending, :paid, :shipped]
field :notes, :textarea
end
endIncant casts built-in filter values through the Ecto field type, applies policy query scoping, and passes the resulting table state through search, filters, sort, count, and pagination.
Query-backed resources
Use application queries when a surface needs joins, aggregates, projections, or a nonstandard schema:
defmodule MyApp.Admin.Resources.CustomerHealth do
use Incant.Resource,
title: "Customer Health"
index &MyApp.Admin.Queries.customer_health/2
read &MyApp.Admin.Queries.customer/2
table do
column :customer, link: true
column :plan
column :monthly_spend, format: :money
column :last_seen_at, format: :relative
end
endKeep domain query logic in application modules. Resource metadata should describe presentation and available operations rather than become a second data layer.
Forms and actions
Resource forms use declared fields or infer compatible schema fields. Create and update still run through application callbacks or Ecto changesets.
Actions can return normal success/error results, navigation, or one-time secret reveals:
action :rotate_key,
callback: &MyApp.Keys.rotate/2,
confirm: "Rotate this key?",
destructive: trueUse Incant.ActionResult.Reveal for a newly generated secret that should appear once in a protected dialog.
UI configuration
The default adapter is Incant.UI.Adapters.LiveView:
config :incant,
ui_adapter: Incant.UI.Adapters.LiveView,
density: :compactPer-admin overrides use the admin module as the application config key:
config :incant, MyApp.Admin,
density: :comfortableTheme tokens remain host CSS variables. See Themes and LiveView.
Testing
Admin metadata is ordinary Elixir data:
metadata = Incant.metadata(MyApp.Admin)
assert MyApp.Admin.Resources.Order in metadata.resourcesUse Phoenix LiveView tests for router/session rendering and focused unit tests for query callbacks, policies, action handlers, and formatters. You do not need a browser to verify the semantic contract.