diff --git a/core/src/main/scala-2/caliban/schema/SchemaVersionSpecific.scala b/core/src/main/scala-2/caliban/schema/SchemaVersionSpecific.scala
new file mode 100644
index 000000000..e601e2c0b
--- /dev/null
+++ b/core/src/main/scala-2/caliban/schema/SchemaVersionSpecific.scala
@@ -0,0 +1,3 @@
+package caliban.schema
+
+trait SchemaVersionSpecific
diff --git a/core/src/main/scala-3/caliban/schema/SchemaVersionSpecific.scala b/core/src/main/scala-3/caliban/schema/SchemaVersionSpecific.scala
new file mode 100644
index 000000000..7d3baf6a9
--- /dev/null
+++ b/core/src/main/scala-3/caliban/schema/SchemaVersionSpecific.scala
@@ -0,0 +1,33 @@
+package caliban.schema
+
+import caliban.introspection.adt.__Field
+import caliban.parsing.adt.Directive
+
+transparent trait SchemaVersionSpecific extends GenericSchema[Any] {
+
+  /**
+   * Scala 3 variant of the `obj` method which improves UX for creating custom object schemas.
+   *
+   * {{{
+   *  case class Author(id: String, firstName: String, lastName: String)
+   *
+   *  given Schema[Any, Author] = Schema.customObj("Author")(
+   *    field("id")(_.id),
+   *    field("fullName")(author => s"${author.firstName} ${author.lastName}"),
+   *  )
+   * }}}
+   *
+   * @see [[caliban.schema.GenericSchema.obj]]
+   */
+  def customObj[R1, V](
+    name: String,
+    description: Option[String] = None,
+    directives: List[Directive] = Nil
+  )(
+    fields: FieldAttributes ?=> (__Field, V => Step[R1])*
+  ): Schema[R1, V] =
+    obj(name, description, directives) { case given FieldAttributes =>
+      fields.toList.map(identity)
+    }
+
+}
diff --git a/core/src/main/scala/caliban/schema/Schema.scala b/core/src/main/scala/caliban/schema/Schema.scala
index b7c00fc86..8193ad0c9 100644
--- a/core/src/main/scala/caliban/schema/Schema.scala
+++ b/core/src/main/scala/caliban/schema/Schema.scala
@@ -142,7 +142,7 @@ trait Schema[-R, T] { self =>
   }
 }
 
-object Schema extends GenericSchema[Any]
+object Schema extends GenericSchema[Any] with SchemaVersionSpecific
 
 trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
 
@@ -311,6 +311,8 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
    *  )
    *
    * }}}
+   *
+   * @see `customObj` for an improved variant using context functions (Scala 3 only)
    */
   def obj[R1, V](name: String, description: Option[String] = None, directives: List[Directive] = Nil)(
     fields: FieldAttributes => List[(__Field, V => Step[R1])]
diff --git a/core/src/test/scala-3/caliban/schema/SchemaDerivesAutoSpec.scala b/core/src/test/scala-3/caliban/schema/SchemaDerivesAutoSpec.scala
index 7475cfe8f..1478bf258 100644
--- a/core/src/test/scala-3/caliban/schema/SchemaDerivesAutoSpec.scala
+++ b/core/src/test/scala-3/caliban/schema/SchemaDerivesAutoSpec.scala
@@ -273,6 +273,7 @@ object SchemaDerivesAutoSpec extends ZIOSpecDefault {
 
             |type A {
             |  a: String!
+            |  b: Int
             |}
 
             |type Queries {
@@ -280,10 +281,10 @@ object SchemaDerivesAutoSpec extends ZIOSpecDefault {
             |}""".stripMargin
         List(
           test("from GenericSchema[Any]") {
-            case class A(a: String)
+            case class A(a: String, b: Option[Int])
             case class Queries(as: List[A]) derives Schema.Auto
 
-            val resolver = RootResolver(Queries(List(A("a"), A("b"))))
+            val resolver = RootResolver(Queries(List(A("a", None), A("b", None))))
             val gql      = graphQL(resolver)
 
             assertTrue(gql.render == expected)
@@ -292,10 +293,10 @@ object SchemaDerivesAutoSpec extends ZIOSpecDefault {
             trait Foo
             object FooSchema extends SchemaDerivation[Foo]
 
-            case class A(a: String)
+            case class A(a: String, b: Option[Int])
             case class Queries(as: List[A]) derives FooSchema.Auto
 
-            val resolver = RootResolver(Queries(List(A("a"), A("b"))))
+            val resolver = RootResolver(Queries(List(A("a", None), A("b", None))))
             val gql      = graphQL(resolver)
 
             assertTrue(gql.render == expected)
@@ -303,9 +304,10 @@ object SchemaDerivesAutoSpec extends ZIOSpecDefault {
           test("from local scope") {
             case class A(a: Int)
 
-            given Schema[Any, A] = Schema.obj[Any, A]("A") { case given FieldAttributes =>
-              List(Schema.field("a")(_.a.toString))
-            }
+            given Schema[Any, A] = Schema.customObj[Any, A]("A")(
+              Schema.field("a")(_.a.toString),
+              Schema.field("b")(v => Option(v.a))
+            )
 
             case class Queries(as: List[A]) derives Schema.Auto
 
@@ -319,9 +321,10 @@ object SchemaDerivesAutoSpec extends ZIOSpecDefault {
             object FooSchema extends SchemaDerivation[Foo]
             case class A(a: Int)
 
-            given Schema[Any, A] = Schema.obj[Any, A]("A") { case given FieldAttributes =>
-              List(Schema.field("a")(_.a.toString))
-            }
+            given Schema[Any, A] = Schema.customObj[Any, A]("A")(
+              Schema.field("a")(_.a.toString),
+              Schema.field("b")(v => Option(v.a))
+            )
 
             case class Queries(as: List[A]) derives FooSchema.Auto