Skip to content
  • Beginner

Generating code from a template

Add .template { } to a layout declaration and that file can be generated as code.

"UseCase" {
layout {
"core/domain" / mainSourceSet / kotlin / "com/example/core/domain/useCase" /
"${capture("name")}UseCase".ktFile()
.template {
val name = captureValue("name")
"""
package com.example.core.domain.useCase
interface ${name}UseCase {
suspend operator fun invoke()
}
""".trimIndent()
}
}
}
"UseCaseImplementation" {
layout {
"core/domain" / mainSourceSet / kotlin / "com/example/core/domain/useCase" /
"${capture("name")}UseCaseImpl".ktFile()
.template {
val name = captureValue("name")
val implBody by stringParameter(default = """TODO("Implement ${name}UseCase")""")
"""
package com.example.core.domain.useCase
class ${name}UseCaseImpl : ${name}UseCase {
override suspend fun invoke() {
$implBody
}
}
""".trimIndent()
}
}
}
Terminal window
# generate both UseCase and UseCaseImplementation at once
./gradlew katachiTemplate \
--arg "template=UseCase,UseCaseImplementation" --arg "name=GetItemList"

--arg template=<specifier> says which templates to generate.

A template is specified by the qualified name of the Role that has it (group.Role). To generate several templates at once, separate them with ,.

What you write What it means
Controller The template of Role Controller, which is in no group
feature.Screen The template of Role Screen, in group feature
RepositoryInterface A specifier with no group. Usable when, among the Roles that have a template, only one is named RepositoryInterface

When no template matches what you wrote, the run fails, listing the specifiers you can write.

Declare the parameters a generation run can pass with --arg, inside the template’s block.

"${capture("name")}UseCaseImpl".ktFile()
.template {
val name = captureValue("name")
val implBody by stringParameter(default = """TODO("Implement ${name}UseCase")""")
// ...
}

The property’s name is the parameter’s name, as is. val implBody by stringParameter(default = ...) declares the parameter passed as --arg implBody=....

Terminal window
./gradlew katachiTemplate \
--arg "template=UseCaseImplementation" \
--arg "name=GetItemList" --arg 'implBody=println("Hello World")'

There are four parameter types. All of them are passed as --arg name=value.

How to write it How to pass it What it accepts
val name by stringParameter() --arg name=User Anything
val withImpl by booleanParameter(default = true) --arg withImpl=false true / false (lower case only)
val pageSize by intParameter(default = 20) --arg pageSize=50 An integer
val visibility by enumParameter(default = Visibility.Public) --arg visibility=Internal An enum entry name

A capture and a parameter are each one, by name, across every template a single run generates. If two templates share one capture("name"), one --arg name=User fills in both. When two templates declare a parameter of the same name, give it the same type and the same default.

When the destination has a wildcard, name it and pass a value

Section titled “When the destination has a wildcard, name it and pass a value”

capture() gives a * a name. It doesn’t declare anything by itself — it just returns a string with the name embedded in it — so you can write it right inside a Kotlin string template.

layout {
"feature" / capture("feature") / "src" / "main" / "kotlin" / "*ViewModel".ktFile()
}

As far as checking goes, it’s exactly the same as *. Rewriting capture("feature") as "*" doesn’t change which files are allowed, not even one.

The name is used when passing a value with --arg. Left as *, a template can’t tell whether to generate into feature/home or feature/settings. Name it, and you can choose with --arg feature=home. For a Role with no template, * on its own is fine.

.template { } is attached to the file declaration itself, so the destination path is fixed to that one layout { } declaration. If a * or ** with no name from capture() is still left in it, there is nowhere for a value to arrive, so it is an error.

"FeatureComponent" {
layout {
"feature" / capture("feature") / "commonMain".sourceSet / kotlin /
"com/example/kmp/feature" / capture("featurePackage") / "component" /
"${capture("name")}".ktFile()
.template {
val featurePackage = captureValue("featurePackage")
val name = captureValue("name")
"""
package com.example.kmp.feature.$featurePackage.component
@Composable
internal fun $name(title: String) {
Text(text = title)
}
""".trimIndent()
}
}
}
Terminal window
./gradlew katachiTemplate \
--arg "template=FeatureComponent" --arg "feature=home" --arg "featurePackage=home" --arg "name=HomeUserCard"

--arg feature=home decides the * of the module’s directory and --arg featurePackage=home decides the * of the package’s directory, both as home, so HomeUserCard.kt lands in component/ under feature/home/. The same capture name can’t be used twice in one path, so give the module’s directory and the package’s directory different names.

A capture’s name becomes the --arg argument name, as is. Inside the template, read the same value with captureValue("featurePackage"), for example.

.template { } can be attached to a file declared with .file(), not only to .ktFile(). The string the block returns becomes the file’s content as it is, so you can also generate a document with a fixed shape, such as Markdown.

"FeatureReadme" {
layout {
"docs" / capture("feature") / "README.md".file()
.template {
val feature = captureValue("feature")
"""
# $feature
## Responsibility
TODO
## Public API
TODO
""".trimIndent()
}
}
}
Terminal window
./gradlew katachiTemplate --arg "template=FeatureReadme" --arg "feature=home"

This generates docs/home/README.md.

--arg onExisting= chooses what happens when a file already exists at the destination. The default is fail. When generating more than one template at once, onExisting applies to the whole set.

Value Behavior
fail (default) If even one already exists, writes none and fails
skip If even one already exists, writes none, without failing either
overwrite Replaces what’s there
Terminal window
./gradlew katachiTemplate \
--arg "template=UseCase" --arg "name=GetItemList" --arg "onExisting=overwrite"

You can also set the default in build.gradle.kts.

architecture-test/build.gradle.kts
import me.tbsten.katachi.gradle.KatachiOnExisting
katachi {
processors {
template {
onExisting = KatachiOnExisting.SKIP
}
}
}

Listing templates and seeing their details

Section titled “Listing templates and seeing their details”

The katachiTemplates task shows which templates exist, and what to pass to generate what.

Terminal window
# a list of templates (title / summary, parameter names, the specifier you can write for --arg template=)
./gradlew katachiTemplates
# the detail of one template
./gradlew katachiTemplates --arg "template=data.RepositoryInterface"

The detail shows the list of parameters, the destination path and the file’s contents, and a katachiTemplate command.

With the IntelliJ IDEA / Android Studio plugin installed, you can list templates, fill in their parameters and generate, all inside the IDE. You do not have to assemble --arg. It is experimental, so its screens and behavior may change in later versions.

It is not published on JetBrains Marketplace. Install it from the zip on GitHub Releases:

  1. Download katachi-intellij-plugin-<version>.zip from GitHub Releases.
  2. In the IDE, open Settings → Plugins, choose ⚙ → Install Plugin from Disk…, and select the zip without extracting it.
  3. Restart the IDE if it asks you to.

It works with IntelliJ IDEA 2026.1 and Android Studio Quail (2026.1) or later. If the project has the katachi Gradle plugin 0.2.0 or later and Gradle is synced, you can generate from three places. The screens follow the IDE’s language (Japanese or English).

  • The katachi tool window on the right edge
  • Right-click in the Project tree → New → katachi
  • The notification that appears above the editor when you open a file that a template fits

Updating the IDE does not update the plugin. Install a newer version from Releases again.

  • Add .template { } to a file declaration, and the katachiTemplate task can generate that file as code.
  • The katachiTemplates task shows a list of templates, and the detail of each parameter and destination path.
  • The IDE plugin (experimental) does the same from the IDE screens.