diff --git a/codegen-sbt/src/main/scala/caliban/codegen/CalibanSettings.scala b/codegen-sbt/src/main/scala/caliban/codegen/CalibanSettings.scala
index 48719e429..15c0c61a5 100644
--- a/codegen-sbt/src/main/scala/caliban/codegen/CalibanSettings.scala
+++ b/codegen-sbt/src/main/scala/caliban/codegen/CalibanSettings.scala
@@ -13,6 +13,8 @@ sealed trait CalibanSettings {
   def genView: Option[Boolean]
   def scalarMappings: Seq[(String, String)]
   def imports: Seq[String]
+  def splitFiles: Option[Boolean]
+  def enableFmt: Option[Boolean]
 
   def append(other: Type): Type
 }
@@ -24,7 +26,9 @@ case class CalibanFileSettings(
   packageName: Option[String],
   genView: Option[Boolean],
   scalarMappings: Seq[(String, String)],
-  imports: Seq[String]
+  imports: Seq[String],
+  splitFiles: Option[Boolean],
+  enableFmt: Option[Boolean]
 ) extends CalibanSettings {
   type Type = CalibanFileSettings
   val headers = Seq.empty // Not applicable for file generator
@@ -37,7 +41,9 @@ case class CalibanFileSettings(
       packageName = other.packageName.orElse(packageName),
       genView = other.genView.orElse(genView),
       scalarMappings = scalarMappings ++ other.scalarMappings,
-      imports = imports ++ other.imports
+      imports = imports ++ other.imports,
+      splitFiles = other.splitFiles.orElse(splitFiles),
+      enableFmt = other.enableFmt.orElse(enableFmt)
     )
 
   def clientName(value: String): CalibanFileSettings                 = this.copy(clientName = Some(value))
@@ -47,6 +53,8 @@ case class CalibanFileSettings(
   def scalarMapping(mapping: (String, String)*): CalibanFileSettings =
     this.copy(scalarMappings = this.scalarMappings ++ mapping)
   def imports(values: String*): CalibanFileSettings                  = this.copy(imports = this.imports ++ values)
+  def splitFiles(value: Boolean): CalibanFileSettings                = this.copy(splitFiles = Some(value))
+  def enableFmt(value: Boolean): CalibanFileSettings                 = this.copy(enableFmt = Some(value))
 }
 
 case class CalibanUrlSettings(
@@ -57,7 +65,9 @@ case class CalibanUrlSettings(
   packageName: Option[String],
   genView: Option[Boolean],
   scalarMappings: Seq[(String, String)],
-  imports: Seq[String]
+  imports: Seq[String],
+  splitFiles: Option[Boolean],
+  enableFmt: Option[Boolean]
 ) extends CalibanSettings {
   type Type = CalibanUrlSettings
   def append(other: CalibanUrlSettings): CalibanUrlSettings =
@@ -69,7 +79,9 @@ case class CalibanUrlSettings(
       packageName = other.packageName.orElse(packageName),
       genView = other.genView.orElse(genView),
       scalarMappings = scalarMappings ++ other.scalarMappings,
-      imports = imports ++ other.imports
+      imports = imports ++ other.imports,
+      splitFiles = other.splitFiles.orElse(splitFiles),
+      enableFmt = other.enableFmt.orElse(enableFmt)
     )
 
   def clientName(value: String): CalibanUrlSettings                 = this.copy(clientName = Some(value))
@@ -81,6 +93,8 @@ case class CalibanUrlSettings(
   def scalarMapping(mapping: (String, String)*): CalibanUrlSettings =
     this.copy(scalarMappings = this.scalarMappings ++ mapping)
   def imports(values: String*): CalibanUrlSettings                  = this.copy(imports = this.imports ++ values)
+  def splitFiles(value: Boolean): CalibanUrlSettings                = this.copy(splitFiles = Some(value))
+  def enableFmt(value: Boolean): CalibanUrlSettings                 = this.copy(enableFmt = Some(value))
 }
 
 object CalibanSettings {
@@ -92,7 +106,9 @@ object CalibanSettings {
     packageName = Option.empty[String],
     genView = Option.empty[Boolean],
     scalarMappings = Seq.empty[(String, String)],
-    imports = Seq.empty[String]
+    imports = Seq.empty[String],
+    splitFiles = Option.empty[Boolean],
+    enableFmt = Option.empty[Boolean]
   )
 
   def emptyUrl(url: URL): CalibanUrlSettings = CalibanUrlSettings(
@@ -103,6 +119,8 @@ object CalibanSettings {
     packageName = Option.empty[String],
     genView = Option.empty[Boolean],
     scalarMappings = Seq.empty[(String, String)],
-    imports = Seq.empty[String]
+    imports = Seq.empty[String],
+    splitFiles = Option.empty[Boolean],
+    enableFmt = Option.empty[Boolean]
   )
 }
diff --git a/codegen-sbt/src/main/scala/caliban/codegen/CalibanSourceGenerator.scala b/codegen-sbt/src/main/scala/caliban/codegen/CalibanSourceGenerator.scala
index 4fda59dcb..94b5d58b5 100644
--- a/codegen-sbt/src/main/scala/caliban/codegen/CalibanSourceGenerator.scala
+++ b/codegen-sbt/src/main/scala/caliban/codegen/CalibanSourceGenerator.scala
@@ -84,8 +84,15 @@ object CalibanSourceGenerator {
     ) // NB: Presuming zio-config can read toString'd booleans
     val scalarMappings = pairList("--scalarMappings", settings.scalarMappings)
     val imports        = list("--imports", settings.imports)
-
-    scalafmtPath ++ headers ++ packageName ++ genView ++ scalarMappings ++ imports
+    val splitFiles     = singleOpt(
+      "--splitFiles",
+      settings.splitFiles.map(_.toString())
+    ) // NB: Presuming zio-config can read toString'd booleans
+    val enableFmt = singleOpt(
+      "--enableFmt",
+      settings.enableFmt.map(_.toString())
+    ) // NB: Presuming zio-config can read toString'd booleans
+    scalafmtPath ++ headers ++ packageName ++ genView ++ scalarMappings ++ imports ++ splitFiles ++ enableFmt
   }
 
   def apply(
diff --git a/codegen-sbt/src/sbt-test/codegen/test-compile/test b/codegen-sbt/src/sbt-test/codegen/test-compile/test
index ab7c44bcb..98fd901c6 100644
--- a/codegen-sbt/src/sbt-test/codegen/test-compile/test
+++ b/codegen-sbt/src/sbt-test/codegen/test-compile/test
@@ -25,9 +25,11 @@ $ mkdir src/main/scala/genview/client
 > calibanGenClient project/schema-to-check-name-uniqueness.graphql src/main/scala/genview/client/ClientNameUniqueness.scala --packageName genview.client --genView true
 $ exists src/main/scala/genview/client/ClientNameUniqueness.scala
 $ exec sh verify.sh StarshipView ./src/main/scala/genview/client/ClientNameUniqueness.scala
-> calibanGenClient project/gitlab-schema.graphql src/main/scala/genview/client/ClientGitLab.scala --packageName genview.client --genView true
-$ exists src/main/scala/genview/client/ClientGitLab.scala
-$ exec sh verify.sh VulnerableProjectsByGradeView ./src/main/scala/genview/client/ClientGitLab.scala
+> calibanGenClient project/gitlab-schema.graphql src/main/scala/genview/client/ClientGitLab.scala --packageName genview.client --genView true --splitFiles true --enableFmt false
+$ exists src/main/scala/genview/client/package.scala
+$ exec sh verify.sh VulnerableProjectsByGrade ./src/main/scala/genview/client/package.scala
+$ exists src/main/scala/genview/client/VulnerableProjectsByGrade.scala
+$ exec sh verify.sh VulnerableProjectsByGradeView ./src/main/scala/genview/client/VulnerableProjectsByGrade.scala
 > compile
 
 $ exists target/scala-2.12/src_managed/main/caliban-codegen-sbt/caliban/Client.scala
diff --git a/docs/docs/client.html b/docs/docs/client.html
index 5660e07fe..b45d5700c 100644
--- a/docs/docs/client.html
+++ b/docs/docs/client.html
@@ -64,10 +64,12 @@
   def genView(value: Boolean): CalibanSettings                  // Provide a case class and helper method to select all fields on an object (default: false)
   def scalarMapping(mapping: (String,String)*): CalibanSettings // A mapping from GraphQL scalar types to JVM types, as unknown scalar types are represented as String by default.
   def imports(values: String*): CalibanSettings                 // A list of imports to be added to the top of a generated client
+  def splitFiles(value: Boolean): CalibanSettings               // Split single client object into multiple files (default: false)
+  def enableFmt(value: Boolean): CalibanSettings                // Enable code formatting with scalafmt (default: true)
 
   // Only defined for `url` settings, for use in supplying extra headers when fetching the schema itself
   def headers(pairs: (String,String)*): CalibanSettings
-</code></pre></div><h3 id="calibangenclient"><a href="#calibangenclient" class="header-anchor">#</a> <code>calibanGenClient</code></h3> <p>If you prefer to generate the client explicitly rather than automatically, you can use <code>calibanGenClient</code> on the SBT CLI as follows:</p> <div class="language-scala extra-class"><pre class="language-scala"><code>calibanGenClient schemaPath outputPath <span class="token punctuation">[</span><span class="token operator">--</span>scalafmtPath path<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>headers name<span class="token operator">:</span>value<span class="token punctuation">,</span>name2<span class="token operator">:</span>value2<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>genView <span class="token boolean">true</span><span class="token operator">|</span><span class="token boolean">false</span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>scalarMappings gqlType<span class="token operator">:</span>f<span class="token punctuation">.</span>q<span class="token punctuation">.</span>d<span class="token punctuation">.</span>n<span class="token punctuation">.</span>Type<span class="token punctuation">,</span>gqlType2<span class="token operator">:</span>f<span class="token punctuation">.</span>q<span class="token punctuation">.</span>d<span class="token punctuation">.</span>n<span class="token punctuation">.</span>Type2<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>imports a<span class="token punctuation">.</span>b<span class="token punctuation">.</span>c<span class="token punctuation">.</span>_<span class="token punctuation">,</span>c<span class="token punctuation">.</span>d<span class="token punctuation">.</span>E<span class="token punctuation">]</span>
+</code></pre></div><h3 id="calibangenclient"><a href="#calibangenclient" class="header-anchor">#</a> <code>calibanGenClient</code></h3> <p>If you prefer to generate the client explicitly rather than automatically, you can use <code>calibanGenClient</code> on the SBT CLI as follows:</p> <div class="language-scala extra-class"><pre class="language-scala"><code>calibanGenClient schemaPath outputPath <span class="token punctuation">[</span><span class="token operator">--</span>scalafmtPath path<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>headers name<span class="token operator">:</span>value<span class="token punctuation">,</span>name2<span class="token operator">:</span>value2<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>genView <span class="token boolean">true</span><span class="token operator">|</span><span class="token boolean">false</span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>scalarMappings gqlType<span class="token operator">:</span>f<span class="token punctuation">.</span>q<span class="token punctuation">.</span>d<span class="token punctuation">.</span>n<span class="token punctuation">.</span>Type<span class="token punctuation">,</span>gqlType2<span class="token operator">:</span>f<span class="token punctuation">.</span>q<span class="token punctuation">.</span>d<span class="token punctuation">.</span>n<span class="token punctuation">.</span>Type2<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>imports a<span class="token punctuation">.</span>b<span class="token punctuation">.</span>c<span class="token punctuation">.</span>_<span class="token punctuation">,</span>c<span class="token punctuation">.</span>d<span class="token punctuation">.</span>E<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>splitFiles <span class="token boolean">true</span><span class="token operator">|</span><span class="token boolean">false</span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>enableFmt <span class="token boolean">true</span><span class="token operator">|</span><span class="token boolean">false</span><span class="token punctuation">]</span>
 
 calibanGenClient project<span class="token operator">/</span>schema<span class="token punctuation">.</span>graphql src<span class="token operator">/</span>main<span class="token operator">/</span>client<span class="token operator">/</span>Client<span class="token punctuation">.</span>scala <span class="token operator">--</span>genView <span class="token boolean">true</span>  
 </code></pre></div><p>This command will generate a Scala file in <code>outputPath</code> containing helper functions for all the types defined in the provided GraphQL schema defined at <code>schemaPath</code>.
@@ -80,7 +82,11 @@
 option.
 Provide <code>--genView true</code> option if you want to generate a view for the GraphQL types.
 If you want to force a mapping between a GraphQL type and a Scala class (such as scalars), you can use the
-<code>--scalarMappings</code> option. Also you can add imports for example for your ArgEncoder implicits by providing <code>--imports</code> option.</p> <h2 id="query-building"><a href="#query-building" class="header-anchor">#</a> Query building</h2> <p>Once the boilerplate code is generated, you can start building queries. For each <em>type</em> in your schema, a corresponding Scala object has been created. For each <em>field</em> in your schema, a corresponding Scala function has been created.</p> <p>For example, given the following schema:</p> <div class="language-graphql extra-class"><pre class="language-graphql"><code><span class="token keyword">type</span> <span class="token class-name">Character</span> <span class="token punctuation">{</span>
+<code>--scalarMappings</code> option. Also you can add imports for example for your ArgEncoder implicits by providing <code>--imports</code> option.
+Use the <code>--splitFiles true</code> option if you want to generate multiple files within the same package instead of a single file.
+In this case the filename part of the <code>outputPath</code> will be ignored, but the value will still be used to determine the mandatory package name and destination directory.
+This can be helpful with large schemas and incremental compilation.
+Provide <code>--enableFmt</code> option if you don't need to format generated files.</p> <h2 id="query-building"><a href="#query-building" class="header-anchor">#</a> Query building</h2> <p>Once the boilerplate code is generated, you can start building queries. For each <em>type</em> in your schema, a corresponding Scala object has been created. For each <em>field</em> in your schema, a corresponding Scala function has been created.</p> <p>For example, given the following schema:</p> <div class="language-graphql extra-class"><pre class="language-graphql"><code><span class="token keyword">type</span> <span class="token class-name">Character</span> <span class="token punctuation">{</span>
   <span class="token attr-name">name</span><span class="token punctuation">:</span> <span class="token scalar">String</span><span class="token operator">!</span>
   <span class="token attr-name">nicknames</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token scalar">String</span><span class="token operator">!</span><span class="token punctuation">]</span><span class="token operator">!</span>
   <span class="token attr-name">origin</span><span class="token punctuation">:</span> <span class="token class-name">Origin</span><span class="token operator">!</span>
diff --git a/tools/src/main/scala/caliban/tools/ClientWriter.scala b/tools/src/main/scala/caliban/tools/ClientWriter.scala
index f19774617..621518240 100644
--- a/tools/src/main/scala/caliban/tools/ClientWriter.scala
+++ b/tools/src/main/scala/caliban/tools/ClientWriter.scala
@@ -19,8 +19,11 @@ object ClientWriter {
     objectName: String = "Client",
     packageName: Option[String] = None,
     genView: Boolean = false,
-    additionalImports: Option[List[String]] = None
-  )(implicit scalarMappings: ScalarMappings): String = {
+    additionalImports: Option[List[String]] = None,
+    splitFiles: Boolean = false
+  )(implicit scalarMappings: ScalarMappings): List[(String, String)] = {
+    require(packageName.isDefined || !splitFiles, "splitFiles option requires a package name")
+
     val schemaDef = schema.schemaDefinition
 
     implicit val mappingClashedTypeNames: MappingClashedTypeNames = MappingClashedTypeNames(
@@ -32,7 +35,8 @@ object ClientWriter {
           case UnionTypeDefinition(_, name, _, _)       => name
           case ScalarTypeDefinition(_, name, _)         => name
           case InterfaceTypeDefinition(_, name, _, _)   => name
-        }
+        },
+        if (splitFiles) List("package") else Nil
       )
     )
 
@@ -47,6 +51,18 @@ object ClientWriter {
       safeTypeName(name) -> op
     }.toMap)
 
+    val objectTypes =
+      if (splitFiles)
+        schema.objectTypeDefinitions
+          .filterNot(obj =>
+            reservedType(obj) ||
+              schemaDef.exists(_.query.getOrElse("Query") == obj.name) ||
+              schemaDef.exists(_.mutation.getOrElse("Mutation") == obj.name) ||
+              schemaDef.exists(_.subscription.getOrElse("Subscription") == obj.name)
+          )
+          .map(writeObjectType)
+      else Nil
+
     val objects = schema.objectTypeDefinitions
       .filterNot(obj =>
         reservedType(obj) ||
@@ -54,74 +70,192 @@ object ClientWriter {
           schemaDef.exists(_.mutation.getOrElse("Mutation") == obj.name) ||
           schemaDef.exists(_.subscription.getOrElse("Subscription") == obj.name)
       )
-      .map(writeObject(_, genView))
-      .mkString("\n")
+      .map { typedef =>
+        val content     = writeObject(typedef, genView)
+        val fullContent =
+          if (splitFiles)
+            s"""import caliban.client.FieldBuilder._
+               |import caliban.client._
+               |
+               |$content
+               |""".stripMargin
+          else
+            s"""${writeObjectType(typedef)}
+               |$content
+               |""".stripMargin
+        safeTypeName(typedef.name) -> fullContent
+      }
 
-    val inputs = schema.inputObjectTypeDefinitions.map(writeInputObject).mkString("\n")
+    val inputs = schema.inputObjectTypeDefinitions.map { typedef =>
+      val content     = writeInputObject(typedef)
+      val fullContent =
+        if (splitFiles)
+          s"""import caliban.client._
+             |import caliban.client.__Value._
+             |
+             |$content
+             |""".stripMargin
+        else
+          content
+      safeTypeName(typedef.name) -> fullContent
+    }
 
     val enums = schema.enumTypeDefinitions
       .filter(e => !scalarMappings.scalarMap.exists(_.contains(e.name)))
-      .map(writeEnum)
-      .mkString("\n")
+      .map { typedef =>
+        val content     = writeEnum(typedef)
+        val fullContent =
+          if (splitFiles)
+            s"""import caliban.client.CalibanClientError.DecodingError
+               |import caliban.client._
+               |import caliban.client.__Value._
+               |
+               |$content
+               |""".stripMargin
+          else
+            content
+        safeTypeName(typedef.name) -> fullContent
+      }
 
     val scalars = schema.scalarTypeDefinitions
       .filterNot(s => isScalarSupported(s.name))
       .map(writeScalar)
-      .mkString("\n")
+
+    val queryTypes =
+      if (splitFiles)
+        schema
+          .objectTypeDefinition(schemaDef.flatMap(_.query).getOrElse("Query"))
+          .map(writeRootQueryType)
+          .toList
+      else Nil
 
     val queries = schema
       .objectTypeDefinition(schemaDef.flatMap(_.query).getOrElse("Query"))
-      .map(writeRootQuery)
-      .getOrElse("")
+      .map { typedef =>
+        val content     = writeRootQuery(typedef)
+        val fullContent =
+          if (splitFiles)
+            s"""import caliban.client.FieldBuilder._
+               |import caliban.client._
+               |
+               |$content
+               |""".stripMargin
+          else
+            s"""${writeRootQueryType(typedef)}
+               |$content
+               |""".stripMargin
+        safeTypeName(typedef.name) -> fullContent
+      }
+
+    val mutationTypes =
+      if (splitFiles)
+        schema
+          .objectTypeDefinition(schemaDef.flatMap(_.mutation).getOrElse("Mutation"))
+          .map(writeRootMutationType)
+          .toList
+      else Nil
 
     val mutations = schema
       .objectTypeDefinition(schemaDef.flatMap(_.mutation).getOrElse("Mutation"))
-      .map(writeRootMutation)
-      .getOrElse("")
+      .map { typedef =>
+        val content     = writeRootMutation(typedef)
+        val fullContent =
+          if (splitFiles)
+            s"""import caliban.client.FieldBuilder._
+               |import caliban.client._
+               |
+               |$content
+               |""".stripMargin
+          else
+            s"""${writeRootMutationType(typedef)}
+               |$content
+               |""".stripMargin
+        safeTypeName(typedef.name) -> fullContent
+      }
+
+    val subscriptionTypes =
+      if (splitFiles)
+        schema
+          .objectTypeDefinition(schemaDef.flatMap(_.subscription).getOrElse("Subscription"))
+          .map(writeRootSubscriptionType)
+          .toList
+      else Nil
 
     val subscriptions = schema
       .objectTypeDefinition(schemaDef.flatMap(_.subscription).getOrElse("Subscription"))
-      .map(writeRootSubscription)
-      .getOrElse("")
-
-    val additionalImportsString = additionalImports.fold("")(_.map(i => s"import $i").mkString("\n"))
-
-    val imports =
-      s"""${if (enums.nonEmpty)
-        """import caliban.client.CalibanClientError.DecodingError
-          |""".stripMargin
-      else ""}${if (objects.nonEmpty || queries.nonEmpty || mutations.nonEmpty || subscriptions.nonEmpty)
-        """import caliban.client.FieldBuilder._
-          |""".stripMargin
-      else
-        ""}${if (
-        enums.nonEmpty || objects.nonEmpty || queries.nonEmpty || mutations.nonEmpty || subscriptions.nonEmpty || inputs.nonEmpty
-      )
-        """import caliban.client._
-          |""".stripMargin
-      else ""}${if (enums.nonEmpty || inputs.nonEmpty)
-        """import caliban.client.__Value._
-          |""".stripMargin
-      else ""}"""
-
-    s"""${packageName.fold("")(p => s"package $p\n\n")}$imports\n
-       |$additionalImportsString
-       |
-       |object $objectName {
-       |
-       |  $scalars
-       |  $enums
-       |  $objects
-       |  $inputs
-       |  $queries
-       |  $mutations
-       |  $subscriptions
-       |  
-       |}""".stripMargin
+      .map { typedef =>
+        val content     = writeRootSubscription(typedef)
+        val fullContent =
+          if (splitFiles)
+            s"""import caliban.client.FieldBuilder._
+               |import caliban.client._
+               |
+               |$content
+               |""".stripMargin
+          else
+            s"""${writeRootSubscriptionType(typedef)}
+               |$content
+               |""".stripMargin
+        safeTypeName(typedef.name) -> fullContent
+      }
+
+    if (splitFiles) {
+      val parentPackageName = packageName.filter(_.contains(".")).map(_.reverse.dropWhile(_ != '.').drop(1).reverse)
+      val packageObject = "package" ->
+        s"""${parentPackageName.fold("")(p => s"package $p\n")}
+           |package object ${packageName.get.reverse.takeWhile(_ != '.').reverse} {
+           |  ${(scalars ::: objectTypes ::: queryTypes ::: mutationTypes ::: subscriptionTypes).mkString("\n")}
+           |}
+           |""".stripMargin
+      val classFiles    =
+        (enums ::: objects ::: inputs ::: queries.toList ::: mutations.toList ::: subscriptions.toList).map {
+          case (name, content) =>
+            val fullContent =
+              s"""${packageName.fold("")(p => s"package $p\n\n")}$content\n
+                 |""".stripMargin
+            name -> fullContent
+        }
+      packageObject :: classFiles
+    } else {
+      val additionalImportsString = additionalImports.fold("")(_.map(i => s"import $i").mkString("\n"))
+
+      val imports =
+        s"""${if (enums.nonEmpty)
+          """import caliban.client.CalibanClientError.DecodingError
+            |""".stripMargin
+        else ""}${if (objects.nonEmpty || queries.nonEmpty || mutations.nonEmpty || subscriptions.nonEmpty)
+          """import caliban.client.FieldBuilder._
+            |""".stripMargin
+        else
+          ""}${if (
+          enums.nonEmpty || objects.nonEmpty || queries.nonEmpty || mutations.nonEmpty || subscriptions.nonEmpty || inputs.nonEmpty
+        )
+          """import caliban.client._
+            |""".stripMargin
+        else ""}${if (enums.nonEmpty || inputs.nonEmpty)
+          """import caliban.client.__Value._
+            |""".stripMargin
+        else ""}"""
+
+      List(objectName -> s"""${packageName.fold("")(p => s"package $p\n\n")}$imports\n
+                            |$additionalImportsString
+                            |
+                            |object $objectName {
+                            |
+                            |  ${scalars.mkString("\n")}
+                            |  ${enums.map(_._2).mkString("\n")}
+                            |  ${objects.map(_._2).mkString("\n")}
+                            |  ${inputs.map(_._2).mkString("\n")}
+                            |  ${queries.map(_._2).mkString("\n")}
+                            |  ${mutations.map(_._2).mkString("\n")}
+                            |  ${subscriptions.map(_._2).mkString("\n")}
+                            |
+                            |}""".stripMargin)
+    }
   }
 
-  private def getMappingsClashedNames(typeNames: List[String]): Map[String, String] =
-    typeNames
+  private def getMappingsClashedNames(typeNames: List[String], reservedNames: List[String] = Nil): Map[String, String] =
+    (reservedNames ::: typeNames)
       .map(name => name.toLowerCase -> name)
       .groupBy(_._1)
       .collect {
@@ -145,6 +279,11 @@ object ClientWriter {
   def reservedType(typeDefinition: ObjectTypeDefinition): Boolean =
     typeDefinition.name == "Query" || typeDefinition.name == "Mutation" || typeDefinition.name == "Subscription"
 
+  def writeRootQueryType(
+    typedef: ObjectTypeDefinition
+  ): String =
+    s"type ${typedef.name} = _root_.caliban.client.Operations.RootQuery"
+
   def writeRootQuery(
     typedef: ObjectTypeDefinition
   )(implicit
@@ -152,12 +291,16 @@ object ClientWriter {
     mappingClashedTypeNames: MappingClashedTypeNames,
     scalarMappings: ScalarMappings
   ): String =
-    s"""type ${typedef.name} = _root_.caliban.client.Operations.RootQuery
-       |object ${typedef.name} {
+    s"""object ${typedef.name} {
        |  ${typedef.fields.map(writeField(_, "_root_.caliban.client.Operations.RootQuery")).mkString("\n  ")}
        |}
        |""".stripMargin
 
+  def writeRootMutationType(
+    typedef: ObjectTypeDefinition
+  ): String =
+    s"type ${typedef.name} = _root_.caliban.client.Operations.RootMutation"
+
   def writeRootMutation(
     typedef: ObjectTypeDefinition
   )(implicit
@@ -165,12 +308,16 @@ object ClientWriter {
     mappingClashedTypeNames: MappingClashedTypeNames,
     scalarMappings: ScalarMappings
   ): String =
-    s"""type ${typedef.name} = _root_.caliban.client.Operations.RootMutation
-       |object ${typedef.name} {
+    s"""object ${typedef.name} {
        |  ${typedef.fields.map(writeField(_, "_root_.caliban.client.Operations.RootMutation")).mkString("\n  ")}
        |}
        |""".stripMargin
 
+  def writeRootSubscriptionType(
+    typedef: ObjectTypeDefinition
+  ): String =
+    s"type ${typedef.name} = _root_.caliban.client.Operations.RootSubscription"
+
   def writeRootSubscription(
     typedef: ObjectTypeDefinition
   )(implicit
@@ -178,12 +325,23 @@ object ClientWriter {
     mappingClashedTypeNames: MappingClashedTypeNames,
     scalarMappings: ScalarMappings
   ): String =
-    s"""type ${typedef.name} = _root_.caliban.client.Operations.RootSubscription
-       |object ${typedef.name} {
+    s"""object ${typedef.name} {
        |  ${typedef.fields.map(writeField(_, "_root_.caliban.client.Operations.RootSubscription")).mkString("\n  ")}
        |}
        |""".stripMargin
 
+  def writeObjectType(
+    typedef: ObjectTypeDefinition
+  )(implicit
+    mappingClashedTypeNames: MappingClashedTypeNames,
+    scalarMappings: ScalarMappings
+  ): String = {
+
+    val objectName: String = safeTypeName(typedef.name)
+
+    s"type $objectName"
+  }
+
   def writeObject(
     typedef: ObjectTypeDefinition,
     genView: Boolean
@@ -200,8 +358,7 @@ object ClientWriter {
         "\n  " + writeView(typedef.name, fields.map(_.typeInfo))
       else ""
 
-    s"""type $objectName
-       |object $objectName {$view
+    s"""object $objectName {$view
        |  ${fields.map(writeFieldInfo).mkString("\n  ")}
        |}
        |""".stripMargin
diff --git a/tools/src/main/scala/caliban/tools/Codegen.scala b/tools/src/main/scala/caliban/tools/Codegen.scala
index b05fb0609..b38734e47 100644
--- a/tools/src/main/scala/caliban/tools/Codegen.scala
+++ b/tools/src/main/scala/caliban/tools/Codegen.scala
@@ -28,22 +28,31 @@ object Codegen {
     }
     val genView            = arguments.genView.getOrElse(false)
     val scalarMappings     = arguments.scalarMappings
+    val splitFiles         = arguments.splitFiles.getOrElse(false)
+    val enableFmt          = arguments.enableFmt.getOrElse(true)
     val loader             = getSchemaLoader(arguments.schemaPath, arguments.headers)
     for {
       schema    <- loader.load
       code       = genType match {
                      case GenType.Schema =>
-                       SchemaWriter.write(schema, packageName, effect, arguments.imports, abstractEffectType)(
-                         ScalarMappings(scalarMappings)
+                       List(
+                         objectName -> SchemaWriter.write(schema, packageName, effect, arguments.imports, abstractEffectType)(
+                           ScalarMappings(scalarMappings)
+                         )
                        )
                      case GenType.Client =>
-                       ClientWriter.write(schema, objectName, packageName, genView, arguments.imports)(
+                       ClientWriter.write(schema, objectName, packageName, genView, arguments.imports, splitFiles)(
                          ScalarMappings(scalarMappings)
                        )
                    }
-      formatted <- Formatter.format(code, arguments.fmtPath)
-      _         <- Task(new PrintWriter(new File(arguments.toPath)))
-                     .bracket(q => UIO(q.close()), pw => Task(pw.println(formatted)))
+      formatted <- if (enableFmt) Formatter.format(code, arguments.fmtPath) else Task.succeed(code)
+      _         <- Task.collectAll(formatted.map { case (objectName, objectCode) =>
+                     val path =
+                       if (splitFiles) s"${arguments.toPath.reverse.dropWhile(_ != '/').reverse}$objectName.scala"
+                       else arguments.toPath
+                     Task(new PrintWriter(new File(path)))
+                       .bracket(q => UIO(q.close()), pw => Task(pw.println(objectCode)))
+                   })
     } yield ()
   }
 
diff --git a/tools/src/main/scala/caliban/tools/Formatter.scala b/tools/src/main/scala/caliban/tools/Formatter.scala
index 50f4c0d5c..b8bbecc20 100644
--- a/tools/src/main/scala/caliban/tools/Formatter.scala
+++ b/tools/src/main/scala/caliban/tools/Formatter.scala
@@ -7,15 +7,20 @@ import zio.Task
 
 object Formatter {
 
-  def format(str: String, fmtPath: Option[String]): Task[String] = Task {
+  def format(str: String, fmtPath: Option[String]): Task[String]                                  =
+    format(List("Nil.scala" -> str), fmtPath).map(_.head._2)
+
+  def format(strs: List[(String, String)], fmtPath: Option[String]): Task[List[(String, String)]] = Task {
     val scalafmt          = Scalafmt.create(this.getClass.getClassLoader)
     val defaultConfigPath = Paths.get(".scalafmt.conf")
     val defaultConfig     =
       if (Files.exists(defaultConfigPath)) defaultConfigPath else Paths.get("")
     val config            = fmtPath.fold(defaultConfig)(Paths.get(_))
-    val result            = scalafmt
-      .withRespectVersion(false)
-      .format(config, Paths.get("Nil.scala"), str)
+    val result            = strs.map { case (name, code) =>
+      name -> scalafmt
+        .withRespectVersion(false)
+        .format(config, Paths.get(s"$name.scala"), code)
+    }
     scalafmt.clear()
     result
   }
diff --git a/tools/src/main/scala/caliban/tools/Options.scala b/tools/src/main/scala/caliban/tools/Options.scala
index 5d6aca1b3..27fb19eae 100644
--- a/tools/src/main/scala/caliban/tools/Options.scala
+++ b/tools/src/main/scala/caliban/tools/Options.scala
@@ -13,7 +13,9 @@ final case class Options(
   effect: Option[String],
   scalarMappings: Option[Map[String, String]],
   imports: Option[List[String]],
-  abstractEffectType: Option[Boolean]
+  abstractEffectType: Option[Boolean],
+  splitFiles: Option[Boolean],
+  enableFmt: Option[Boolean]
 )
 
 object Options {
@@ -26,7 +28,9 @@ object Options {
     effect: Option[String],
     scalarMappings: Option[List[String]],
     imports: Option[List[String]],
-    abstractEffectType: Option[Boolean]
+    abstractEffectType: Option[Boolean],
+    splitFiles: Option[Boolean],
+    enableFmt: Option[Boolean]
   )
 
   def fromArgs(args: List[String]): Option[Options] =
@@ -65,7 +69,9 @@ object Options {
               }.toMap
             },
             rawOpts.imports,
-            rawOpts.abstractEffectType
+            rawOpts.abstractEffectType,
+            rawOpts.splitFiles,
+            rawOpts.enableFmt
           )
         }
       case _                             => None
diff --git a/tools/src/test/scala/caliban/tools/ClientWriterSpec.scala b/tools/src/test/scala/caliban/tools/ClientWriterSpec.scala
index 8641ce0f4..3528b7ce6 100644
--- a/tools/src/test/scala/caliban/tools/ClientWriterSpec.scala
+++ b/tools/src/test/scala/caliban/tools/ClientWriterSpec.scala
@@ -17,7 +17,24 @@ object ClientWriterSpec extends DefaultRunnableSpec {
     .parseQuery(schema)
     .flatMap(doc =>
       Formatter.format(
-        ClientWriter.write(doc, additionalImports = Some(additionalImports))(
+        ClientWriter
+          .write(doc, additionalImports = Some(additionalImports))(
+            ScalarMappings(Some(scalarMappings))
+          )
+          .head
+          ._2,
+        None
+      )
+    )
+
+  def genSplit(
+    schema: String,
+    scalarMappings: Map[String, String] = Map.empty
+  ): Task[List[(String, String)]] = Parser
+    .parseQuery(schema)
+    .flatMap(doc =>
+      Formatter.format(
+        ClientWriter.write(doc, packageName = Some("test"), splitFiles = true)(
           ScalarMappings(Some(scalarMappings))
         ),
         None
@@ -711,6 +728,58 @@ object Client {
 """
           )
         }
+      },
+      testM("schema with splitFiles") {
+        val schema =
+          """
+             schema {
+               query: Q
+             }
+
+             type Q {
+               characters: [Character!]!
+             }
+
+             type Character {
+               name: String!
+               nicknames: [String!]!
+             }
+            """.stripMargin
+
+        assertM(genSplit(schema))(
+          equalTo(
+            List(
+              "package"   -> """package object test {
+                             |  type Character
+                             |  type Q = _root_.caliban.client.Operations.RootQuery
+                             |}
+                             |""".stripMargin,
+              "Character" -> """package test
+                               |
+                               |import caliban.client.FieldBuilder._
+                               |import caliban.client._
+                               |
+                               |object Character {
+                               |  def name: SelectionBuilder[Character, String]            = _root_.caliban.client.SelectionBuilder.Field("name", Scalar())
+                               |  def nicknames: SelectionBuilder[Character, List[String]] =
+                               |    _root_.caliban.client.SelectionBuilder.Field("nicknames", ListOf(Scalar()))
+                               |}
+                               |""".stripMargin,
+              "Q"         -> """package test
+                       |
+                       |import caliban.client.FieldBuilder._
+                       |import caliban.client._
+                       |
+                       |object Q {
+                       |  def characters[A](
+                       |    innerSelection: SelectionBuilder[Character, A]
+                       |  ): SelectionBuilder[_root_.caliban.client.Operations.RootQuery, List[A]] =
+                       |    _root_.caliban.client.SelectionBuilder.Field("characters", ListOf(Obj(innerSelection)))
+                       |}
+                       |""".stripMargin
+            )
+          )
+        )
       }
     ) @@ TestAspect.sequential
 }
diff --git a/tools/src/test/scala/caliban/tools/ClientWriterViewSpec.scala b/tools/src/test/scala/caliban/tools/ClientWriterViewSpec.scala
index 5c44e3636..9d809c466 100644
--- a/tools/src/test/scala/caliban/tools/ClientWriterViewSpec.scala
+++ b/tools/src/test/scala/caliban/tools/ClientWriterViewSpec.scala
@@ -12,7 +12,7 @@ object ClientWriterViewSpec extends DefaultRunnableSpec {
   val gen: String => Task[String] = (schema: String) =>
     Parser
       .parseQuery(schema)
-      .flatMap(doc => Formatter.format(ClientWriter.write(doc, genView = true)(ScalarMappings(None)), None))
+      .flatMap(doc => Formatter.format(ClientWriter.write(doc, genView = true)(ScalarMappings(None)).head._2, None))
 
   override def spec: ZSpec[TestEnvironment, Any] =
     suite("ClientWriterViewSpec")(
diff --git a/tools/src/test/scala/caliban/tools/OptionsSpec.scala b/tools/src/test/scala/caliban/tools/OptionsSpec.scala
index 5dbbf5305..7e1211801 100644
--- a/tools/src/test/scala/caliban/tools/OptionsSpec.scala
+++ b/tools/src/test/scala/caliban/tools/OptionsSpec.scala
@@ -24,6 +24,8 @@ object OptionsSpec extends DefaultRunnableSpec {
                 None,
                 None,
                 None,
+                None,
+                None,
                 None
               )
             )
@@ -46,6 +48,8 @@ object OptionsSpec extends DefaultRunnableSpec {
                 None,
                 None,
                 None,
+                None,
+                None,
                 None
               )
             )
@@ -58,7 +62,7 @@ object OptionsSpec extends DefaultRunnableSpec {
         assert(result)(
           equalTo(
             Some(
-              Options("schema", "output", None, None, None, None, None, None, None, None)
+              Options("schema", "output", None, None, None, None, None, None, None, None, None, None)
             )
           )
         )
@@ -93,6 +97,8 @@ object OptionsSpec extends DefaultRunnableSpec {
                 None,
                 None,
                 None,
+                None,
+                None,
                 None
               )
             )
@@ -115,6 +121,8 @@ object OptionsSpec extends DefaultRunnableSpec {
                 Some("cats.effect.IO"),
                 None,
                 None,
+                None,
+                None,
                 None
               )
             )
@@ -137,6 +145,8 @@ object OptionsSpec extends DefaultRunnableSpec {
                 None,
                 None,
                 None,
+                None,
+                None,
                 None
               )
             )
@@ -159,6 +169,8 @@ object OptionsSpec extends DefaultRunnableSpec {
                 None,
                 Some(Map("Long" -> "scala.Long")),
                 None,
+                None,
+                None,
                 None
               )
             )
@@ -181,6 +193,8 @@ object OptionsSpec extends DefaultRunnableSpec {
                 None,
                 None,
                 Some(List("a.b.Clazz", "b.c._")),
+                None,
+                None,
                 None
               )
             )
@@ -203,7 +217,9 @@ object OptionsSpec extends DefaultRunnableSpec {
                 Some("F"),
                 None,
                 None,
-                Some(true)
+                Some(true),
+                None,
+                None
               )
             )
           )
@@ -225,6 +241,8 @@ object OptionsSpec extends DefaultRunnableSpec {
                 None,
                 None,
                 None,
+                None,
+                None,
                 None
               )
             )