{"id":41126412,"url":"https://github.com/ktool-dev/ktml","last_synced_at":"2026-03-14T07:13:55.857Z","repository":{"id":314630615,"uuid":"1056221254","full_name":"ktool-dev/ktml","owner":"ktool-dev","description":"Fast component based HTML template engine for Kotlin on the JVM or with KMP","archived":false,"fork":false,"pushed_at":"2026-01-17T22:04:26.000Z","size":707,"stargazers_count":19,"open_issues_count":1,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-18T10:05:16.692Z","etag":null,"topics":["html","kotlin","ssr"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ktool-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-09-13T16:31:16.000Z","updated_at":"2026-01-17T22:04:30.000Z","dependencies_parsed_at":"2025-09-13T18:40:14.380Z","dependency_job_id":"3ca614b2-e5d9-43e4-a26a-a60154e9d857","html_url":"https://github.com/ktool-dev/ktml","commit_stats":null,"previous_names":["ktool-dev/ktml"],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/ktool-dev/ktml","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktool-dev%2Fktml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktool-dev%2Fktml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktool-dev%2Fktml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktool-dev%2Fktml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ktool-dev","download_url":"https://codeload.github.com/ktool-dev/ktml/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktool-dev%2Fktml/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28667881,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T17:07:18.858Z","status":"ssl_error","status_checked_at":"2026-01-22T17:05:02.040Z","response_time":144,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["html","kotlin","ssr"],"created_at":"2026-01-22T18:08:24.841Z","updated_at":"2026-01-22T18:08:25.567Z","avatar_url":"https://github.com/ktool-dev.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"![KTML](https://github.com/ktool-dev/.github/blob/main/KTML%20Logo.png?raw=true \"KT\")\n\n# Kotlin Multiplatform HTML Template Engine\n\n[![Build](https://github.com/ktool-dev/ktml/actions/workflows/ci.yaml/badge.svg)](https://github.com/ktool-dev/ktml/actions/workflows/ci.yaml)\n[![Maven Central](https://img.shields.io/maven-central/v/dev.ktml/ktml-runtime)](https://central.sonatype.com/artifact/dev.ktml/ktml-runtime)\n[![GitHub License](https://img.shields.io/github/license/ktool-dev/ktml)](https://github.com/ktool-dev/ktml/blob/main/LICENSE)\n[![GitHub issues](https://img.shields.io/github/issues/ktool-dev/ktml)](https://github.com/ktool-dev/ktml/issues)\n[![Kotlin](https://img.shields.io/badge/kotlin-2.2.21-blue.svg?logo=kotlin)](http://kotlinlang.org)\n\n![](https://img.shields.io/badge/Platforms-gray)\n![](https://img.shields.io/badge/Jvm-blue)\n![](https://img.shields.io/badge/Linux-blue)\n![](https://img.shields.io/badge/macOS-blue)\n![](https://img.shields.io/badge/Windows-blue)\n![](https://img.shields.io/badge/iOS-blue)\n![](https://img.shields.io/badge/Android-blue)\n\nA blazingly fast, type-safe HTML template engine for Kotlin JVM and Multiplatform that transforms `.ktml` templates into\noptimized Kotlin functions.\n\n## Why KTML?\n\nKTML is designed to make building web applications with Kotlin simple, safe, and fast:\n\n- **🚀 Just HTML** - templates are valid HTML\n- **🧩 Component Based** - Custom tags are trivial to create, making it easy to break down content into reusable\n  components\n- **🔒 Type Safety** - Full compile-time type checking with nullable types, default parameters, and imported types from\n  your codebase\n- **📄 Flexible Template Types** - Support for full pages (with `\u003chtml\u003e` root), custom tags (reusable components), and\n  fragments (embeddable or directly routable)\n- **⚡ Blazing Fast** - Templates compile to pure Kotlin functions with little runtime overhead, even with hundreds of\n  custom tags\n- **🎯 Simple API** - Only two special attributes (`if` and `each`) to learn—the rest is just HTML and Kotlin\n- **🌍 Kotlin Multiplatform** - Works on JVM and Native platforms\n- **🔥 Hot Reloading** - Instant template updates in dev mode without restarting your server\n- **💻 Embedded Kotlin** - Use `\u003cscript type=\"text/kotlin\"\u003e` tags for complex processing directly in templates (use with\n  care 🙂)\n\n## Quick Start\n\n### 1. Create a Custom Tag Component\n\nCustom tags let you build reusable components with type-safe parameters that can be included in other templates:\n\n```html\n\u003c!-- card.ktml --\u003e\n\u003ccard header=\"${Content? = null}\" content=\"$Content\"\u003e\n    \u003cdiv class=\"card\"\u003e\n        \u003cdiv if=\"${header != null}\" class=\"card-header\"\u003e\n            $header\n        \u003c/div\u003e\n        \u003cdiv class=\"card-body\"\u003e\n            $content\n        \u003c/div\u003e\n    \u003c/div\u003e\n\u003c/card\u003e\n```\n\n### 2. Create a Full Page Template\n\nPages use `\u003chtml\u003e` as the root and can be rendered from a controller. A template can import types from your code and use\nvalues from a context model. To pull a value from the context model into the template you prefix the attribute name on\nthe root template tag with a `@`.\n\n```html\n\u003c!-- dashboard.ktml --\u003e\n\u003c!DOCTYPE html\u003e\n\nimport com.myapp.User\n\n\u003chtml lang=\"en\" @user=\"$User\"\u003e\n\u003chead\u003e\n    \u003ctitle\u003eDashboard\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003ccard\u003e\n    \u003cheader\u003e\u003ch2\u003eWelcome, ${user.name}!\u003c/h2\u003e\u003c/header\u003e\n    \u003cp if=\"${user.type == UserType.ADMIN}\"\u003eYou have admin privileges\u003c/p\u003e\n\u003c/card\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n### 3. Use in Your Application\n\nWith integrations for Spring MVC, Ktor, and Javalin, KTML works like other template engines. The Gradle plugin will\nautomatically generate and compile the code from your templates.\n\nHere's an example using Ktor:\n\n```kotlin\nfun main() {\n    embeddedServer(CIO, port = 8080, host = \"0.0.0.0\") {\n        install(KtmlPlugin)\n        configureRouting()\n    }.start(wait = true)\n}\n\nfun Application.configureRouting() {\n    routing {\n        get(\"/\") {\n            call.respondKtml(path = \"dashboard\", model = mapOf(\"user\" to User()))\n        }\n    }\n}\n```\n\nCheck out other [example applications here](https://github.com/ktool-dev/ktml/tree/main/applications)!\n\n## Gradle Plugin\n\nKTML has a Gradle plugin that helps integrate the code generation process into your build. You can find\nthe [documentation for it here](https://github.com/ktool-dev/ktml/tree/main/integrations/gradle).\n\n## Why build KTML when there are other template engines\n\nThere are a lot of HTML template engines available for the JVM. I've used most of them. The two I like the most are\nThymeleaf and Java Template Engine. In a lot of ways KTML is a combination of those two.\n\n### Thymeleaf\n\nFrom Thymeleaf I really like the concept of a template just being valid HTML, rather than having a bunch of added\ndeclarations in the file. The main issue I had with Thymeleaf is building custom tags was too difficult, and if you use\nthem too much, it greatly affects page render speed. I also found their `replace` concept, with the idea of have demo\ncontent in the template, just wasn't practical and wasn't something we ever used. Plus, it makes invoking other\ntemplates rather cumbersome. So, it didn't have the natural composability I wanted and had major performance issues.\n\n### Java Template Engine\n\nI like the type safety that JTE has and the performance. The idea of generating code from the template and compiling it\nseemed like the best way to go from a performance perspective. I didn't care for how other templates were invoked, or\nthat the template wasn't valid HTML like Thymeleaf. So, it also didn't have the natural composability I was looking for.\n\n### KTML is more like client side component frameworks\n\nHaving built a lot of web apps with client side component frameworks like React, I really liked the idea of breaking a\nsite down into reusable pieces of HTML that you use to build pages. I just also think SSR over SPA is often the right\nchoice and leads to an easier development process.\n\n## Template Types\n\nKTML supports three template types, each with a specific purpose:\n\n### 1. Full Pages\n\nTemplates with `\u003chtml\u003e` as the root element become pages accessible via web routes. All parameters declared on a page\ntemplate will be pulled from the context model. Since a page isn't called from another tag, all the parameters to it\nhave to come from the context model, so all parameter names have to be prefixed with `$`.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\" @userName=\"$String\"\u003e\n\u003chead\u003e\u003ctitle\u003eMy Page\u003c/title\u003e\u003c/head\u003e\n\u003cbody\u003e\u003ch1\u003eHello, ${userName}!\u003c/h1\u003e\u003c/body\u003e\n\u003c/html\u003e\n```\n\n### 2. Custom Tags\n\nTemplates with custom root elements become reusable components. Since strings are often used in attribute values, and\nattributes often use `\"` around the value, KTML lets you use a `'` inside the value. It will be changed to a `\"` in\nthe actual Kotlin code. If you actually need a `'` in the value, you can escape it with `\\'`.\n\n```html\n\n\u003cbutton-primary text=\"$String\" onClick=\"${String = ''}\"\u003e\n    \u003cbutton class=\"btn-primary\" onclick=\"${raw(onClick)}\"\u003e\n        ${text}\n    \u003c/button\u003e\n\u003c/button-primary\u003e\n```\n\nUse in other templates: `\u003cbutton-primary text=\"Click me!\" onClick=\"handleClick()\" /\u003e`\n\n### 3. Fragments\n\nCustom tags can also be labeled as fragments, which allows them to be called from other templates or called directly\nfrom a controller like a page. All template parameter values will get populated from the context model.\n\n```html\n\n\u003cuser-info fragment userName=\"$String\" userEmail=\"$String\"\u003e\n    \u003ch3\u003e$userName\u003c/h3\u003e\n    \u003cp\u003e$userEmail\u003c/p\u003e\n\u003c/user-info\u003e\n```\n\n## Type Safety Features\n\n### Nullable Types and Default Parameters\n\nKTML supports Kotlin's type system, including nullable types and default values:\n\n```html\n\n\u003cuser-profile\n        name=\"$String\"\n        bio=\"${String? = null}\"\n        role=\"${String = 'Member'}\"\n        isActive=\"${Boolean = true}\"\u003e\n\n    \u003cdiv class=\"profile\"\u003e\n        \u003ch2\u003e$name\u003c/h2\u003e\n        \u003cp if=\"${bio != null}\"\u003e$bio\u003c/p\u003e\n        \u003cspan class=\"role\"\u003e$role\u003c/span\u003e\n    \u003c/div\u003e\n\u003c/user-profile\u003e\n```\n\n### Imported Types\n\nImport your own Kotlin types for full type safety:\n\n```html\nimport dev.ktml.User\nimport dev.ktml.UserType\nimport dev.ktml.models.Product\n\n\u003cproduct-card product=\"Product\" user=\"User\"\u003e\n    \u003cdiv class=\"product\"\u003e\n        \u003ch3\u003e${product.name}\u003c/h3\u003e\n        \u003cp\u003e${product.price}\u003c/p\u003e\n        \u003cbutton if=\"${user.type == UserType.ADMIN}\"\u003eEdit\u003c/button\u003e\n    \u003c/div\u003e\n\u003c/product-card\u003e\n```\n\n### Context Parameters\n\nSometimes templates need to access data that isn't passed directly to them by the calling template but comes from the\ncontext of the request. To have a template define a context value it uses we use `$` prefix for the parameter. The\ngenerated code will automatically pull the value from the context object and ensure it's of the correct type:\n\n```html\n\n\u003csidebar @items=\"${List\u003cMenuItem\u003e = listOf()}\"\u003e\n    \u003cnav\u003e\n        \u003ca each=\"${item in items}\" href=\"${item.url}\"\u003e${item.label}\u003c/a\u003e\n    \u003c/nav\u003e\n\u003c/sidebar\u003e\n```\n\n## Simple API: Just `if` and `each`\n\nKTML keeps it simple with only two special attributes to learn:\n\n### Conditional Rendering: `if`\n\n```html\n\u003ch2 if=\"${user.isAdmin}\"\u003eAdmin Panel\u003c/h2\u003e\n\u003cp if=\"${user.balance \u003e 0}\"\u003eBalance: ${user.balance}\u003c/p\u003e\n```\n\n### Loops: `each`\n\n```html\n\n\u003cul\u003e\n    \u003cli each=\"${item in items}\"\u003e${item.name}\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003c!-- With index --\u003e\n\u003cdiv each=\"${(index, product) in products.withIndex()}\"\u003e\n    ${index + 1}. ${product.name}\n\u003c/div\u003e\n```\n\n## Embedded Kotlin for Complex Logic\n\nWhen you need more than simple expressions, embed Kotlin directly in templates:\n\n```html\n\n\u003creport sales=\"${List\u003cSale\u003e}\"\u003e\n    \u003cscript type=\"text/kotlin\"\u003e\n        val totalRevenue = sales.sumOf { it.amount }\n        val avgSale = totalRevenue / sales.size\n        val topSale = sales.maxByOrNull { it.amount }\n    \u003c/script\u003e\n\n    \u003cdiv class=\"report\"\u003e\n        \u003ch2\u003eSales Report\u003c/h2\u003e\n        \u003cp\u003eTotal Revenue: $${totalRevenue}\u003c/p\u003e\n        \u003cp\u003eAverage Sale: $${avgSale}\u003c/p\u003e\n        \u003cp if=\"${topSale != null}\"\u003eTop Sale: $${topSale.amount}\u003c/p\u003e\n    \u003c/div\u003e\n\u003c/report\u003e\n```\n\n## Content Parameters\n\nPass HTML blocks as parameters for flexible composition:\n\n```html\n\n\u003cmodal title=\"$String\" footer=\"${Content? = null}\" content=\"$Content\"\u003e\n    \u003cdiv class=\"modal\"\u003e\n        \u003cdiv class=\"modal-header\"\u003e\u003ch3\u003e${title}\u003c/h3\u003e\u003c/div\u003e\n        \u003cdiv class=\"modal-body\"\u003e${content}\u003c/div\u003e\n        \u003cdiv if=\"${footer != null}\" class=\"modal-footer\"\u003e${footer}\u003c/div\u003e\n    \u003c/div\u003e\n\u003c/modal\u003e\n```\n\n```html\n\n\u003cmodal title=\"Confirm Action\"\u003e\n    \u003cp\u003eAre you sure you want to proceed?\u003c/p\u003e\n    \u003cfooter\u003e\n        \u003cbutton\u003eCancel\u003c/button\u003e\n        \u003cbutton\u003eConfirm\u003c/button\u003e\n    \u003c/footer\u003e\n\u003c/modal\u003e\n```\n\n## Raw Output\n\nUse `raw()` to output unescaped HTML (use carefully!):\n\n```html\n\n\u003ccode-block code=\"String\"\u003e\n    \u003cpre\u003e\u003ccode\u003e${raw(code)}\u003c/code\u003e\u003c/pre\u003e\n\u003c/code-block\u003e\n```\n\n## Dev Mode\n\nWhen you include the `dev-mode` dependency for KTML, you can run locally and you're templates will automatically be\ncompiled as they change. If compilation fails, you'll get messages in the console explaining where the code in the\ntemplate is that caused the error. And if you try to render a KTML page in the browser, you'll get the compilation error\nscreen. Which looks like this:\n\n\u003cimg src=\"https://iili.io/KrmIz6N.jpg\" alt=\"KTML Compiler Error\"\u003e\n\n## Performance\n\nKTML is designed for speed:\n\n- **Zero Runtime Overhead** - Templates compile to simple Kotlin functions\n- **No Reflection** - Everything is resolved at compile time\n- **Efficient Composition** - Custom tag calls are just function calls\n- **Optimized String Building** - Direct writer output with minimal allocations\n\nRendering a complex page with hundreds of custom tags is as fast as manually writing the equivalent Kotlin string\nconcatenation code.\n\n## Building\n\n```bash\n# Build all platforms\n./gradlew build\n```\n\n## Testing\n\n```bash\n# Run all tests\n./gradlew test\n\n# Run JVM tests only\n./gradlew jvmTest\n```\n\n## Platform Support\n\n- **JVM** - Java 22+ required\n- **Native** - Linux x64, macOS x64/ARM64, Windows x64\n\n## Dependencies\n\n- **Kotlin Multiplatform** - Core platform\n- **Ksoup** - HTML parsing and entity handling\n- **Kotlin Logging** - Logging framework\n- **Kotlinx Coroutines** - Async support\n- **Kotest** - Testing framework\n\n## Architecture\n\nKTML processes templates through a clean pipeline:\n\n1. **HTML Parsing** - Parse `.ktml` files using Ksoup\n2. **AST Generation** - Build abstract syntax tree\n3. **Kotlin Code Generation** - Generate type-safe Kotlin functions\n4. **Compilation** - Compile to bytecode (JVM) or native code\n5. **Execution** - Fast function calls at runtime\n\nKey components:\n\n- **KtmlEngine** - Main orchestrator for template processing\n- **TemplateParser** - Parses HTML templates\n- **KotlinFileGenerator** - Generates type-safe Kotlin functions\n- **Templates Registry** - Manages template dependencies\n- **Context System** - Handles type-safe parameter binding\n\n## License\n\nThis project is licensed under the [Apache 2.0 License](LICENSE).\n\n## Contributing\n\nContributions are welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch\n3. Write tests for your changes\n4. Ensure all tests pass with `./gradlew test`\n5. Submit a pull request\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fktool-dev%2Fktml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fktool-dev%2Fktml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fktool-dev%2Fktml/lists"}