Repository: phoenixframework/phoenix
Pull request #6118 (2025-03-12 13:11:25)

add route_prefix option to scopes


Patch:
diff --git a/guides/authn_authz/scopes.md b/guides/authn_authz/scopes.md
index 908647472..09f979738 100644
--- a/guides/authn_authz/scopes.md
+++ b/guides/authn_authz/scopes.md
@@ -202,6 +202,10 @@ In this example, the scope is called `user` and it is the `default` scope that i
 
 * `access_path` - a list of keys that define the path to the identifying field in the scope struct. The generators generate code like `where: schema_key == ^scope.user.id`.
 
+* `route_prefix` - (optional) a path template string for how resources should be nested. For example, `/orgs/:org` would generate routes like `/orgs/:org/posts`. The parameter segment (`:org`) will be replaced with the appropriate scope access value in templates and LiveViews.
+
+* `route_access_path` - (optional) list of keys that define the path to the field used in route generation (if `route_prefix` is set). This is particularly useful for user-friendly URLs where you might want to use a slug instead of an ID. If not specified, it defaults to `Enum.drop(scope.access_path, -1)` or `access_path` if the former is empty. For example, if the `access_path` is `[:organization, :id]`, it defaults to `[:organization]`, assuming that the value at `scope.organization` implements the `Phoenix.Param` protocol.
+
 * `schema_key` - the foreign key that ties the resource to the scope. New scoped schemas are created with a foreign key field named `schema_key` of type `schema_type` to the `schema_table` table.
 
 * `schema_type` - the type of the foreign key field in the schema. Typically `:id` or `:binary_id`.
@@ -325,11 +329,13 @@ defmodule MyApp.Accounts.Organization do
   use Ecto.Schema
   import Ecto.Changeset
 
+  @derive {Phoenix.Param, key: :slug}
   schema "organizations" do
     field :name, :string
+    field :slug, :string
     ...
 
-    has_many :users, MyApp.Accounts.User
+    many_to_many :users, MyApp.Accounts.User, join_through: "organizations_users"
 
     timestamps(type: :utc_datetime)
   end
@@ -403,7 +409,7 @@ end
 def on_mount(:assign_org_to_scope, _params, _session, socket), do: {:cont, socket}
 ```
 
-This way, if a route is defined like `live /organizations/:org/posts`, the `maybe_add_org_to_scope` plug would fetch the organization from the path and assign it to the scope. This code assumes that `get_organization_by_slug!/2` raises an
+This way, if a route is defined like `live /organizations/:org/posts`, the `assign_org_to_scope` plug would fetch the organization from the path and assign it to the scope. This code assumes that `get_organization_by_slug!/2` raises an
 `Ecto.NoResultsError` which would be automatically converted to `404`, but you could also handle the error explicitly and,
 for example, set an error flash and redirect to another page, like a dashboard. The `get_organization_by_slug!/2` function
 should also rely on the current scope to filter the organizations to those the user has access to.
@@ -419,6 +425,7 @@ config :my_app, :scopes,
     module: MyApp.Accounts.Scope,
     assign_key: :current_scope,
     access_path: [:organization, :id],
+    route_prefix: "/orgs/:org",
     schema_key: :org_id,
     schema_type: :id,
     schema_table: :organizations,
@@ -451,22 +458,12 @@ defmodule MyAppWeb.ConnCase do
 end
 ```
 
-As the organization is part of the URL path, we also need to adjust all the paths that are generated, e.g. for `phx.gen.live` to include the correct organization. Assuming we scope the Blog example from previously to the organization, this would look like this:
+Now that our scope configuration includes the `route_prefix` and `route_access_path`, we can generate resources scoped to the organization, and all paths will be automatically generated with the correct organization slug:
 
 ```console
 $ mix phx.gen.live Blog Post posts title:string body:text --scope organization
 ```
 
-```diff
-     test "lists all posts", %{conn: conn, post: post} do
--      {:ok, _index_live, html} = live(conn, ~p"/posts")
-+      {:ok, _index_live, html} = live(conn, ~p"/organizations/#{post.organization}/posts")
-
-       assert html =~ "Listing Posts"
-       assert html =~ post.title
-     end
-```
-
 This shows that scopes are quite flexible, allowing you to keep a well-defined data structure, even when your application grows.
 
 Most of the time, your application will have a single scope module, like in this example. But sometimes, you might want to create a new scope module, for example to completely separate a user-facing scope from an admin scope, where also the context functions are supposed to only be called by one of the two.
diff --git a/lib/mix/phoenix/schema.ex b/lib/mix/phoenix/schema.ex
index acaa912c0..cd4692c86 100644
--- a/lib/mix/phoenix/schema.ex
+++ b/lib/mix/phoenix/schema.ex
@@ -149,7 +149,7 @@ defmodule Mix.Phoenix.Schema do
       web_path: web_path,
       route_helper: route_helper(web_path, singular),
       route_prefix: route_prefix(web_path, schema_plural),
-      api_route_prefix: api_route_prefix(web_path, schema_plural, api_prefix),
+      api_route_prefix: api_prefix,
       sample_id: sample_id(opts),
       context_app: ctx_app,
       generate?: generate?,
@@ -557,11 +557,6 @@ defmodule Mix.Phoenix.Schema do
     "/" <> String.trim_leading(path, "/")
   end
 
-  defp api_route_prefix(web_path, plural, api_prefix) do
-    path = Path.join(for str <- [api_prefix, web_path, plural], do: to_string(str))
-    "/" <> String.trim_leading(path, "/")
-  end
-
   defp migration_module do
     case Application.get_env(:ecto_sql, :migration_module, Ecto.Migration) do
       migration_module when is_atom(migration_module) -> migration_module
diff --git a/lib/mix/phoenix/scope.ex b/lib/mix/phoenix/scope.ex
index 863f1ade6..324f7b599 100644
--- a/lib/mix/phoenix/scope.ex
+++ b/lib/mix/phoenix/scope.ex
@@ -7,6 +7,8 @@ defmodule Mix.Phoenix.Scope do
             alias: nil,
             assign_key: nil,
             access_path: nil,
+            route_prefix: nil,
+            route_access_path: nil,
             schema_table: nil,
             schema_key: nil,
             schema_type: nil,
@@ -21,10 +23,17 @@ defmodule Mix.Phoenix.Scope do
     scope = struct!(__MODULE__, opts)
     alias = Module.concat([scope.module |> Module.split() |> List.last()])
 
+    route_access_path =
+      case scope.route_access_path || Enum.drop(scope.access_path, -1) do
+        [] -> scope.access_path
+        rap -> rap
+      end
+
     %{
       scope
       | name: name,
         alias: alias,
+        route_access_path: route_access_path,
         schema_migration_type: scope.schema_migration_type || scope.schema_type
     }
   end
@@ -91,4 +100,55 @@ defmodule Mix.Phoenix.Scope do
       """)
     end)
   end
+
+  @doc """
+  Generates a route prefix string with placeholders for the access path.
+
+  Takes a scope_key (what to use for accessing the scope) and a schema with scope information.
+  If the schema doesn't have a scope with route_prefix, returns an empty string.
+  Otherwise, it processes the route_prefix, replacing param segments with dynamic path elements.
+
+  ## Examples
+
+      scope_route_prefix("socket.assigns.current_scope", schema_with_scope)
+      # => "/orgs/\#{socket.assigns.current_scope.organization.slug}"
+
+      scope_route_prefix("@current_scope", schema_with_scope)
+      # => "/orgs/\#{@current_scope.organization.slug}"
+
+      scope_route_prefix("scope", schema_with_scope)
+      # => "/orgs/\#{scope.organization.slug}"
+  """
+  def route_prefix(
+        scope_key,
+        %{scope: %{route_prefix: route_prefix, route_access_path: route_access_path}} = _schema
+      )
+      when not is_nil(route_prefix) do
+    # Replace any path segment that starts with a colon with route_access_path from the scope
+    path_segments = String.split(route_prefix, "/", trim: true)
+    param_segments = Enum.filter(path_segments, &String.starts_with?(&1, ":"))
+
+    if length(param_segments) > 1 do
+      Mix.raise(
+        "The route_prefix option in scope configuration must contain only one parameter. Found: #{inspect(param_segments)}"
+      )
+    end
+
+    path_with_placeholders =
+      path_segments
+      |> Enum.map(fn segment ->
+        if String.starts_with?(segment, ":") do
+          # Extract parameter name without the colon
+          access_string = Enum.join(route_access_path, ".")
+          "\#{#{scope_key}.#{access_string}}"
+        else
+          segment
+        end
+      end)
+      |> Enum.join("/")
+
+    "/#{path_with_placeholders}"
+  end
+
+  def route_prefix(_scope_key, _schema), do: ""
 end
diff --git a/lib/mix/tasks/phx.gen.context.ex b/lib/mix/tasks/phx.gen.context.ex
index ef2fa5117..c4b0a219f 100644
--- a/lib/mix/tasks/phx.gen.context.ex
+++ b/lib/mix/tasks/phx.gen.context.ex
@@ -109,12 +109,14 @@ defmodule Mix.Tasks.Phx.Gen.Context do
     end
 
     {context, schema} = build(args)
+
     binding = [
       context: context,
       schema: schema,
       scope: context.scope,
       primary_key: schema.opts[:primary_key] || :id
     ]
+
     paths = Mix.Phoenix.generator_paths()
 
     prompt_for_conflicts(context)
@@ -190,6 +192,13 @@ defmodule Mix.Tasks.Phx.Gen.Context do
   defp inject_schema_access(%Context{file: file} = context, paths, binding) do
     ensure_context_file_exists(context, paths, binding)
 
+    binding =
+      if File.exists?(file) and File.read!(file) =~ "defp broadcast" do
+        [{:add_broadcast, false} | binding]
+      else
+        [{:add_broadcast, true} | binding]
+      end
+
     paths
     |> Mix.Phoenix.eval_from(
       "priv/templates/phx.gen.context/#{schema_access_template(context)}",
diff --git a/lib/mix/tasks/phx.gen.html.ex b/lib/mix/tasks/phx.gen.html.ex
index 325cb3db0..d94e3e39f 100644
--- a/lib/mix/tasks/phx.gen.html.ex
+++ b/lib/mix/tasks/phx.gen.html.ex
@@ -91,7 +91,7 @@ defmodule Mix.Tasks.Phx.Gen.Html do
   """
   use Mix.Task
 
-  alias Mix.Phoenix.{Context, Schema}
+  alias Mix.Phoenix.{Context, Schema, Scope}
   alias Mix.Tasks.Phx.Gen
 
   @doc false
@@ -122,7 +122,12 @@ defmodule Mix.Tasks.Phx.Gen.Html do
       scope: schema.scope,
       inputs: inputs(schema),
       conn_scope: conn_scope,
-      context_scope_prefix: context_scope_prefix
+      context_scope_prefix: context_scope_prefix,
+      scope_conn_route_prefix: Scope.route_prefix(conn_scope, schema),
+      scope_param_route_prefix: Scope.route_prefix("scope", schema),
+      scope_assign_route_prefix: scope_assign_route_prefix(schema),
+      test_context_scope:
+        if(schema.scope && schema.scope.route_prefix, do: ", scope: scope", else: "")
     ]
 
     paths = Mix.Phoenix.generator_paths()
@@ -182,6 +187,13 @@ defmodule Mix.Tasks.Phx.Gen.Html do
 
   @doc false
   def print_shell_instructions(%Context{schema: schema, context_app: ctx_app} = context) do
+    resource_path =
+      if schema.scope && schema.scope.route_prefix do
+        "#{schema.scope.route_prefix}/#{schema.plural}"
+      else
+        "/#{schema.plural}"
+      end
+
     if schema.web_namespace do
       Mix.shell().info("""
 
@@ -190,7 +202,7 @@ defmodule Mix.Tasks.Phx.Gen.Html do
           scope "/#{schema.web_path}", #{inspect(Module.concat(context.web_module, schema.web_namespace))}, as: :#{schema.web_path} do
             pipe_through :browser
             ...
-            resources "/#{schema.plural}", #{inspect(schema.alias)}Controller#{if schema.opts[:primary_key], do: ~s[, param: "#{schema.opts[:primary_key]}"]}
+            resources "#{resource_path}", #{inspect(schema.alias)}Controller#{if schema.opts[:primary_key], do: ~s[, param: "#{schema.opts[:primary_key]}"]}
           end
       """)
     else
@@ -198,10 +210,16 @@ defmodule Mix.Tasks.Phx.Gen.Html do
 
       Add the resource to your browser scope in #{Mix.Phoenix.web_path(ctx_app)}/router.ex:
 
-          resources "/#{schema.plural}", #{inspect(schema.alias)}Controller#{if schema.opts[:primary_key], do: ~s[, param: "#{schema.opts[:primary_key]}"]}
+          resources "#{resource_path}", #{inspect(schema.alias)}Controller#{if schema.opts[:primary_key], do: ~s[, param: "#{schema.opts[:primary_key]}"]}
       """)
     end
 
+    if schema.scope do
+      Mix.shell().info(
+        "Ensure the routes are defined in a block that sets the `#{inspect(context.scope.assign_key)}` assign."
+      )
+    end
+
     if context.generate?, do: Gen.Context.print_shell_instructions(context)
   end
 
@@ -274,6 +292,15 @@ defmodule Mix.Tasks.Phx.Gen.Html do
 
   defp label(key), do: Phoenix.Naming.humanize(to_string(key))
 
+  defp scope_assign_route_prefix(
+         %{scope: %{route_prefix: route_prefix, assign_key: assign_key}} = schema
+       )
+       when not is_nil(route_prefix) do
+    Scope.route_prefix("@#{assign_key}", schema)
+  end
+
+  defp scope_assign_route_prefix(_), do: ""
+
   @doc false
   def indent_inputs(inputs, column_padding) do
     columns = String.duplicate(" ", column_padding)
diff --git a/lib/mix/tasks/phx.gen.json.ex b/lib/mix/tasks/phx.gen.json.ex
index 22f330c23..c5eae5caa 100644
--- a/lib/mix/tasks/phx.gen.json.ex
+++ b/lib/mix/tasks/phx.gen.json.ex
@@ -89,7 +89,7 @@ defmodule Mix.Tasks.Phx.Gen.Json do
 
   use Mix.Task
 
-  alias Mix.Phoenix.Context
+  alias Mix.Phoenix.{Context, Scope}
   alias Mix.Tasks.Phx.Gen
 
   @doc false
@@ -119,7 +119,11 @@ defmodule Mix.Tasks.Phx.Gen.Json do
       gettext?: Code.ensure_loaded?(Module.concat(context.web_module, "Gettext")),
       primary_key: schema.opts[:primary_key] || :id,
       conn_scope: conn_scope,
-      context_scope_prefix: context_scope_prefix
+      context_scope_prefix: context_scope_prefix,
+      scope_conn_route_prefix: Scope.route_prefix(conn_scope, schema),
+      scope_param_route_prefix: Scope.route_prefix("scope", schema),
+      test_context_scope:
+        if(schema.scope && schema.scope.route_prefix, do: ", scope: scope", else: "")
     ]
 
     paths = Mix.Phoenix.generator_paths()
@@ -175,6 +179,13 @@ defmodule Mix.Tasks.Phx.Gen.Json do
 
   @doc false
   def print_shell_instructions(%Context{schema: schema, context_app: ctx_app} = context) do
+    resource_path =
+      if schema.scope && schema.scope.route_prefix do
+        "#{schema.scope.route_prefix}/#{schema.plural}"
+      else
+        "/#{schema.plural}"
+      end
+
     if schema.web_namespace do
       Mix.shell().info("""
 
@@ -183,7 +194,7 @@ defmodule Mix.Tasks.Phx.Gen.Json do
           scope "/#{schema.web_path}", #{inspect(Module.concat(context.web_module, schema.web_namespace))}, as: :#{schema.web_path} do
             pipe_through :api
             ...
-            resources "/#{schema.plural}", #{inspect(schema.alias)}Controller#{if schema.opts[:primary_key], do: ~s[, param: "#{schema.opts[:primary_key]}"]}
+            resources "#{resource_path}", #{inspect(schema.alias)}Controller#{if schema.opts[:primary_key], do: ~s[, param: "#{schema.opts[:primary_key]}"]}
           end
       """)
     else
@@ -191,10 +202,16 @@ defmodule Mix.Tasks.Phx.Gen.Json do
 
       Add the resource to the "#{Application.get_env(ctx_app, :generators)[:api_prefix] || "/api"}" scope in #{Mix.Phoenix.web_path(ctx_app)}/router.ex:
 
-          resources "/#{schema.plural}", #{inspect(schema.alias)}Controller, except: [:new, :edit]#{if schema.opts[:primary_key], do: ~s[, param: "#{schema.opts[:primary_key]}"]}
+          resources "#{resource_path}", #{inspect(schema.alias)}Controller, except: [:new, :edit]#{if schema.opts[:primary_key], do: ~s[, param: "#{schema.opts[:primary_key]}"]}
       """)
     end
 
+    if schema.scope do
+      Mix.shell().info(
+        "Ensure the routes are defined in a block that sets the `#{inspect(context.scope.assign_key)}` assign."
+      )
+    end
+
     if context.generate?, do: Gen.Context.print_shell_instructions(context)
   end
 end
diff --git a/lib/mix/tasks/phx.gen.live.ex b/lib/mix/tasks/phx.gen.live.ex
index 07e3ceb6b..b87f83060 100644
--- a/lib/mix/tasks/phx.gen.live.ex
+++ b/lib/mix/tasks/phx.gen.live.ex
@@ -102,7 +102,7 @@ defmodule Mix.Tasks.Phx.Gen.Live do
   """
   use Mix.Task
 
-  alias Mix.Phoenix.{Context, Schema}
+  alias Mix.Phoenix.{Context, Schema, Scope}
   alias Mix.Tasks.Phx.Gen
 
   @doc false
@@ -120,12 +120,13 @@ defmodule Mix.Tasks.Phx.Gen.Live do
 
     Gen.Context.prompt_for_code_injection(context)
 
-    {socket_scope, context_scope_prefix} =
+    {socket_scope, context_scope_prefix, assign_scope, assign_scope_prefix} =
       if schema.scope do
-        base = "socket.assigns.#{schema.scope.assign_key}"
-        {base, "#{base}, "}
+        base_socket = "socket.assigns.#{schema.scope.assign_key}"
+        base_assign = "@#{schema.scope.assign_key}"
+        {base_socket, "#{base_socket}, ", base_assign, "#{base_assign}, "}
       else
-        {"", ""}
+        {"", "", "", ""}
       end
 
     binding = [
@@ -135,7 +136,16 @@ defmodule Mix.Tasks.Phx.Gen.Live do
       scope: schema.scope,
       inputs: inputs(schema),
       socket_scope: socket_scope,
-      context_scope_prefix: context_scope_prefix
+      context_scope_prefix: context_scope_prefix,
+      assign_scope: assign_scope,
+      assign_scope_prefix: assign_scope_prefix,
+      scope_param_route_prefix: Scope.route_prefix("scope", schema),
+      scope_param: scope_param(schema),
+      scope_param_prefix: scope_param_prefix(schema),
+      scope_socket_route_prefix: Scope.route_prefix(socket_scope, schema),
+      scope_assign_route_prefix: scope_assign_route_prefix(schema),
+      test_context_scope:
+        if(schema.scope && schema.scope.route_prefix, do: ", scope: scope", else: "")
     ]
 
     paths = Mix.Phoenix.generator_paths()
@@ -281,6 +291,12 @@ defmodule Mix.Tasks.Phx.Gen.Live do
       """)
     end
 
+    if schema.scope do
+      Mix.shell().info(
+        "Ensure the routes are defined in a block that sets the `#{inspect(context.scope.assign_key)}` assign."
+      )
+    end
+
     if context.generate?, do: Gen.Context.print_shell_instructions(context)
     maybe_print_upgrade_info()
   end
@@ -297,11 +313,19 @@ defmodule Mix.Tasks.Phx.Gen.Live do
   end
 
   defp live_route_instructions(schema) do
+    route_base =
+      if schema.scope && schema.scope.route_prefix do
+        scope_prefix = schema.scope.route_prefix
+        "#{scope_prefix}/#{schema.plural}"
+      else
+        "/#{schema.plural}"
+      end
+
     [
-      ~s|live "/#{schema.plural}", #{inspect(schema.alias)}Live.Index, :index\n|,
-      ~s|live "/#{schema.plural}/new", #{inspect(schema.alias)}Live.Form, :new\n|,
-      ~s|live "/#{schema.plural}/:#{schema.opts[:primary_key] || :id}", #{inspect(schema.alias)}Live.Show, :show\n|,
-      ~s|live "/#{schema.plural}/:#{schema.opts[:primary_key] || :id}/edit", #{inspect(schema.alias)}Live.Form, :edit|
+      ~s|live "#{route_base}", #{inspect(schema.alias)}Live.Index, :index\n|,
+      ~s|live "#{route_base}/new", #{inspect(schema.alias)}Live.Form, :new\n|,
+      ~s|live "#{route_base}/:#{schema.opts[:primary_key] || :id}", #{inspect(schema.alias)}Live.Show, :show\n|,
+      ~s|live "#{route_base}/:#{schema.opts[:primary_key] || :id}/edit", #{inspect(schema.alias)}Live.Form, :edit|
     ]
   end
 
@@ -376,4 +400,25 @@ defmodule Mix.Tasks.Phx.Gen.Live do
   defp default_options({:array, _}), do: []
 
   defp label(key), do: Phoenix.Naming.humanize(to_string(key))
+
+  defp scope_param(%{scope: nil}), do: ""
+
+  defp scope_param(%{scope: %{route_prefix: route_prefix}}) when not is_nil(route_prefix),
+    do: "scope"
+
+  defp scope_param(_), do: "_scope"
+
+  defp scope_param_prefix(schema) do
+    param = scope_param(schema)
+    if param != "", do: "#{param}, ", else: ""
+  end
+
+  defp scope_assign_route_prefix(
+         %{scope: %{route_prefix: route_prefix, assign_key: assign_key}} = schema
+       )
+       when not is_nil(route_prefix) do
+    Scope.route_prefix("@#{assign_key}", schema)
+  end
+
+  defp scope_assign_route_prefix(_), do: ""
 end
diff --git a/priv/templates/phx.gen.auth/auth.ex b/priv/templates/phx.gen.auth/auth.ex
index 1a716fbcd..be017048a 100644
--- a/priv/templates/phx.gen.auth/auth.ex
+++ b/priv/templates/phx.gen.auth/auth.ex
@@ -130,7 +130,7 @@ defmodule <%= inspect auth_module %> do
       to socket assigns based on <%= schema.singular %>_token, or nil if
       there's no <%= schema.singular %>_token or no matching <%= schema.singular %>.
 
-    * `:ensure_authenticated` - Authenticates the <%= schema.singular %> from the session,
+    * `:require_authenticated` - Authenticates the <%= schema.singular %> from the session,
       and assigns the current_scope to socket assigns based
       on <%= schema.singular %>_token.
       Redirects to login page if there's no logged <%= schema.singular %>.
@@ -149,7 +149,7 @@ defmodule <%= inspect auth_module %> do
 
   Or use the `live_session` of your router to invoke the on_mount callback:
 
-      live_session :authenticated, on_mount: [{<%= inspect auth_module %>, :ensure_authenticated}] do
+      live_session :authenticated, on_mount: [{<%= inspect auth_module %>, :require_authenticated}] do
         live "/profile", ProfileLive, :index
       end
   """
@@ -157,7 +157,7 @@ defmodule <%= inspect auth_module %> do
     {:cont, mount_current_scope(socket, session)}
   end
 
-  def on_mount(:ensure_authenticated, _params, session, socket) do
+  def on_mount(:require_authenticated, _params, session, socket) do
     socket = mount_current_scope(socket, session)
 
     if socket.assigns.current_scope && socket.assigns.current_scope.<%= schema.singular %> do
@@ -172,7 +172,7 @@ defmodule <%= inspect auth_module %> do
     end
   end
 
-  def on_mount(:ensure_sudo_mode, _params, session, socket) do
+  def on_mount(:require_sudo_mode, _params, session, socket) do
     socket = mount_current_scope(socket, session)
 
     if <%= inspect context.alias %>.sudo_mode?(socket.assigns.current_scope.<%= schema.singular %>, -10) do
diff --git a/priv/templates/phx.gen.auth/routes.ex b/priv/templates/phx.gen.auth/routes.ex
index 0d4f96d57..f40cc6ae8 100644
--- a/priv/templates/phx.gen.auth/routes.ex
+++ b/priv/templates/phx.gen.auth/routes.ex
@@ -12,7 +12,7 @@
     pipe_through [:browser, :require_authenticated_<%= schema.singular %>]<%= if live? do %>
 
     live_session :require_authenticated_<%= schema.singular %>,
-      on_mount: [{<%= inspect auth_module %>, :ensure_authenticated}] do
+      on_mount: [{<%= inspect auth_module %>, :require_authenticated}] do
       live "/<%= schema.plural %>/settings", <%= inspect schema.alias %>Live.Settings, :edit
       live "/<%= schema.plural %>/settings/confirm-email/:token", <%= inspect schema.alias %>Live.Settings, :confirm_email
     end
diff --git a/priv/templates/phx.gen.auth/settings_live.ex b/priv/templates/phx.gen.auth/settings_live.ex
index ae3c14394..3a7c32d16 100644
--- a/priv/templates/phx.gen.auth/settings_live.ex
+++ b/priv/templates/phx.gen.auth/settings_live.ex
@@ -1,7 +1,7 @@
 defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.Settings do
   use <%= inspect context.web_module %>, :live_view
 
-  on_mount {<%= inspect auth_module %>, :ensure_sudo_mode}
+  on_mount {<%= inspect auth_module %>, :require_sudo_mode}
 
   alias <%= inspect context.module %>
 
diff --git a/priv/templates/phx.gen.context/schema_access_scope.ex b/priv/templates/phx.gen.context/schema_access_scope.ex
index e107e3ebf..5e4a71fc4 100644
--- a/priv/templates/phx.gen.context/schema_access_scope.ex
+++ b/priv/templates/phx.gen.context/schema_access_scope.ex
@@ -16,13 +16,13 @@
     key = scope.<%= Enum.join(scope.access_path, ".") %>
 
     Phoenix.PubSub.subscribe(<%= inspect context.base_module %>.PubSub, "<%= scope.name %>:#{key}:<%= schema.plural %>")
-  end
+  end<%= if add_broadcast do %>
 
   defp broadcast(%<%= inspect scope.alias %>{} = scope, message) do
     key = scope.<%= Enum.join(scope.access_path, ".") %>
 
     Phoenix.PubSub.broadcast(<%= inspect context.base_module %>.PubSub, "<%= scope.name %>:#{key}:<%= schema.plural %>", message)
-  end
+  end<% end %>
 
   @doc """
   Returns the list of <%= schema.plural %>.
diff --git a/priv/templates/phx.gen.html/controller.ex b/priv/templates/phx.gen.html/controller.ex
index 9454d5442..7ef192724 100644
--- a/priv/templates/phx.gen.html/controller.ex
+++ b/priv/templates/phx.gen.html/controller.ex
@@ -24,7 +24,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
       {:ok, <%= schema.singular %>} ->
         conn
         |> put_flash(:info, "<%= schema.human_singular %> created successfully.")
-        |> redirect(to: ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}")
+        |> redirect(to: ~p"<%= scope_conn_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}")
 
       {:error, %Ecto.Changeset{} = changeset} ->
         render(conn, :new, changeset: changeset)
@@ -49,7 +49,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
       {:ok, <%= schema.singular %>} ->
         conn
         |> put_flash(:info, "<%= schema.human_singular %> updated successfully.")
-        |> redirect(to: ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}")
+        |> redirect(to: ~p"<%= scope_conn_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}")
 
       {:error, %Ecto.Changeset{} = changeset} ->
         render(conn, :edit, <%= schema.singular %>: <%= schema.singular %>, changeset: changeset)
@@ -62,6 +62,6 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
 
     conn
     |> put_flash(:info, "<%= schema.human_singular %> deleted successfully.")
-    |> redirect(to: ~p"<%= schema.route_prefix %>")
+    |> redirect(to: ~p"<%= scope_conn_route_prefix %><%= schema.route_prefix %>")
   end
 end
diff --git a/priv/templates/phx.gen.html/edit.html.heex b/priv/templates/phx.gen.html/edit.html.heex
index 1af943b41..7d9e9d392 100644
--- a/priv/templates/phx.gen.html/edit.html.heex
+++ b/priv/templates/phx.gen.html/edit.html.heex
@@ -3,6 +3,6 @@
   <:subtitle>Use this form to manage <%= schema.singular %> records in your database.</:subtitle>
 </.header>
 
-<.<%= schema.singular %>_form changeset={@changeset} action={~p"<%= schema.route_prefix %>/#{@<%= schema.singular %>}"} />
+<.<%= schema.singular %>_form changeset={@changeset} action={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{@<%= schema.singular %>}"} />
 
-<.back navigate={~p"<%= schema.route_prefix %>"}>Back to <%= schema.plural %></.back>
+<.back navigate={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>"}>Back to <%= schema.plural %></.back>
diff --git a/priv/templates/phx.gen.html/index.html.heex b/priv/templates/phx.gen.html/index.html.heex
index 3e70d52aa..4d649c514 100644
--- a/priv/templates/phx.gen.html/index.html.heex
+++ b/priv/templates/phx.gen.html/index.html.heex
@@ -1,22 +1,22 @@
 <.header>
   Listing <%= schema.human_plural %>
   <:actions>
-    <.link class={button_classes()} href={~p"<%= schema.route_prefix %>/new"}>
+    <.link class={button_classes()} href={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/new"}>
       New <%= schema.human_singular %>
     </.link>
   </:actions>
 </.header>
 
-<.table id="<%= schema.plural %>" rows={@<%= schema.collection %>} row_click={&JS.navigate(~p"<%= schema.route_prefix %>/#{&1}")}><%= for {k, _} <- schema.attrs do %>
+<.table id="<%= schema.plural %>" rows={@<%= schema.collection %>} row_click={&JS.navigate(~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{&1}")}><%= for {k, _} <- schema.attrs do %>
   <:col :let={<%= schema.singular %>} label="<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>">{<%= schema.singular %>.<%= k %>}</:col><% end %>
   <:action :let={<%= schema.singular %>}>
     <div class="sr-only">
-      <.link navigate={~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}"}>Show</.link>
+      <.link navigate={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}"}>Show</.link>
     </div>
-    <.link navigate={~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}/edit"}>Edit</.link>
+    <.link navigate={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}/edit"}>Edit</.link>
   </:action>
   <:action :let={<%= schema.singular %>}>
-    <.link href={~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}"} method="delete" data-confirm="Are you sure?">
+    <.link href={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}"} method="delete" data-confirm="Are you sure?">
       Delete
     </.link>
   </:action>
diff --git a/priv/templates/phx.gen.html/new.html.heex b/priv/templates/phx.gen.html/new.html.heex
index 0c3b851d8..664019c2b 100644
--- a/priv/templates/phx.gen.html/new.html.heex
+++ b/priv/templates/phx.gen.html/new.html.heex
@@ -3,6 +3,6 @@
   <:subtitle>Use this form to manage <%= schema.singular %> records in your database.</:subtitle>
 </.header>
 
-<.<%= schema.singular %>_form changeset={@changeset} action={~p"<%= schema.route_prefix %>"} />
+<.<%= schema.singular %>_form changeset={@changeset} action={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>"} />
 
-<.back navigate={~p"<%= schema.route_prefix %>"}>Back to <%= schema.plural %></.back>
+<.back navigate={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>"}>Back to <%= schema.plural %></.back>
diff --git a/priv/templates/phx.gen.html/show.html.heex b/priv/templates/phx.gen.html/show.html.heex
index f1bdeaacc..8e7518883 100644
--- a/priv/templates/phx.gen.html/show.html.heex
+++ b/priv/templates/phx.gen.html/show.html.heex
@@ -2,7 +2,7 @@
   <%= schema.human_singular %> {@<%= schema.singular %>.<%= primary_key %>}
   <:subtitle>This is a <%= schema.singular %> record from your database.</:subtitle>
   <:actions>
-    <.link class={button_classes()} href={~p"<%= schema.route_prefix %>/#{@<%= schema.singular %>}/edit"}>
+    <.link class={button_classes()} href={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{@<%= schema.singular %>}/edit"}>
       Edit <%= schema.singular %>
     </.link>
   </:actions>
@@ -12,4 +12,4 @@
   <:item title="<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>">{@<%= schema.singular %>.<%= k %>}</:item><% end %>
 </.list>
 
-<.back navigate={~p"<%= schema.route_prefix %>"}>Back to <%= schema.plural %></.back>
+<.back navigate={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>"}>Back to <%= schema.plural %></.back>
diff --git a/priv/templates/phx.gen.json/controller.ex b/priv/templates/phx.gen.json/controller.ex
index dda1b8c62..ba07f5747 100644
--- a/priv/templates/phx.gen.json/controller.ex
+++ b/priv/templates/phx.gen.json/controller.ex
@@ -15,7 +15,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
     with {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} <- <%= inspect context.alias %>.create_<%= schema.singular %>(<%= context_scope_prefix %><%= schema.singular %>_params) do
       conn
       |> put_status(:created)
-      |> put_resp_header("location", ~p"<%= schema.api_route_prefix %>/#{<%= schema.singular %>}")
+      |> put_resp_header("location", ~p"<%= schema.api_route_prefix %><%= scope_conn_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}")
       |> render(:show, <%= schema.singular %>: <%= schema.singular %>)
     end
   end
diff --git a/priv/templates/phx.gen.live/form.ex b/priv/templates/phx.gen.live/form.ex
index 1484da83b..1f730d3f0 100644
--- a/priv/templates/phx.gen.live/form.ex
+++ b/priv/templates/phx.gen.live/form.ex
@@ -19,7 +19,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
       </:actions>
     </.simple_form>
 
-    <.back navigate={return_path(@return_to, @<%= schema.singular %>)}>Back</.back>
+    <.back navigate={return_path(<%= assign_scope_prefix %>@return_to, @<%= schema.singular %>)}>Back</.back>
     """
   end
 
@@ -68,7 +68,9 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
         {:noreply,
          socket
          |> put_flash(:info, "<%= schema.human_singular %> updated successfully")
-         |> push_navigate(to: return_path(socket.assigns.return_to, <%= schema.singular %>))}
+         <%= if scope do %>|> push_navigate(
+           to: return_path(<%= context_scope_prefix %>socket.assigns.return_to, <%= schema.singular %>)
+         )}<% else %>|> push_navigate(to: return_path(socket.assigns.return_to, <%= schema.singular %>))}<% end %>
 
       {:error, %Ecto.Changeset{} = changeset} ->
         {:noreply, assign(socket, form: to_form(changeset))}
@@ -81,13 +83,15 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
         {:noreply,
          socket
          |> put_flash(:info, "<%= schema.human_singular %> created successfully")
-         |> push_navigate(to: return_path(socket.assigns.return_to, <%= schema.singular %>))}
+         <%= if scope do %>|> push_navigate(
+           to: return_path(<%= context_scope_prefix %>socket.assigns.return_to, <%= schema.singular %>)
+         )}<% else %>|> push_navigate(to: return_path(socket.assigns.return_to, <%= schema.singular %>))}<% end %>
 
       {:error, %Ecto.Changeset{} = changeset} ->
         {:noreply, assign(socket, form: to_form(changeset))}
     end
   end
 
-  defp return_path("index", _<%= schema.singular %>), do: ~p"<%= schema.route_prefix %>"
-  defp return_path("show", <%= schema.singular %>), do: ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}"
+  defp return_path(<%= scope_param_prefix %>"index", _<%= schema.singular %>), do: ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>"
+  defp return_path(<%= scope_param_prefix %>"show", <%= schema.singular %>), do: ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}"
 end
diff --git a/priv/templates/phx.gen.live/index.ex b/priv/templates/phx.gen.live/index.ex
index cf2bf02b1..0b712ff8a 100644
--- a/priv/templates/phx.gen.live/index.ex
+++ b/priv/templates/phx.gen.live/index.ex
@@ -9,7 +9,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
     <.header>
       Listing <%= schema.human_plural %>
       <:actions>
-        <.link class={button_classes()} navigate={~p"<%= schema.route_prefix %>/new"}>
+        <.link class={button_classes()} navigate={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/new"}>
           New <%= schema.human_singular %>
         </.link>
       </:actions>
@@ -18,14 +18,14 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
     <.table
       id="<%= schema.plural %>"
       rows={@streams.<%= schema.collection %>}
-      row_click={fn {_id, <%= schema.singular %>} -> JS.navigate(~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}") end}
+      row_click={fn {_id, <%= schema.singular %>} -> JS.navigate(~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}") end}
     ><%= for {k, _} <- schema.attrs do %>
       <:col :let={{_id, <%= schema.singular %>}} label="<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>">{<%= schema.singular %>.<%= k %>}</:col><% end %>
       <:action :let={{_id, <%= schema.singular %>}}>
         <div class="sr-only">
-          <.link navigate={~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}"}>Show</.link>
+          <.link navigate={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}"}>Show</.link>
         </div>
-        <.link navigate={~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}/edit"}>Edit</.link>
+        <.link navigate={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}/edit"}>Edit</.link>
       </:action>
       <:action :let={{id, <%= schema.singular %>}}>
         <.link
diff --git a/priv/templates/phx.gen.live/show.ex b/priv/templates/phx.gen.live/show.ex
index 991383788..0c20c04d2 100644
--- a/priv/templates/phx.gen.live/show.ex
+++ b/priv/templates/phx.gen.live/show.ex
@@ -10,7 +10,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
       <%= schema.human_singular %> {@<%= schema.singular %>.<%= primary_key %>}
       <:subtitle>This is a <%= schema.singular %> record from your database.</:subtitle>
       <:actions>
-        <.link class={button_classes()} navigate={~p"<%= schema.route_prefix %>/#{@<%= schema.singular %>}/edit?return_to=show"}>
+        <.link class={button_classes()} navigate={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{@<%= schema.singular %>}/edit?return_to=show"}>
           Edit <%= schema.singular %>
         </.link>
       </:actions>
@@ -20,7 +20,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
       <:item title="<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>">{@<%= schema.singular %>.<%= k %>}</:item><% end %>
     </.list>
 
-    <.back navigate={~p"<%= schema.route_prefix %>"}>Back to <%= schema.plural %></.back>
+    <.back navigate={~p"<%= scope_assign_route_prefix %><%= schema.route_prefix %>"}>Back to <%= schema.plural %></.back>
     """
   end
 
@@ -49,6 +49,6 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
     {:noreply,
      socket
      |> put_flash(:error, "The current <%= schema.singular %> was deleted.")
-     |> push_navigate(to: ~p"<%= schema.route_prefix %>")}
+     |> push_navigate(to: ~p"<%= scope_socket_route_prefix %><%= schema.route_prefix %>")}
   end<% end %>
 end


Test patch:
diff --git a/integration_test/test/code_generation/app_with_scopes_test.exs b/integration_test/test/code_generation/app_with_scopes_test.exs
index 42df99def..f081c6f26 100644
--- a/integration_test/test/code_generation/app_with_scopes_test.exs
+++ b/integration_test/test/code_generation/app_with_scopes_test.exs
@@ -99,6 +99,470 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithScopesTest do
   end
 
   describe "custom scope" do
+    test "route_prefix and route_access_path with all generators" do
+      with_installer_tmp("scopes", fn tmp_dir ->
+        {app_root_path, _} = generate_phoenix_app(tmp_dir, "scopes", ["--live"])
+
+        # First generate authentication system
+        mix_run!(~w(phx.gen.auth Accounts User users --live), app_root_path)
+
+        # sleep to have fresh migration name
+        Process.sleep(1500)
+        mix_run!(~w(ecto.gen.migration AddOrganizationAndOrgUser), app_root_path)
+
+        assert [migration] =
+                 Path.wildcard(
+                   Path.join(
+                     app_root_path,
+                     "priv/repo/migrations/*_add_organization_and_org_user.exs"
+                   )
+                 )
+
+        File.write!(migration, """
+        defmodule Scopes.Repo.Migrations.AddOrganizationAndOrgUser do
+          use Ecto.Migration
+
+          def change do
+            create table(:organizations) do
+              add :name, :string
+              add :slug, :string
+              add :user_id, references(:users, type: :id, on_delete: :delete_all)
+
+              timestamps(type: :utc_datetime)
+            end
+
+            create unique_index(:organizations, [:slug])
+
+            create table(:organizations_users) do
+              add :role, :string
+              add :organization_id, references(:organizations, on_delete: :nothing)
+              add :user_id, references(:users, on_delete: :delete_all)
+
+              timestamps(type: :utc_datetime)
+            end
+
+            create index(:organizations_users, [:organization_id])
+            create index(:organizations_users, [:user_id])
+          end
+        end
+        """)
+
+        File.write!(Path.join(app_root_path, "lib/scopes/accounts/organization.ex"), """
+        defmodule Scopes.Accounts.Organization do
+          use Ecto.Schema
+          import Ecto.Changeset
+          alias Scopes.Accounts.User
+
+          @derive {Phoenix.Param, key: :slug}
+          schema "organizations" do
+            field :name, :string
+            field :slug, :string
+
+            many_to_many :users, User, join_through: "organizations_users"
+
+            timestamps(type: :utc_datetime)
+          end
+
+          @doc false
+          def changeset(organization, attrs) do
+            organization
+            |> cast(attrs, [:name, :slug])
+            |> validate_required([:name, :slug])
+            |> unique_constraint(:slug)
+          end
+        end
+        """)
+
+        File.write!(Path.join(app_root_path, "lib/scopes/accounts/organization_user.ex"), """
+        defmodule Scopes.Accounts.OrganizationUser do
+          use Ecto.Schema
+          import Ecto.Changeset
+
+          schema "organizations_users" do
+            field :role, :string
+            belongs_to :organization, Scopes.Accounts.Organization
+            belongs_to :user, Scopes.Accounts.User
+
+            timestamps(type: :utc_datetime)
+          end
+
+          @doc false
+          def changeset(organization_user, attrs, user_scope \\\\ nil) do
+            organization_user
+            |> cast(attrs, [:role, :organization_id, :user_id])
+            |> validate_required([:role, :organization_id])
+            |> maybe_put_user_id(user_scope)
+          end
+
+          defp maybe_put_user_id(changeset, %{user: %{id: user_id}}) do
+            put_change(changeset, :user_id, user_id)
+          end
+
+          defp maybe_put_user_id(changeset, _), do: changeset
+        end
+        """)
+
+        # Update context
+        modify_file(Path.join(app_root_path, "lib/scopes/accounts.ex"), fn file ->
+          inject_before_final_end(file, ~S'''
+          ## Organization operations
+
+          alias Scopes.Accounts.{Organization, OrganizationUser}
+
+          @doc """
+          Returns the list of organizations the user has access to.
+          """
+          def list_organizations(scope) do
+            user_id = scope.user.id
+
+            OrganizationUser
+            |> where([ou], ou.user_id == ^user_id)
+            |> join(:inner, [ou], o in Organization, on: o.id == ou.organization_id)
+            |> select([_ou, o], o)
+            |> Repo.all()
+          end
+
+          @doc """
+          Gets an organization by slug.
+          """
+          def get_organization_by_slug!(scope, slug) when is_binary(slug) do
+            # This function would typically check if the user has access to the organization
+            # For this example, we'll just return the organization by slug
+            Repo.one!(
+              from o in Organization,
+                join: ou in OrganizationUser, on: ou.organization_id == o.id,
+                join: u in User, on: ou.user_id == u.id,
+                where: u.id == ^scope.user.id,
+                select: o
+            )
+          end
+
+          @doc """
+          Creates a organization.
+          """
+          def create_organization(scope, attrs \\ %{}) do
+            %Organization{}
+            |> Organization.changeset(attrs)
+            |> Repo.insert()
+            |> case do
+              {:ok, organization} ->
+                # Create a membership for the user who created the organization
+                %OrganizationUser{}
+                |> OrganizationUser.changeset(%{
+                  organization_id: organization.id,
+                  user_id: scope.user.id,
+                  role: "owner"
+                })
+                |> Repo.insert()
+
+                {:ok, organization}
+
+              error ->
+                error
+            end
+          end
+
+          ''')
+        end)
+
+        # Update Scope struct to include organization
+        modify_file(Path.join(app_root_path, "lib/scopes/accounts/scope.ex"), fn file ->
+          String.replace(file, "defstruct user: nil", "defstruct user: nil, organization: nil")
+          |> inject_before_final_end("""
+          def put_organization(%__MODULE__{} = scope, %Scopes.Accounts.Organization{} = organization) do
+            %{scope | organization: organization}
+          end
+          """)
+        end)
+
+        # Update the fixtures to support organization_scope_fixture
+        modify_file(
+          Path.join(app_root_path, "test/support/fixtures/accounts_fixtures.ex"),
+          fn file ->
+            inject_before_final_end(file, """
+
+            def valid_organization_attributes(attrs \\\\ %{}) do
+              Enum.into(attrs, %{
+                name: "org\#{System.unique_integer()}",
+                slug: "org\#{System.unique_integer()}"
+              })
+            end
+
+            def organization_fixture(scope \\\\ user_scope_fixture()) do
+              attrs = valid_organization_attributes()
+              {:ok, organization} = Accounts.create_organization(scope, attrs)
+              organization
+            end
+
+            def organization_scope_fixture(scope \\\\ user_scope_fixture()) do
+              org = organization_fixture(scope)
+              Scope.put_organization(scope, org)
+            end
+            """)
+          end
+        )
+
+        # Add the register_and_log_in_user_with_org helper to ConnCase
+        modify_file(Path.join(app_root_path, "test/support/conn_case.ex"), fn file ->
+          inject_before_final_end(file, """
+
+          def register_and_log_in_user_with_org(context) do
+            %{conn: conn, user: user, scope: scope} = register_and_log_in_user(context)
+            scope = Scopes.AccountsFixtures.organization_scope_fixture(scope)
+            %{conn: conn, user: user, scope: scope}
+          end
+          """)
+        end)
+
+        # Modify the scopes config to include route_prefix and route_access_path for organization
+        modify_file(Path.join(app_root_path, "config/config.exs"), fn file ->
+          String.replace(
+            file,
+            """
+            config :scopes, :scopes,
+              user: [
+                default: true,
+                module: Scopes.Accounts.Scope,
+                assign_key: :current_scope,
+                access_path: [:user, :id],
+                schema_key: :user_id,
+                schema_type: :id,
+                schema_table: :users,
+                test_data_fixture: Scopes.AccountsFixtures,
+                test_login_helper: :register_and_log_in_user
+              ]
+            """,
+            """
+            config :scopes, :scopes,
+            user: [
+              default: true,
+              module: Scopes.Accounts.Scope,
+              assign_key: :current_scope,
+              access_path: [:user, :id],
+              schema_key: :user_id,
+              schema_type: :id,
+              schema_table: :users,
+              test_data_fixture: Scopes.AccountsFixtures,
+              test_login_helper: :register_and_log_in_user
+            ],
+            organization: [
+              module: Scopes.Accounts.Scope,
+              assign_key: :current_scope,
+              access_path: [:organization, :id],
+              route_prefix: "/orgs/:slug",
+              schema_key: :organization_id,
+              schema_type: :id,
+              schema_table: :organizations,
+              test_data_fixture: Scopes.AccountsFixtures,
+              test_login_helper: :register_and_log_in_user_with_org
+            ]
+            """
+          )
+        end)
+
+        # Extend the user auth module to assign org to scope
+        modify_file(
+          Path.join(app_root_path, "lib/scopes_web/user_auth.ex"),
+          fn file ->
+            inject_before_final_end(file, """
+
+            def assign_org_to_scope(conn, _opts) do
+              current_scope = conn.assigns.current_scope
+              if slug = conn.params["slug"] do
+                org = Scopes.Accounts.get_organization_by_slug!(current_scope, slug)
+                assign(conn, :current_scope, Scopes.Accounts.Scope.put_organization(current_scope, org))
+              else
+                conn
+              end
+            end
+            """)
+          end
+        )
+
+        # Add the LiveView hook for organization assignment
+        modify_file(
+          Path.join(app_root_path, "lib/scopes_web/user_auth.ex"),
+          fn file ->
+            String.replace(
+              file,
+              """
+                def on_mount(:mount_current_scope, _params, session, socket) do
+                  {:cont, mount_current_scope(socket, session)}
+                end
+              """,
+              """
+              def on_mount(:mount_current_scope, _params, session, socket) do
+                {:cont, mount_current_scope(socket, session)}
+              end
+
+              def on_mount(:assign_org_to_scope, %{"slug" => slug}, _session, socket) do
+                socket =
+                  case socket.assigns.current_scope do
+                    %{organization: nil} = scope ->
+                      org = Scopes.Accounts.get_organization_by_slug!(socket.assigns.current_scope, slug)
+                      Phoenix.Component.assign(socket, :current_scope, Scope.put_organization(scope, org))
+
+                    _ ->
+                      socket
+                  end
+
+                {:cont, socket}
+              end
+
+              def on_mount(:assign_org_to_scope, _params, _session, socket), do: {:cont, socket}
+              """
+            )
+          end
+        )
+
+        # Update the router to use the assign_org_to_scope plug
+        modify_file(Path.join(app_root_path, "lib/scopes_web/router.ex"), fn file ->
+          String.replace(file, "plug :fetch_current_scope_for_user", """
+          plug :fetch_current_scope_for_user
+          plug :assign_org_to_scope
+          """)
+        end)
+
+        # Generate resources with all three generators - use different contexts to avoid naming conflicts
+        Process.sleep(1500)
+
+        mix_run!(
+          ~w(phx.gen.html Blog1 Article articles title:string --scope organization),
+          app_root_path
+        )
+
+        Process.sleep(1500)
+
+        mix_run!(
+          ~w(phx.gen.live Blog2 Post posts title:string --scope organization),
+          app_root_path
+        )
+
+        Process.sleep(1500)
+
+        mix_run!(
+          ~w(phx.gen.json Blog3 Comment comments content:string --scope organization),
+          app_root_path
+        )
+
+        # Update LiveView routes in router.ex
+        modify_file(Path.join(app_root_path, "lib/scopes_web/router.ex"), fn file ->
+          String.replace(
+            file,
+            "{ScopesWeb.UserAuth, :require_authenticated}",
+            "{ScopesWeb.UserAuth, :require_authenticated}, {ScopesWeb.UserAuth, :assign_org_to_scope}"
+          )
+          |> String.replace(
+            "live \"/users/settings/confirm-email/:token\", UserLive.Settings, :confirm_email",
+            """
+            live "/users/settings/confirm-email/:token", UserLive.Settings, :confirm_email
+
+            live "/orgs/:slug/posts", PostLive.Index, :index
+            live "/orgs/:slug/posts/new", PostLive.Form, :new
+            live "/orgs/:slug/posts/:id", PostLive.Show, :show
+            live "/orgs/:slug/posts/:id/edit", PostLive.Form, :edit
+            """
+          )
+        end)
+
+        # Add the HTML routes
+        modify_file(Path.join(app_root_path, "lib/scopes_web/router.ex"), fn file ->
+          String.replace(
+            file,
+            "post \"/users/update-password\", UserSessionController, :update_password",
+            """
+            post "/users/update-password", UserSessionController, :update_password
+
+            resources "/orgs/:slug/articles", ArticleController
+            """
+          )
+        end)
+
+        # Add the JSON API routes
+        modify_file(Path.join(app_root_path, "lib/scopes_web/router.ex"), fn file ->
+          router_code = """
+
+          # API routes
+          scope "/api", ScopesWeb do
+            pipe_through [:api, :fetch_session, :fetch_current_scope_for_user, :assign_org_to_scope, :require_authenticated_user]
+
+            resources "/orgs/:slug/comments", CommentController, except: [:new, :edit]
+          end
+          """
+
+          inject_before_final_end(file, router_code)
+        end)
+
+        # Test HTML generator (Articles)
+        assert_file(
+          Path.join(app_root_path, "lib/scopes_web/controllers/article_html/index.html.heex"),
+          fn file ->
+            assert file =~
+                     ~s|href={~p"/orgs/\#{@current_scope.organization}/articles/new"|
+
+            assert file =~
+                     ~s|navigate={~p"/orgs/\#{@current_scope.organization}/articles/\#{article}"|
+          end
+        )
+
+        assert_file(
+          Path.join(app_root_path, "lib/scopes_web/controllers/article_controller.ex"),
+          fn file ->
+            assert file =~
+                     ~s|redirect(to: ~p"/orgs/\#{conn.assigns.current_scope.organization}/articles/\#{article}"|
+          end
+        )
+
+        assert_file(
+          Path.join(app_root_path, "test/scopes_web/controllers/article_controller_test.exs"),
+          fn file ->
+            assert file =~ ~s|~p"/orgs/\#{scope.organization}/articles"|
+          end
+        )
+
+        # Test LiveView generator (Posts)
+        assert_file(Path.join(app_root_path, "lib/scopes_web/live/post_live/index.ex"), fn file ->
+          assert file =~
+                   ~s|navigate={~p"/orgs/\#{@current_scope.organization}/posts/new"|
+
+          assert file =~
+                   ~s|JS.navigate(~p"/orgs/\#{@current_scope.organization}/posts/\#{post}")|
+        end)
+
+        assert_file(Path.join(app_root_path, "lib/scopes_web/live/post_live/show.ex"), fn file ->
+          assert file =~ ~s|navigate={~p"/orgs/\#{@current_scope.organization}/posts"|
+        end)
+
+        assert_file(
+          Path.join(app_root_path, "test/scopes_web/live/post_live_test.exs"),
+          fn file ->
+            assert file =~ ~s|~p"/orgs/\#{scope.organization}/posts"|
+          end
+        )
+
+        # Test JSON generator (Comments)
+        assert_file(
+          Path.join(app_root_path, "lib/scopes_web/controllers/comment_controller.ex"),
+          fn file ->
+            assert file =~
+                     ~s|~p"/api/orgs/\#{conn.assigns.current_scope.organization}/comments/\#{comment}"|
+          end
+        )
+
+        assert_file(
+          Path.join(app_root_path, "test/scopes_web/controllers/comment_controller_test.exs"),
+          fn file ->
+            assert file =~ ~s|~p"/api/orgs/\#{scope.organization}/comments"|
+          end
+        )
+
+        # Final app validations
+        assert_no_compilation_warnings(app_root_path)
+        drop_test_database(app_root_path)
+        assert_tests_pass(app_root_path)
+      end)
+    end
+
     test "phx.gen.json" do
       with_installer_tmp("scopes", fn tmp_dir ->
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "scopes")
diff --git a/priv/templates/phx.gen.auth/auth_test.exs b/priv/templates/phx.gen.auth/auth_test.exs
index b96996e63..ac3e4e1bc 100644
--- a/priv/templates/phx.gen.auth/auth_test.exs
+++ b/priv/templates/phx.gen.auth/auth_test.exs
@@ -189,13 +189,13 @@ defmodule <%= inspect auth_module %>Test do
     end
   end
 
-  describe "on_mount :ensure_authenticated" do
+  describe "on_mount :require_authenticated" do
     test "authenticates current_scope based on a valid <%= schema.singular %>_token", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
       <%= schema.singular %>_token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)
       session = conn |> put_session(:<%= schema.singular %>_token, <%= schema.singular %>_token) |> get_session()
 
       {:cont, updated_socket} =
-        <%= inspect schema.alias %>Auth.on_mount(:ensure_authenticated, %{}, session, %LiveView.Socket{})
+        <%= inspect schema.alias %>Auth.on_mount(:require_authenticated, %{}, session, %LiveView.Socket{})
 
       assert updated_socket.assigns.current_scope.<%= schema.singular %>.id == <%= schema.singular %>.id
     end
@@ -209,7 +209,7 @@ defmodule <%= inspect auth_module %>Test do
         assigns: %{__changed__: %{}, flash: %{}}
       }
 
-      {:halt, updated_socket} = <%= inspect schema.alias %>Auth.on_mount(:ensure_authenticated, %{}, session, socket)
+      {:halt, updated_socket} = <%= inspect schema.alias %>Auth.on_mount(:require_authenticated, %{}, session, socket)
       assert updated_socket.assigns.current_scope == nil
     end
 
@@ -221,12 +221,12 @@ defmodule <%= inspect auth_module %>Test do
         assigns: %{__changed__: %{}, flash: %{}}
       }
 
-      {:halt, updated_socket} = <%= inspect schema.alias %>Auth.on_mount(:ensure_authenticated, %{}, session, socket)
+      {:halt, updated_socket} = <%= inspect schema.alias %>Auth.on_mount(:require_authenticated, %{}, session, socket)
       assert updated_socket.assigns.current_scope == nil
     end
   end
 
-  describe "on_mount :ensure_sudo_mode" do
+  describe "on_mount :require_sudo_mode" do
     test "allows <%= schema.plural %> that have authenticated in the last 10 minutes", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
       <%= schema.singular %>_token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)
       session = conn |> put_session(:<%= schema.singular %>_token, <%= schema.singular %>_token) |> get_session()
@@ -237,7 +237,7 @@ defmodule <%= inspect auth_module %>Test do
       }
 
       assert {:cont, _updated_socket} =
-               <%= inspect schema.alias %>Auth.on_mount(:ensure_sudo_mode, %{}, session, socket)
+               <%= inspect schema.alias %>Auth.on_mount(:require_sudo_mode, %{}, session, socket)
     end
 
     test "redirects when authentication is too old", %{<%= schema.singular %>: <%= schema.singular %>} do
@@ -253,7 +253,7 @@ defmodule <%= inspect auth_module %>Test do
       }
 
       assert {:halt, _updated_socket} =
-               <%= inspect schema.alias %>Auth.on_mount(:ensure_sudo_mode, %{}, %{}, socket)
+               <%= inspect schema.alias %>Auth.on_mount(:require_sudo_mode, %{}, %{}, socket)
     end
   end<% else %>describe "require_sudo_mode/2" do
     test "allows <%= schema.plural %> that have authenticated in the last 10 minutes", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
diff --git a/priv/templates/phx.gen.html/controller_test.exs b/priv/templates/phx.gen.html/controller_test.exs
index aa868e4b8..82cc788b5 100644
--- a/priv/templates/phx.gen.html/controller_test.exs
+++ b/priv/templates/phx.gen.html/controller_test.exs
@@ -10,32 +10,32 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   setup :<%= scope.test_login_helper %><% end %>
 
   describe "index" do
-    test "lists all <%= schema.plural %>", %{conn: conn} do
-      conn = get(conn, ~p"<%= schema.route_prefix %>")
+    test "lists all <%= schema.plural %>", %{conn: conn<%= test_context_scope %>} do
+      conn = get(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>")
       assert html_response(conn, 200) =~ "Listing <%= schema.human_plural %>"
     end
   end
 
   describe "new <%= schema.singular %>" do
-    test "renders form", %{conn: conn} do
-      conn = get(conn, ~p"<%= schema.route_prefix %>/new")
+    test "renders form", %{conn: conn<%= test_context_scope %>} do
+      conn = get(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/new")
       assert html_response(conn, 200) =~ "New <%= schema.human_singular %>"
     end
   end
 
   describe "create <%= schema.singular %>" do
-    test "redirects to show when data is valid", %{conn: conn} do
-      conn = post(conn, ~p"<%= schema.route_prefix %>", <%= schema.singular %>: @create_attrs)
+    test "redirects to show when data is valid", %{conn: conn<%= test_context_scope %>} do
+      conn = post(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>", <%= schema.singular %>: @create_attrs)
 
       assert %{<%= primary_key %>: <%= primary_key %>} = redirected_params(conn)
-      assert redirected_to(conn) == ~p"<%= schema.route_prefix %>/#{<%= primary_key %>}"
+      assert redirected_to(conn) == ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= primary_key %>}"
 
-      conn = get(conn, ~p"<%= schema.route_prefix %>/#{<%= primary_key %>}")
+      conn = get(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= primary_key %>}")
       assert html_response(conn, 200) =~ "<%= schema.human_singular %> #{<%= primary_key %>}"
     end
 
-    test "renders errors when data is invalid", %{conn: conn} do
-      conn = post(conn, ~p"<%= schema.route_prefix %>", <%= schema.singular %>: @invalid_attrs)
+    test "renders errors when data is invalid", %{conn: conn<%= test_context_scope %>} do
+      conn = post(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>", <%= schema.singular %>: @invalid_attrs)
       assert html_response(conn, 200) =~ "New <%= schema.human_singular %>"
     end
   end
@@ -43,8 +43,8 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   describe "edit <%= schema.singular %>" do
     setup [:create_<%= schema.singular %>]
 
-    test "renders form for editing chosen <%= schema.singular %>", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
-      conn = get(conn, ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}/edit")
+    test "renders form for editing chosen <%= schema.singular %>", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do
+      conn = get(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}/edit")
       assert html_response(conn, 200) =~ "Edit <%= schema.human_singular %>"
     end
   end
@@ -52,17 +52,17 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   describe "update <%= schema.singular %>" do
     setup [:create_<%= schema.singular %>]
 
-    test "redirects when data is valid", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
-      conn = put(conn, ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}", <%= schema.singular %>: @update_attrs)
-      assert redirected_to(conn) == ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}"
+    test "redirects when data is valid", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do
+      conn = put(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}", <%= schema.singular %>: @update_attrs)
+      assert redirected_to(conn) == ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}"
 
-      conn = get(conn, ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}")<%= if schema.string_attr do %>
+      conn = get(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}")<%= if schema.string_attr do %>
       assert html_response(conn, 200) =~ <%= inspect Mix.Phoenix.Schema.default_param(schema, :update) %><% else %>
       assert html_response(conn, 200)<% end %>
     end
 
-    test "renders errors when data is invalid", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
-      conn = put(conn, ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}", <%= schema.singular %>: @invalid_attrs)
+    test "renders errors when data is invalid", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do
+      conn = put(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}", <%= schema.singular %>: @invalid_attrs)
       assert html_response(conn, 200) =~ "Edit <%= schema.human_singular %>"
     end
   end
@@ -70,12 +70,12 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   describe "delete <%= schema.singular %>" do
     setup [:create_<%= schema.singular %>]
 
-    test "deletes chosen <%= schema.singular %>", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
-      conn = delete(conn, ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}")
-      assert redirected_to(conn) == ~p"<%= schema.route_prefix %>"
+    test "deletes chosen <%= schema.singular %>", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do
+      conn = delete(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}")
+      assert redirected_to(conn) == ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>"
 
       assert_error_sent 404, fn ->
-        get(conn, ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}")
+        get(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}")
       end
     end
   end
diff --git a/priv/templates/phx.gen.json/controller_test.exs b/priv/templates/phx.gen.json/controller_test.exs
index 2e50cba56..4f7d58c76 100644
--- a/priv/templates/phx.gen.json/controller_test.exs
+++ b/priv/templates/phx.gen.json/controller_test.exs
@@ -19,18 +19,18 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   end
 
   describe "index" do
-    test "lists all <%= schema.plural %>", %{conn: conn} do
-      conn = get(conn, ~p"<%= schema.api_route_prefix %>")
+    test "lists all <%= schema.plural %>", %{conn: conn<%= test_context_scope %>} do
+      conn = get(conn, ~p"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>")
       assert json_response(conn, 200)["data"] == []
     end
   end
 
   describe "create <%= schema.singular %>" do
-    test "renders <%= schema.singular %> when data is valid", %{conn: conn} do
-      conn = post(conn, ~p"<%= schema.api_route_prefix %>", <%= schema.singular %>: @create_attrs)
+    test "renders <%= schema.singular %> when data is valid", %{conn: conn<%= test_context_scope %>} do
+      conn = post(conn, ~p"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>", <%= schema.singular %>: @create_attrs)
       assert %{"<%= primary_key %>" => <%= primary_key %>} = json_response(conn, 201)["data"]
 
-      conn = get(conn, ~p"<%= schema.api_route_prefix %>/#{<%= primary_key %>}")
+      conn = get(conn, ~p"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= primary_key %>}")
 
       assert %{
                "<%= primary_key %>" => ^<%= primary_key %><%= for {key, val} <- schema.params.create |> Phoenix.json_library().encode!() |> Phoenix.json_library().decode!() do %>,
@@ -38,8 +38,8 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
              } = json_response(conn, 200)["data"]
     end
 
-    test "renders errors when data is invalid", %{conn: conn} do
-      conn = post(conn, ~p"<%= schema.api_route_prefix %>", <%= schema.singular %>: @invalid_attrs)
+    test "renders errors when data is invalid", %{conn: conn<%= test_context_scope %>} do
+      conn = post(conn, ~p"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>", <%= schema.singular %>: @invalid_attrs)
       assert json_response(conn, 422)["errors"] != %{}
     end
   end
@@ -47,11 +47,11 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   describe "update <%= schema.singular %>" do
     setup [:create_<%= schema.singular %>]
 
-    test "renders <%= schema.singular %> when data is valid", %{conn: conn, <%= schema.singular %>: %<%= inspect schema.alias %>{<%= primary_key %>: <%= primary_key %>} = <%= schema.singular %>} do
-      conn = put(conn, ~p"<%= schema.api_route_prefix %>/#{<%= schema.singular %>}", <%= schema.singular %>: @update_attrs)
+    test "renders <%= schema.singular %> when data is valid", %{conn: conn, <%= schema.singular %>: %<%= inspect schema.alias %>{<%= primary_key %>: <%= primary_key %>} = <%= schema.singular %><%= test_context_scope %>} do
+      conn = put(conn, ~p"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}", <%= schema.singular %>: @update_attrs)
       assert %{"<%= primary_key %>" => ^<%= primary_key %>} = json_response(conn, 200)["data"]
 
-      conn = get(conn, ~p"<%= schema.api_route_prefix %>/#{<%= primary_key %>}")
+      conn = get(conn, ~p"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= primary_key %>}")
 
       assert %{
                "<%= primary_key %>" => ^<%= primary_key %><%= for {key, val} <- schema.params.update |> Phoenix.json_library().encode!() |> Phoenix.json_library().decode!() do %>,
@@ -59,8 +59,8 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
              } = json_response(conn, 200)["data"]
     end
 
-    test "renders errors when data is invalid", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
-      conn = put(conn, ~p"<%= schema.api_route_prefix %>/#{<%= schema.singular %>}", <%= schema.singular %>: @invalid_attrs)
+    test "renders errors when data is invalid", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do
+      conn = put(conn, ~p"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}", <%= schema.singular %>: @invalid_attrs)
       assert json_response(conn, 422)["errors"] != %{}
     end
   end
@@ -68,12 +68,12 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   describe "delete <%= schema.singular %>" do
     setup [:create_<%= schema.singular %>]
 
-    test "deletes chosen <%= schema.singular %>", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
-      conn = delete(conn, ~p"<%= schema.api_route_prefix %>/#{<%= schema.singular %>}")
+    test "deletes chosen <%= schema.singular %>", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do
+      conn = delete(conn, ~p"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}")
       assert response(conn, 204)
 
       assert_error_sent 404, fn ->
-        get(conn, ~p"<%= schema.api_route_prefix %>/#{<%= schema.singular %>}")
+        get(conn, ~p"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}")
       end
     end
   end
diff --git a/priv/templates/phx.gen.live/live_test.exs b/priv/templates/phx.gen.live/live_test.exs
index 5088a56e4..93c7225a4 100644
--- a/priv/templates/phx.gen.live/live_test.exs
+++ b/priv/templates/phx.gen.live/live_test.exs
@@ -22,21 +22,21 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   describe "Index" do
     setup [:create_<%= schema.singular %>]
 
-    test "lists all <%= schema.plural %>", <%= if schema.string_attr do %>%{conn: conn, <%= schema.singular %>: <%= schema.singular %>}<% else %>%{conn: conn}<% end %> do
-      {:ok, _index_live, html} = live(conn, ~p"<%= schema.route_prefix %>")
+    test "lists all <%= schema.plural %>", <%= if schema.string_attr do %>%{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>}<% else %>%{conn: conn<%= test_context_scope %>}<% end %> do
+      {:ok, _index_live, html} = live(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>")
 
       assert html =~ "Listing <%= schema.human_plural %>"<%= if schema.string_attr do %>
       assert html =~ <%= schema.singular %>.<%= schema.string_attr %><% end %>
     end
 
-    test "saves new <%= schema.singular %>", %{conn: conn} do
-      {:ok, index_live, _html} = live(conn, ~p"<%= schema.route_prefix %>")
+    test "saves new <%= schema.singular %>", %{conn: conn<%= test_context_scope %>} do
+      {:ok, index_live, _html} = live(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>")
 
       assert {:ok, form_live, _} =
                index_live
                |> element("a", "New <%= schema.human_singular %>")
                |> render_click()
-               |> follow_redirect(conn, ~p"<%= schema.route_prefix %>/new")
+               |> follow_redirect(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/new")
 
       assert render(form_live) =~ "New <%= schema.human_singular %>"
 
@@ -48,21 +48,21 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
                form_live
                |> form("#<%= schema.singular %>-form", <%= schema.singular %>: @create_attrs)
                |> render_submit()
-               |> follow_redirect(conn, ~p"<%= schema.route_prefix %>")
+               |> follow_redirect(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>")
 
       html = render(index_live)
       assert html =~ "<%= schema.human_singular %> created successfully"<%= if schema.string_attr do %>
       assert html =~ "some <%= schema.string_attr %>"<% end %>
     end
 
-    test "updates <%= schema.singular %> in listing", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
-      {:ok, index_live, _html} = live(conn, ~p"<%= schema.route_prefix %>")
+    test "updates <%= schema.singular %> in listing", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do
+      {:ok, index_live, _html} = live(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>")
 
       assert {:ok, form_live, _html} =
                index_live
                |> element("#<%= schema.plural %>-#{<%= schema.singular %>.<%= primary_key %>} a", "Edit")
                |> render_click()
-               |> follow_redirect(conn, ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}/edit")
+               |> follow_redirect(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}/edit")
 
       assert render(form_live) =~ "Edit <%= schema.human_singular %>"
 
@@ -74,15 +74,15 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
                form_live
                |> form("#<%= schema.singular %>-form", <%= schema.singular %>: @update_attrs)
                |> render_submit()
-               |> follow_redirect(conn, ~p"<%= schema.route_prefix %>")
+               |> follow_redirect(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>")
 
       html = render(index_live)
       assert html =~ "<%= schema.human_singular %> updated successfully"<%= if schema.string_attr do %>
       assert html =~ "some updated <%= schema.string_attr %>"<% end %>
     end
 
-    test "deletes <%= schema.singular %> in listing", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
-      {:ok, index_live, _html} = live(conn, ~p"<%= schema.route_prefix %>")
+    test "deletes <%= schema.singular %> in listing", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do
+      {:ok, index_live, _html} = live(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>")
 
       assert index_live |> element("#<%= schema.plural %>-#{<%= schema.singular %>.<%= primary_key %>} a", "Delete") |> render_click()
       refute has_element?(index_live, "#<%= schema.plural %>-#{<%= schema.singular %>.<%= primary_key %>}")
@@ -92,21 +92,21 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   describe "Show" do
     setup [:create_<%= schema.singular %>]
 
-    test "displays <%= schema.singular %>", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
-      {:ok, _show_live, html} = live(conn, ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}")
+    test "displays <%= schema.singular %>", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do
+      {:ok, _show_live, html} = live(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}")
 
       assert html =~ "Show <%= schema.human_singular %>"<%= if schema.string_attr do %>
       assert html =~ <%= schema.singular %>.<%= schema.string_attr %><% end %>
     end
 
-    test "updates <%= schema.singular %> and returns to show", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
-      {:ok, show_live, _html} = live(conn, ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}")
+    test "updates <%= schema.singular %> and returns to show", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do
+      {:ok, show_live, _html} = live(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}")
 
       assert {:ok, form_live, _} =
                show_live
                |> element("a", "Edit")
                |> render_click()
-               |> follow_redirect(conn, ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}/edit?return_to=show")
+               |> follow_redirect(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}/edit?return_to=show")
 
       assert render(form_live) =~ "Edit <%= schema.human_singular %>"
 
@@ -118,7 +118,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
                form_live
                |> form("#<%= schema.singular %>-form", <%= schema.singular %>: @update_attrs)
                |> render_submit()
-               |> follow_redirect(conn, ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}")
+               |> follow_redirect(conn, ~p"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}")
 
       html = render(show_live)
       assert html =~ "<%= schema.human_singular %> updated successfully"<%= if schema.string_attr do %>
diff --git a/test/mix/tasks/phx.gen.auth_test.exs b/test/mix/tasks/phx.gen.auth_test.exs
index a22a6a6d0..c27807d4b 100644
--- a/test/mix/tasks/phx.gen.auth_test.exs
+++ b/test/mix/tasks/phx.gen.auth_test.exs
@@ -311,7 +311,7 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
                    pipe_through [:browser, :require_authenticated_user]
 
                    live_session :require_authenticated_user,
-                     on_mount: [{MyAppWeb.UserAuth, :ensure_authenticated}] do
+                     on_mount: [{MyAppWeb.UserAuth, :require_authenticated}] do
                      live "/users/settings", UserLive.Settings, :edit
                      live "/users/settings/confirm-email/:token", UserLive.Settings, :confirm_email
                    end
@@ -398,7 +398,7 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
                    pipe_through [:browser, :require_authenticated_user]
 
                    live_session :require_authenticated_user,
-                     on_mount: [{MyAppWeb.UserAuth, :ensure_authenticated}] do
+                     on_mount: [{MyAppWeb.UserAuth, :require_authenticated}] do
                      live "/users/settings", UserLive.Settings, :edit
                      live "/users/settings/confirm-email/:token", UserLive.Settings, :confirm_email
                    end
@@ -1387,11 +1387,13 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
             ],
             accounts_user: [
               default: false,
-              module: MyApp.Accounts.Scope
+              module: MyApp.Accounts.Scope,
+              access_path: []
             ],
             my_app_accounts_user: [
               default: false,
-              module: MyApp.Accounts.Scope
+              module: MyApp.Accounts.Scope,
+              access_path: []
             ]
           ],
           fn ->
diff --git a/test/mix/tasks/phx.gen.html_test.exs b/test/mix/tasks/phx.gen.html_test.exs
index 7aa292eca..0805e7078 100644
--- a/test/mix/tasks/phx.gen.html_test.exs
+++ b/test/mix/tasks/phx.gen.html_test.exs
@@ -485,5 +485,52 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
         end)
       end)
     end
+
+    test "respect route_prefix in scopes", config do
+      in_tmp_project(config.test, fn ->
+        with_scope_env(
+          :phoenix,
+          [
+            organization: [
+              module: Phoenix.Organizations.Scope,
+              assign_key: :current_organization,
+              access_path: [:organization, :id],
+              route_access_path: [:organization, :slug],
+              route_prefix: "/orgs/:slug"
+            ]
+          ],
+          fn ->
+            Gen.Html.run(~w(Blog Post posts title:string --scope organization))
+
+        assert_file "lib/phoenix_web/controllers/post_controller.ex", fn file ->
+          assert file =~ ~s|redirect(to: ~p"/orgs/\#{conn.assigns.current_organization.organization.slug}/posts|
+        end
+
+        assert_file "lib/phoenix_web/controllers/post_html/index.html.heex", fn file ->
+          assert file =~ ~s|href={~p"/orgs/\#{@current_organization.organization.slug}/posts/new"|
+          assert file =~ ~s|navigate={~p"/orgs/\#{@current_organization.organization.slug}/posts/|
+        end
+
+        assert_file "lib/phoenix_web/controllers/post_html/show.html.heex", fn file ->
+          assert file =~ ~s|navigate={~p"/orgs/\#{@current_organization.organization.slug}/posts"|
+        end
+
+        assert_file "lib/phoenix_web/controllers/post_html/edit.html.heex", fn file ->
+          assert file =~ ~s|action={~p"/orgs/\#{@current_organization.organization.slug}/posts/\#{@post}"|
+        end
+
+        assert_file "lib/phoenix_web/controllers/post_html/new.html.heex", fn file ->
+          assert file =~ ~s|action={~p"/orgs/\#{@current_organization.organization.slug}/posts"|
+        end
+
+        assert_file "test/phoenix_web/controllers/post_controller_test.exs", fn file ->
+          assert file =~ ~s|~p"/orgs/\#{scope.organization.slug}/posts"|
+          assert file =~ ~s|~p"/orgs/\#{scope.organization.slug}/posts/new"|
+          assert file =~ ~s|~p"/orgs/\#{scope.organization.slug}/posts/\#{post}"|
+          assert file =~ ~s|~p"/orgs/\#{scope.organization.slug}/posts/\#{post}/edit"|
+        end
+          end)
+        end)
+      end
   end
 end
diff --git a/test/mix/tasks/phx.gen.json_test.exs b/test/mix/tasks/phx.gen.json_test.exs
index eed4ddbe7..0331ff66c 100644
--- a/test/mix/tasks/phx.gen.json_test.exs
+++ b/test/mix/tasks/phx.gen.json_test.exs
@@ -375,4 +375,32 @@ defmodule Mix.Tasks.Phx.Gen.JsonTest do
       end
     end)
   end
-end
+
+  test "respect route_prefix in scopes", config do
+    in_tmp_project(config.test, fn ->
+      with_scope_env(
+        :phoenix,
+        [
+          organization: [
+            module: Phoenix.Organizations.Scope,
+            assign_key: :current_organization,
+            access_path: [:organization, :id],
+            route_access_path: [:organization, :slug],
+            route_prefix: "/orgs/:slug"
+          ]
+        ],
+        fn ->
+          Gen.Json.run(~w(Blog Post posts title:string --scope organization))
+
+      assert_file "lib/phoenix_web/controllers/post_controller.ex", fn file ->
+        assert file =~ ~s|~p"/api/orgs/\#{conn.assigns.current_organization.organization.slug}/posts|
+      end
+
+      assert_file "test/phoenix_web/controllers/post_controller_test.exs", fn file ->
+        assert file =~ ~s|~p"/api/orgs/\#{scope.organization.slug}/posts"|
+        assert file =~ ~s|~p"/api/orgs/\#{scope.organization.slug}/posts/\#{post}"|
+      end
+        end)
+      end)
+    end
+  end
diff --git a/test/mix/tasks/phx.gen.live_test.exs b/test/mix/tasks/phx.gen.live_test.exs
index 665b20cba..79b53f198 100644
--- a/test/mix/tasks/phx.gen.live_test.exs
+++ b/test/mix/tasks/phx.gen.live_test.exs
@@ -464,4 +464,44 @@ defmodule Mix.Tasks.Phx.Gen.LiveTest do
       Mix.Tasks.Phx.Gen.Live.run(~w(Blog Form forms title:string))
     end
   end
-end
+
+  test "respect route_prefix in scopes", config do
+    in_tmp_live_project config.test, fn ->
+      with_scope_env(
+        :phoenix,
+        [
+          organization: [
+            module: Phoenix.Organizations.Scope,
+            assign_key: :current_organization,
+            access_path: [:organization, :id],
+            route_access_path: [:organization, :slug],
+            route_prefix: "/orgs/:slug"
+          ]
+        ],
+        fn ->
+          Gen.Live.run(~w(Blog Post posts title:string --scope organization))
+
+      assert_file "lib/phoenix_web/live/post_live/index.ex", fn file ->
+        assert file =~ ~s|navigate={~p"/orgs/\#{@current_organization.organization.slug}/posts|
+        assert file =~ ~s|navigate={~p"/orgs/\#{@current_organization.organization.slug}/posts/new"|
+      end
+
+      assert_file "lib/phoenix_web/live/post_live/show.ex", fn file ->
+        assert file =~ ~s|navigate={~p"/orgs/\#{@current_organization.organization.slug}/posts"|
+        assert file =~ ~s|navigate={~p"/orgs/\#{@current_organization.organization.slug}/posts/\#{@post}/edit|
+      end
+
+      assert_file "lib/phoenix_web/live/post_live/form.ex", fn file ->
+        assert file =~ ~s|defp return_path(scope, "index", _post), do: ~p"/orgs/\#{scope.organization.slug}/posts"|
+      end
+
+      assert_file "test/phoenix_web/live/post_live_test.exs", fn file ->
+        assert file =~ ~s|~p"/orgs/\#{scope.organization.slug}/posts"|
+        assert file =~ ~s|~p"/orgs/\#{scope.organization.slug}/posts/new"|
+        assert file =~ ~s|~p"/orgs/\#{scope.organization.slug}/posts/\#{post}"|
+        assert file =~ ~s|~p"/orgs/\#{scope.organization.slug}/posts/\#{post}/edit"|
+      end
+        end)
+      end
+    end
+  end