Installation
On the Hello Doodle page, you got a look at why Doodle might be worth your time. Now let's make it real: this page gets a project on your machine set up and building — no app code yet, just a clean foundation for Applications to build on next.
Before you start
- JDK 11 or newer. Both Gradle and Doodle's Desktop target need it.
- An IDE with Kotlin support. IntelliJ IDEA is the natural choice, but any editor works.
Don't worry if Gradle isn't your comfort zone — everything you need is right here to copy in.
Two ways to get started
- Fastest: borrow a working example. Doodle Tutorials has several real, runnable apps waiting for you — a calculator, a contacts app, a photo stream, and more — each with its own tested
build.gradle.ktsready to copy wholesale. - From scratch: build your own. That's the road the rest of this page takes, and it's exactly where Applications picks up.
Choosing your target
| If you want to build for... | Use |
|---|---|
| Just the Web | Single-platform: Web (JavaScript) or Web (WebAssembly) |
| Just Desktop | Single-platform: Desktop (JVM) |
| Both | Multiplatform |
Still deciding? Go with a single platform for now. Doodle's core APIs — Application, Display, View, layout, rendering — are identical everywhere, so nothing you build here is wasted if you branch out to another target down the line.
Single-platform setup
- Browser (JavaScript)
- Browser (WasmJs)
- Desktop (JVM)
You can set up an app that runs in the Browser using JavaScript with the following build script.
build.gradle.kts
plugins {
id ("org.jetbrains.kotlin.multiplatform") version "2.1.10"
}
version = "1.0.0"
group = "com.my.cool.app"
repositories {
mavenCentral()
}
kotlin {
js {
browser {
binaries.executable()
}
}
val doodleVersion = "0.11.5" // <--- Latest Doodle version
dependencies {
implementation ("io.nacular.doodle:browser:$doodleVersion")
// Optional
// implementation ("io.nacular.doodle:controls:$doodleVersion" )
// implementation ("io.nacular.doodle:animation:$doodleVersion")
// implementation ("io.nacular.doodle:themes:$doodleVersion" )
}
}Learn more about Kotlin for Javascript.
WasmJs apps run in the Browser and are configured as follows.
build.gradle.kts
plugins {
id ("org.jetbrains.kotlin.multiplatform") version "2.1.10"
}
version = "1.0.0"
group = "com.my.cool.app"
repositories {
mavenCentral()
}
kotlin {
wasmJs {
browser {
binaries.executable()
}
}
val doodleVersion = "0.11.5" // <--- Latest Doodle version
dependencies {
implementation ("io.nacular.doodle:browser:$doodleVersion")
// Optional
// implementation ("io.nacular.doodle:controls:$doodleVersion" )
// implementation ("io.nacular.doodle:animation:$doodleVersion")
// implementation ("io.nacular.doodle:themes:$doodleVersion" )
}
}Learn more about Kotlin for WebAssembly.
You can set up a pure JVM app that runs on Desktop with the following build scripts.
build.gradle.kts
//plugins {
// kotlin("multiplatform") version "2.1.10"
// application
//}
//
//version = "1.0.0"
//group = "com.my.cool.app"
//
//repositories {
// mavenCentral()
// maven {
// url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev")
// }
//}
//
//kotlin {
// compilerOptions {
// jvmTarget.set(JVM_11) // JVM 11 is needed for Desktop
// }
// withJava()
//
// val doodleVersion = "0.11.5" // <--- Latest Doodle version
//
// dependencies {
// // helper to derive OS/architecture pair
// when (osTarget()) {
// "macos-x64" -> implementation("io.nacular.doodle:desktop-jvm-macos-x64:$doodleVersion" )
// "macos-arm64" -> implementation("io.nacular.doodle:desktop-jvm-macos-arm64:$doodleVersion" )
// "linux-x64" -> implementation("io.nacular.doodle:desktop-jvm-linux-x64:$doodleVersion" )
// "linux-arm64" -> implementation("io.nacular.doodle:desktop-jvm-linux-arm64:$doodleVersion" )
// "windows-x64" -> implementation("io.nacular.doodle:desktop-jvm-windows-x64:$doodleVersion" )
// "windows-arm64" -> implementation("io.nacular.doodle:desktop-jvm-windows-arm64:$doodleVersion")
// }
//
// // Optional
// // implementation ("io.nacular.doodle:controls:$doodleVersion" )
// // implementation ("io.nacular.doodle:animation:$doodleVersion")
// // implementation ("io.nacular.doodle:themes:$doodleVersion" )
// }
//
// // could be moved to buildSrc, but kept here for clarity
// fun osTarget(): String {
// val osName = System.getProperty("os.name")
// val targetOs = when {
// osName == "Mac OS X" -> "macos"
// osName.startsWith("Win" ) -> "windows"
// osName.startsWith("Linux") -> "linux"
// else -> error("Unsupported OS: $osName")
// }
//
// val targetArch = when (val osArch = System.getProperty("os.arch")) {
// "x86_64", "amd64" -> "x64"
// "aarch64" -> "arm64"
// else -> error("Unsupported arch: $osArch")
// }
//
// return "${targetOs}-${targetArch}"
// }
//}
//
//application {
// mainClass.set("YOUR_CLASS")
//}Learn more about Kotlin for the JVM.
Multiplatform setup
Doodle's own libraries are Kotlin Multiplatform, so there's no reason yours can't be too. Write everything except main() in shared (commonMain) code, and watch it run unchanged on both Web and Desktop.
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
plugins {
id ("org.jetbrains.kotlin.multiplatform") version "2.4.21"
application
}
version = "1.0.0"
group = "com.my.cool.app"
repositories {
mavenCentral()
maven {
url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
// could be moved to buildSrc, but kept here for clarity
fun osTarget(): String {
val osName = System.getProperty("os.name")
val targetOs = when {
osName == "Mac OS X" -> "macos"
osName.startsWith("Win" ) -> "windows"
osName.startsWith("Linux") -> "linux"
else -> error("Unsupported OS: $osName")
}
val targetArch = when (val osArch = System.getProperty("os.arch")) {
"x86_64", "amd64" -> "x64"
"aarch64" -> "arm64"
else -> error("Unsupported arch: $osArch")
}
return "${targetOs}-${targetArch}"
}
kotlin {
js {
browser { binaries.executable() }
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser { binaries.executable() }
}
jvm {
compilerOptions {
jvmTarget.set(JVM_11) // JVM 11 is needed for Desktop
}
}
val doodleVersion = "0.11.0" // <--- Latest Doodle version
sourceSets {
commonMain.dependencies {
// Optional
// implementation ("io.nacular.doodle:controls:$doodleVersion" )
// implementation ("io.nacular.doodle:animation:$doodleVersion")
// implementation ("io.nacular.doodle:themes:$doodleVersion" )
}
jsMain.dependencies {
implementation ("io.nacular.doodle:browser:$doodleVersion")
}
jvmMain.dependencies {
// helper to derive OS/architecture pair
when (osTarget()) {
"macos-x64" -> implementation("io.nacular.doodle:desktop-jvm-macos-x64:$doodleVersion" )
"macos-arm64" -> implementation("io.nacular.doodle:desktop-jvm-macos-arm64:$doodleVersion" )
"linux-x64" -> implementation("io.nacular.doodle:desktop-jvm-linux-x64:$doodleVersion" )
"linux-arm64" -> implementation("io.nacular.doodle:desktop-jvm-linux-arm64:$doodleVersion" )
"windows-x64" -> implementation("io.nacular.doodle:desktop-jvm-windows-x64:$doodleVersion" )
"windows-arm64" -> implementation("io.nacular.doodle:desktop-jvm-windows-arm64:$doodleVersion")
}
}
}
}
application {
mainClass.set("YOUR_CLASS")
}
App launch code is the only part that needs to live in a platform-specific source set (jsMain, wasmJsMain, jvmMain) — everything else can go in commonMain.
Check your setup
There's no app code yet — that's what Applications is for — so there's nothing to actually run just yet. What we can confirm right now is that the project builds cleanly, with every dependency resolved:
./gradlew build
A clean finish means your setup is correct — well done. Once you've built your first Application on the next page, come back to these commands to actually watch it appear on screen:
| Target | Run with |
|---|---|
| Web (JavaScript) | ./gradlew jsBrowserDevelopmentRun |
| Web (WebAssembly) | ./gradlew wasmJsBrowserDevelopmentRun |
| Desktop (JVM) | ./gradlew run (single-platform) or ./gradlew jvmRun (multiplatform) |
Recap
- Pick single-platform if you only need Web or only need Desktop; pick multiplatform if you need both.
- Doodle's dependency (
browserfor Web, adesktop-jvm-*artifact for Desktop) goes in the source set for that platform —commonMainonly ever holds platform-agnostic code. ./gradlew buildconfirms your setup before you've written any app code; the per-platform run commands come into play once you have.
Next: head to Applications to write your first Application class and give these commands something to actually run.