ostap
Matching all subdomains in Phoenix routing
I love how one can define a host where the last bit would automatically get matched:
scope "/", host: "about.mysite." do`
But it’s impossible to do the opposite end:
scope "/", host: ".mysite.com" do
(for matching all remaining subdomains)
What are the the implications that prevent this from being possible and what are the alternatives for implementing wildcard subdomain matching in a Phoenix project?
Out of curiosity found that this logic is defined in Plug.Router.Utils.build_host_match:
def build_host_match(host) do
cond do
is_nil(host) -> quote do: _
String.last(host) == "." -> quote do: unquote(host) <> _
is_binary(host) -> host
end
end
Therefore I naively tried adding this matching rule
:
String.first(host) == "." -> quote do: _ <> unquote(host)
But it’s not that easy, since it results in:
Compiling 1 file (.ex)
error: a binary field without size is only allowed at the end of a binary pattern, at the right side of binary concatenation and never allowed in binary generators. The following examples are invalid:
rest <> "foo"
<<rest::binary, "foo">>
"foo" <> rest
<<"foo", rest::binary>>
lib/my_app_web/router.ex:1
Marked As Solved
sodapopcan
In short, the “match any subdomain” clause in the router is to just not specify the :host option. The subdomain itself will be available in conn.host so you could pull it out in a plug and do whatever you need to do with it.
If you want different routes depending on the presence of a subdomain, that’s a little different. There are a few discussions around here about it but I’m having trouble finding the ones I found helpful. I do have the (abandon) project I was using it in, though. In short you want to make a plug, check if there is a subdomain then delegate to another router. Maybe there is an easier way at this point? In any event, here is my code:
# lib/my_app_web/subdomain_router.ex
defmodule MyAppWeb.SubdomainRouter do
# This is just a regular router but with subdomain specific routes
end
# lib/my_app_web/plugs/subdomain_plug
defmodule MyAppWeb.SubdomainPlug do
import Plug.Conn
def init(opts), do: opts
def call(conn, router) do
case get_subdomain(conn.host) do
subdomain when byte_size(subdomain) > 0 ->
conn
|> fetch_session()
|> put_session(:subdomain, subdomain)
|> router.call(router.init({}))
|> halt()
_ -> conn
end
end
defp get_subdomain(host) do
root_host = MyAppWeb.Endpoint.config(:url)[:host]
if root_host == "localhost" do
# This handles tests
""
else
String.replace(host, ~r/.?#{root_host}/, "")
end
end
end
# BOTTOM of lib/my_app_web/endpoint.ex
defmodule MyAppWeb.Endpoint do
# ...
plug MyAppWeb.SubdomainPlug, MyAppWeb.SubdomainRouter
plug MyAppWeb.Router
end
Maybe it’s easier at this point? I did always find this to be a little bit of a pain point in Phoenix. Actually, have you tried simply host: "." at all? I can’t remember if that’s a thing or not and don’t feel like spinning up that app ![]()
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









