code-quality
Use when refactoring or reviewing Elixir code quality — compiler warnings and Credo first, then the plugin's on-demand…
Use when running phx.gen.live/html/json/context with scopes (--scope, --no-scope), editing config :my_app, :scopes, extending the %Scope{} struct with an org, or adopting scopes in a pre-1.8 app. Not for login plumbing (phoenix-liveview-auth) or permission rules
$ npx -y skills add j-morgan6/elixir-phoenix-guide --skill phoenix-scopes-and-generators --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/phoenix-scopes-and-generatorsContext preview
The summary Claude sees to decide when to auto-load this skill.
Use when running phx.gen.live/html/json/context with scopes (--scope, --no-scope), editing config :my_app, :scopes, extending the %Scope{} struct with an org, or adopting scopes in a pre-1.8 app. Not for login plumbing (phoenix-liveview-auth) or permission rules
name: phoenix-scopes-and-generators
description: "Use when running phx.gen.live/html/json/context with scopes (--scope, --no-scope), editing config :my_app, :scopes, extending the %Scope{} struct with an org, or adopting scopes in a pre-1.8 app. Not for login plumbing (phoenix-liveview-auth) or permission rules (phoenix-authorization-patterns)."> Verified against Elixir 1.20, Phoenix 1.8, LiveView 1.2, Ecto 3.14 — October 2026
1. **Check `config :my_app, :scopes` before generating.** Generators read it at mix-task time; with no default entry (no `phx.gen.auth` yet) they silently emit unscoped contexts with no PubSub topic and no owner column. Run `phx.gen.auth` first, or add the entry. 2. **Take the default scope unless the data is truly global.** `--no-scope` (lookup tables, public content) drops the scope argument, the owner column, the `subscribe_*/broadcast_*` functions and the cross-scope tests. `--scope name` only accepts a configured entry, and combining the two flags raises. 3. **Don't pass the owner as a field.** With the default scope, `user_id:references:users` aborts the generator ("same name as the scope schema key"); the scope already adds `field :user_id` and the migration column. 4. **Keep the generated ownership checks.** `update_*`, `delete_*` and `change_*` all start with `true = post.user_id == scope.user.id`. A hand-written "new" form must build `%Post{user_id: scope.user.id}`, or `change_post/2` raises `MatchError`. 5. **Extend the one `Scope` struct; don't add a second assign.** Add fields (`org`, `org_role`) plus a builder (`put_org/3`), and a second config entry that points at the **same** `module` and `assign_key` with a different `access_path` and `schema_key`. 6. **Fill extra scope fields with a plug *and* an `on_mount` hook.** The plug covers the HTTP render and controllers; the hook covers the websocket mount and live navigation. Both look up membership through the current scope and never trust the slug alone. 7. **Write the test helpers before generating against a new entry.** Generated tests call `<name>_scope_fixture/0` from `test_data_fixture` and `setup :<test_setup_helper>`. Both must exist, and the setup helper must return `scope:`. 8. **Scoped controllers need a pipeline that assigns the scope.** `phx.gen.json` output reads `conn.assigns.current_scope`, but the generated `:api` pipeline never sets it, so every action fails with `KeyError`. 9. **Pass the scope down; never rebuild it from params.** Contexts pass it to other contexts. Background jobs store ids and rebuild it in `perform/1`. 10. **Adopt scopes in a pre-1.8 app in this order:** Scope module, then config, then assign `:current_scope`, then test helpers, then owner columns, then generate or convert contexts one at a time.
---
`lib/my_app/accounts/scope.ex` is a plain struct with `for_user/1` (`nil` in gives `nil` out; guests have no scope). Its moduledoc asks you to extend it. The config entry tells every later generator how to use it:
config :my_app, :scopes,
user: [
default: true,
module: MyApp.Accounts.Scope,
assign_key: :current_scope,
access_path: [:user, :id],
schema_key: :user_id,
schema_type: :id,
schema_table: :users,
test_data_fixture: MyApp.AccountsFixtures,
test_setup_helper: :register_and_log_in_user
]Keys accepted by `Mix.Phoenix.Scope` (an unknown key raises):
| Key | Drives | |---|---| | `default` | used when no flag is given; only one entry may be `true` (more raises) | | `module` | the struct pattern-matched in context heads (`%Scope{} = scope`) | | `assign_key` | `socket.assigns.current_scope` / `conn.assigns.current_scope` in generated web code | | `access_path` | the owner value: `[:user, :id]` becomes `scope.user.id` in queries, `put_change` and topics | | `schema_key` | the owner field added to the schema and migration (`:user_id`) | | `schema_type` | that field's type (`:id`, `:binary_id`) | | `schema_table` | `references(:users, on_delete: :delete_all)`; `nil` gives a plain column with no FK | | `schema_migration_type` | overrides the migration column type (defaults to `schema_type` unless it is `:id`) | | `route_prefix` | e.g. `"/orgs/:org_slug"`, prepended to every generated path; at most one `:param` | | `route_access_path` | what fills that param, e.g. `[:org, :slug]` (defaults to `access_path` minus its last element) | | `test_data_fixture` | the module generated tests import `<name>_scope_fixture/0` from | | `test_setup_helper` | the `setup :helper` in generated LiveView and controller tests |
The **entry name** (`user`, `org`) is also the PubSub topic prefix (`"user:#{key}:posts"`) and the changeset's argument name (`user_scope`). The config must be in `config/config.exs` (or a file it imports). Generators never load `runtime.exs`.
`phx.gen.auth` takes `--scope name` and `--assign-key key` to rename its entry. It marks the entry `default: true` only if no default exists yet.
---
| Command | Result | |---|---| | `mix phx.gen.live Blog Post posts title:string` | default scope | | `mix phx.gen.live Projects Project projects name:string --scope org` | the `org` entry | | `mix phx.gen.html Pages Page pages title:string --no-scope` | unscoped | | `--scope missing` | `** (Mix) Scope :missing not configured!` |
`phx.gen.live`, `phx.gen.html`, `phx.gen.json`, `phx.gen.context` and `phx.gen.schema` all take `--scope` / `--no-scope`.
A scoped run changes these files:
· Changelog A Claude Code plugin (with Codex support for the skills) that keeps Claude's Elixir and Phoenix code idiomatic and current.
Use when refactoring or reviewing Elixir code quality — compiler warnings and Credo first, then the plugin's on-demand…
Use when preparing releases or deployment config — mix phx.gen.release, runtime.exs vs compile-time config, release migrations,…
Use when writing Ecto changeset validations beyond cast/validate_required — custom and cross-field validations (e.g. end date…
Use when defining schemas, writing queries, or creating migrations — scoped context functions, Repo.transact, upserts, indexes,…
Use when migrating PostgreSQL tables that serve live traffic — index concurrently,…
Use when one form or operation manages parent and child records together — cast_assoc/cast_embed, inputs_for with sort/drop…