diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09947f0..ac685d6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Changelog
 
+## main
+
+- `Ecto.ParameterizedType` implementation traverses associations so prefixes only need to be defined on schema primary keys
+- `Ecto.ParameterizedType` implementation `type` option can be set globally with a `default_type` Application configuration
+
 ## 0.4.0
 
 - Implements `Jason.Encoder` protocol
diff --git a/lib/type_id.ex b/lib/type_id.ex
index cece53c..53eaaee 100644
--- a/lib/type_id.ex
+++ b/lib/type_id.ex
@@ -275,81 +275,22 @@ defmodule TypeID do
     use Ecto.ParameterizedType
 
     @impl Ecto.ParameterizedType
-    def init(opts), do: validate_opts!(opts)
+    defdelegate init(opts), to: TypeID.Ecto
 
     @impl Ecto.ParameterizedType
-    def type(%{type: type}), do: type
+    defdelegate type(params), to: TypeID.Ecto
 
     @impl Ecto.ParameterizedType
-    def autogenerate(%{prefix: prefix}) do
-      new(prefix)
-    end
+    defdelegate autogenerate(params), to: TypeID.Ecto
 
     @impl Ecto.ParameterizedType
-    def cast(nil, _params), do: {:ok, nil}
-    def cast(%__MODULE__{prefix: prefix} = tid, %{prefix: prefix}), do: {:ok, tid}
-
-    def cast(str, %{prefix: prefix}) when is_binary(str) do
-      if String.starts_with?(str, prefix) do
-        from_string(str)
-      else
-        with {:ok, uuid} <- Ecto.UUID.cast(str) do
-          from_uuid(prefix, uuid)
-        end
-      end
-    end
-
-    def cast(_, _), do: :error
+    defdelegate cast(data, params), to: TypeID.Ecto
 
     @impl Ecto.ParameterizedType
-    def dump(nil, _dumper, _params), do: {:ok, nil}
-
-    def dump(%__MODULE__{prefix: prefix} = tid, _, %{prefix: prefix, type: :string}) do
-      {:ok, __MODULE__.to_string(tid)}
-    end
-
-    def dump(%__MODULE__{prefix: prefix} = tid, _, %{prefix: prefix, type: :binary_id}) do
-      {:ok, uuid(tid)}
-    end
-
-    def dump(_, _, _), do: :error
+    defdelegate dump(data, dumper, params), to: TypeID.Ecto
 
     @impl Ecto.ParameterizedType
-    def load(nil, _, _), do: {:ok, nil}
-
-    def load(str, _, %{type: :string, prefix: prefix}) do
-      with {:ok, %__MODULE__{prefix: ^prefix}} = loaded <- from_string(str) do
-        loaded
-      end
-    end
-
-    def load(<<_::128>> = uuid, _, %{type: :binary_id, prefix: prefix}) do
-      from_uuid_bytes(prefix, uuid)
-    end
-
-    def load(<<_::288>> = uuid, _, %{type: :binary_id, prefix: prefix}) do
-      from_uuid(prefix, uuid)
-    rescue
-      _ -> :error
-    end
-
-    def load(_, _, _), do: :error
-
-    defp validate_opts!(opts) do
-      type = Keyword.get(opts, :type, :string)
-      prefix = Keyword.get(opts, :prefix, "")
-
-      unless prefix && prefix =~ ~r/^[a-z]{0,63}$/ do
-        raise ArgumentError,
-              "must specify `prefix` using only lowercase letters between 0 and 63 characters long."
-      end
-
-      unless type in ~w[string binary_id]a do
-        raise ArgumentError, "`type` must be `:string` or `:binary_id`"
-      end
-
-      %{prefix: prefix, type: type}
-    end
+    defdelegate load(data, loader, params), to: TypeID.Ecto
   end
 end
 
diff --git a/lib/type_id/ecto.ex b/lib/type_id/ecto.ex
new file mode 100644
index 0000000..a32f18f
--- /dev/null
+++ b/lib/type_id/ecto.ex
@@ -0,0 +1,118 @@
+if Code.ensure_loaded?(Ecto.ParameterizedType) do
+  defmodule TypeID.Ecto do
+    @moduledoc false
+
+    @doc false
+    def init(opts), do: validate_opts!(opts)
+
+    @doc false
+    def type(%{type: type}), do: type
+
+    @doc false
+    def autogenerate(params) do
+      params
+      |> find_prefix()
+      |> TypeID.new()
+    end
+
+    @doc false
+    def cast(nil, _params), do: {:ok, nil}
+
+    def cast(%TypeID{prefix: prefix} = tid, params) do
+      if prefix == find_prefix(params) do
+        {:ok, tid}
+      else
+        :error
+      end
+    end
+
+    def cast(str, params) when is_binary(str) do
+      prefix = find_prefix(params)
+
+      if String.starts_with?(str, prefix) do
+        TypeID.from_string(str)
+      else
+        with {:ok, uuid} <- Ecto.UUID.cast(str) do
+          TypeID.from_uuid(prefix, uuid)
+        end
+      end
+    end
+
+    def cast(_, _), do: :error
+
+    @doc false
+    def dump(nil, _dumper, _params), do: {:ok, nil}
+
+    def dump(%TypeID{} = tid, _, %{type: type} = params) do
+      prefix = find_prefix(params)
+
+      case {tid.prefix, type} do
+        {^prefix, :string} -> {:ok, TypeID.to_string(tid)}
+        {^prefix, :binary_id} -> {:ok, TypeID.uuid(tid)}
+        _ -> :error
+      end
+    end
+
+    def dump(_, _, _), do: :error
+
+    @doc false
+    def load(nil, _, _), do: {:ok, nil}
+
+    def load(str, _, %{type: :string} = params) do
+      prefix = find_prefix(params)
+
+      with {:ok, %TypeID{prefix: ^prefix}} = loaded <- TypeID.from_string(str) do
+        loaded
+      end
+    end
+
+    def load(<<_::128>> = uuid, _, %{type: :binary_id} = params) do
+      prefix = find_prefix(params)
+      TypeID.from_uuid_bytes(prefix, uuid)
+    end
+
+    def load(<<_::288>> = uuid, _, %{type: :binary_id} = params) do
+      prefix = find_prefix(params)
+      TypeID.from_uuid(prefix, uuid)
+    rescue
+      _ -> :error
+    end
+
+    def load(_, _, _), do: :error
+
+    defp validate_opts!(opts) do
+      primary_key = Keyword.get(opts, :primary_key, false)
+      schema = Keyword.fetch!(opts, :schema)
+      field = Keyword.fetch!(opts, :field)
+      default_type = Application.get_env(:typeid_elixir, :default_type, :string)
+      type = Keyword.get(opts, :type, default_type)
+      prefix = Keyword.get(opts, :prefix, "")
+
+      if primary_key do
+        unless prefix && prefix =~ ~r/^[a-z]{0,63}$/ do
+          raise ArgumentError,
+                "must specify `prefix` using only lowercase letters between 0 and 63 characters long."
+        end
+      end
+
+      unless type in ~w[string binary_id]a do
+        raise ArgumentError, "`type` must be `:string` or `:binary_id`"
+      end
+
+      if primary_key do
+        %{primary_key: primary_key, schema: schema, field: field, prefix: prefix, type: type}
+      else
+        %{schema: schema, field: field, type: type}
+      end
+    end
+
+    defp find_prefix(%{prefix: prefix}), do: prefix
+
+    defp find_prefix(%{schema: schema, field: field}) do
+      %{related: schema, related_key: field} = schema.__schema__(:association, field)
+      {:parameterized, TypeID, %{prefix: prefix}} = schema.__schema__(:type, field)
+
+      prefix
+    end
+  end
+end