{"id":13428847,"url":"https://github.com/konform-kt/konform","last_synced_at":"2026-01-11T17:02:51.976Z","repository":{"id":37768245,"uuid":"124291072","full_name":"konform-kt/konform","owner":"konform-kt","description":"Portable validations for Kotlin","archived":false,"fork":false,"pushed_at":"2025-12-31T12:33:06.000Z","size":780,"stargazers_count":788,"open_issues_count":17,"forks_count":40,"subscribers_count":5,"default_branch":"main","last_synced_at":"2026-01-03T08:14:26.943Z","etag":null,"topics":["kotlin","kotlin-multiplatform","validation"],"latest_commit_sha":null,"homepage":"https://www.konform.io","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/konform-kt.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":".github/CODEOWNERS","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":null,"dco":null,"cla":null}},"created_at":"2018-03-07T20:32:19.000Z","updated_at":"2025-12-23T00:47:16.000Z","dependencies_parsed_at":"2025-12-13T18:03:35.637Z","dependency_job_id":null,"html_url":"https://github.com/konform-kt/konform","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/konform-kt/konform","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konform-kt%2Fkonform","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konform-kt%2Fkonform/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konform-kt%2Fkonform/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konform-kt%2Fkonform/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/konform-kt","download_url":"https://codeload.github.com/konform-kt/konform/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konform-kt%2Fkonform/sbom","scorecard":{"id":1011,"data":{"date":"2025-08-04","repo":{"name":"github.com/konform-kt/konform","commit":"bf5c66132c37636c5041a71e06390588b5fdcb7d"},"scorecard":{"version":"v5.2.1-28-gc1d103a9","commit":"c1d103a9bb9f635ec7260bf9aa0699466fa4be0e"},"score":5.3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 1/15 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#code-review"}},{"name":"Maintained","score":10,"reason":"14 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#cii-best-practices"}},{"name":"Token-Permissions","score":10,"reason":"GitHub workflow tokens follow principle of least privilege","details":["Info: topLevel 'contents' permission set to 'read': .github/workflows/release.yml:9","Info: topLevel 'contents' permission set to 'read': .github/workflows/test.yml:9","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/konform-kt/konform/release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/konform-kt/konform/release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:36: update your workflow using https://app.stepsecurity.io/secureworkflow/konform-kt/konform/release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:83: update your workflow using https://app.stepsecurity.io/secureworkflow/konform-kt/konform/release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:84: update your workflow using https://app.stepsecurity.io/secureworkflow/konform-kt/konform/release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/konform-kt/konform/test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/konform-kt/konform/test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/konform-kt/konform/test.yml/main?enable=pin","Info:   0 out of   8 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#pinned-dependencies"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#license"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":3,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'main'","Info: 'force pushes' disabled on branch 'main'","Info: 'branch protection settings apply to administrators' is required to merge on branch 'main'","Warn: could not determine whether codeowners review is allowed","Warn: 'up-to-date branches' is disabled on branch 'main'","Info: status check found to merge onto on branch 'main'","Warn: PRs are not required to make changes on branch 'main'; or we don't have data to detect it.If you think it might be the latter, make sure to run Scorecard with a PAT or use Repo Rules (that are always public) instead of Branch Protection settings"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#branch-protection"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/release.yml:14"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":5,"reason":"5 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-14T12:31:11.088Z","repository_id":37768245,"created_at":"2025-08-14T12:31:11.088Z","updated_at":"2025-08-14T12:31:11.088Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28314260,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T14:58:17.114Z","status":"ssl_error","status_checked_at":"2026-01-11T14:55:53.580Z","response_time":60,"last_error":"SSL_read: 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":["kotlin","kotlin-multiplatform","validation"],"created_at":"2024-07-31T01:01:06.632Z","updated_at":"2026-01-11T17:02:51.970Z","avatar_url":"https://github.com/konform-kt.png","language":"Kotlin","funding_links":[],"categories":["Libraries","Kotlin","Testing"],"sub_categories":["Test","SQL"],"readme":"[![Test](https://github.com/konform-kt/konform/actions/workflows/release.yml/badge.svg?branch=main)](https://github.com/konform-kt/konform/actions/workflows/release.yml)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.konform/konform/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.konform/konform)\n\n# Portable validations for Kotlin\n\n- **✅ Type-safe DSL**\n- **🔗 Multi-platform support** (JVM, JS, Native, Wasm)\n- **🐥 Zero dependencies**\n\n### Installation\n\nFor multiplatform projects:\n\n```\nkotlin {\n    sourceSets {\n        commonMain {\n            dependencies {\n                implementation(\"io.konform:konform:0.11.0\")\n            }\n        }\n    }\n}\n```\n\nFor jvm-only projects add:\n\n```\ndependencies {\n    implementation(\"io.konform:konform-jvm:0.11.0\")\n}\n```\n\n### Use\n\nSuppose you have a data class like this:\n\n```kotlin\ndata class UserProfile(\n    val fullName: String,\n    val age: Int?\n)\n```\n\nUsing the Konform type-safe DSL you can quickly write up a validation\n\n```kotlin\nval validateUser = Validation\u003cUserProfile\u003e {\n    UserProfile::fullName {\n        minLength(2)\n        maxLength(100)\n    }\n\n    UserProfile::age ifPresent {\n        minimum(0)\n        maximum(150)\n    }\n}\n```\n\nand apply it to your data\n\n```kotlin\nval invalidUser = UserProfile(\"A\", -1)\nval validationResult = validateUser(invalidUser)\n```\n\nsince the validation fails the `validationResult` will be of type `Invalid` and you can get a list of validation errors by indexed access:\n\n```kotlin\nvalidationResult.errors.messagesAtPath(UserProfile::fullName)\n// yields listOf(\"must have at least 2 characters\")\n\nvalidationResult.errors.messagesAtPath(UserProfile::age)\n// yields listOf(\"must be at least '0'\")\n```\n\nor you can get all validation errors with details as a list:\n\n```kotlin\nvalidationResult.errors\n// yields listOf(\n//     ValidationError(path=ValidationPath(Prop(fullName)), message=must have at least 2 characters),\n//     ValidationError(path=ValidationPath(Prop(age)), message=must be at least '0')\n// )\n```\n\nIn case the validation went through successfully you get a result of type `Valid` with the validated value in the `value` field.\n\n```kotlin\nval validUser = UserProfile(\"Alice\", 25)\nval validationResult = validateUser(validUser)\n// yields Valid(UserProfile(\"Alice\", 25))\n```\n\n### Detailed usage\n\n#### Hints\n\nYou can add custom hints to validations\n\n```kotlin\nval validateUser = Validation\u003cUserProfile\u003e {\n    UserProfile::age ifPresent {\n        minimum(0) hint \"Registering before birth is not supported\"\n    }\n}\n```\n\nYou can use `{value}` to include the `.toString()`-ed data in the hint\n\n```kotlin\nval validateUser = Validation\u003cUserProfile\u003e {\n    UserProfile::fullName {\n        minLength(2) hint \"'{value}' is too short a name, must be at least 2 characters long.\"\n    }\n}\n```\n\n#### Custom context\n\nYou can add customs context to validation errors\n\n```kotlin\nval validateUser = Validation\u003cUserProfile\u003e {\n    UserProfile::age {\n        minimum(0) userContext Severity.ERROR\n        // You can also set multiple things at once\n        minimum(0).replace(\n            hint = \"Registering before birth is not supported\",\n            userContext = Severity.ERROR,\n        )\n    }\n}\n```\n\n#### Custom validations\n\nYou can add custom validations on properties by using `constrain`\n\n```kotlin\nval validateUser = Validation\u003cUserProfile\u003e {\n    UserProfile::fullName {\n        constrain(\"Name cannot contain a tab\") { !it.contains(\"\\t\") }\n        // Set a custom path for the error\n        constrain(\"Name must have a non-whitespace character\", path = ValidationPath.of(\"trimmedName\")) {\n            it.trim().isNotEmpty()\n        }\n        // Set custom context\n        constrain(\"Must have 5 characters\", userContext = Severity.ERROR) {\n            it.size \u003e= 5\n        }\n    }\n}\n```\n\nYou can transform data and then add a validation on the result\n\n```kotlin\nval validateUser = Validation\u003cUserProfile\u003e {\n    validate(\"trimmedName\", { it.fullName.trim() }) {\n        minLength(5)\n    }\n    // This also required and ifPresent for nullable values\n    required(\"yourName\", /* ...*/) {\n        // your validations, giving an error out if the result is null\n    }\n    ifPresent(\"yourName\", /* ... */) {\n        // your validations, only running if the result is not null\n    }\n    // You can use a more extensive path, for example\n    // the path will be \".fullName.trimmed\" here:\n    validate(ValidationPath.of(UserProfile::fullName, \"trimmed\"), { /* ... */ }) {\n        /* ... */\n    }\n}\n```\n\n#### Split validations\n\nYou can define validations separately and run them from other validations\n\n```kotlin\nval ageCheck = Validation\u003cInt?\u003e {\n    required {\n        minimum(21)\n    }\n}\n\nval validateUser = Validation\u003cUserProfile\u003e {\n    UserProfile::age {\n        run(ageCheck)\n    }\n\n    // You can also transform the data and then run a validation against the result\n    validate(\"ageMinus10\", { it.age?.let { age -\u003e age - 10 } }) {\n        run(ageCheck)\n    }\n}\n```\n\n#### Collections\n\nIt is also possible to validate nested data classes and properties that are collections (List, Map, etc...)\n\n```kotlin\ndata class Person(val name: String, val email: String?, val age: Int)\n\ndata class Event(\n    val organizer: Person,\n    val attendees: List\u003cPerson\u003e,\n    val ticketPrices: Map\u003cString, Double?\u003e\n)\n\nval validateEvent = Validation\u003cEvent\u003e {\n    Event::organizer {\n        // even though the email is nullable you can force it to be set in the validation\n        Person::email required {\n            // Optionally set a hint, default hint is \"is required\"\n            hint = \"Email address must be given\"\n            // Optionally set a user context, default is `null`\n            userContext = Severity.ERROR\n            pattern(\".+@bigcorp.com\") hint \"Organizers must have a BigCorp email address\"\n        }\n    }\n\n    // validation on the attendees list\n    Event::attendees {\n        maxItems(100)\n    }\n\n    // validation on individual attendees\n    Event::attendees onEach {\n        Person::name {\n            minLength(2)\n        }\n        Person::age {\n            minimum(18) hint \"Attendees must be 18 years or older\"\n        }\n        // Email is optional but if it is set it must be valid\n        Person::email ifPresent {\n            pattern(\".+@.+\\..+\") hint \"Please provide a valid email address (optional)\"\n        }\n    }\n\n    // validation on the ticketPrices Map as a whole\n    Event::ticketPrices {\n        minItems(1) hint \"Provide at least one ticket price\"\n    }\n\n    // validations for the individual entries\n    Event::ticketPrices onEach {\n        // Tickets may be free in which case they are null\n        Entry\u003cString, Double?\u003e::value ifPresent {\n            minimum(0.01)\n        }\n    }\n}\n```\n\nErrors in the `ValidationResult` can also be accessed using the index access method. In case of `Iterables` and `Arrays` you use the\nnumerical index and in case of `Maps` you use the key as string.\n\n```kotlin\n// get the error messages for the first attendees age if any\nresult.errors.messagesAtPath(Event::attendees, 0, Person::age)\n\n// get the error messages for the free ticket if any\nresult.errors.messagesAtPath(Event::ticketPrices, \"free\")\n```\n\n#### Dynamic Validations\n\nSometimes you want to create validations that depend on the context of the actual value being validated,\nor define validations for fields that depend on other fields.\nNote that this will generally have worse performance than using static validations.\n\n```kotlin\nValidation\u003cAddress\u003e {\n    Address::postalCode dynamic { address -\u003e\n        when (address.countryCode) {\n            \"US\" -\u003e pattern(\"[0-9]{5}\")\n            else -\u003e pattern(\"[A-Z]+\")\n        }\n    }\n}\n```\n\nif you need to use a value further in, you can capture an earlier value with `dynamic`.\n\n```kotlin\ndata class Numbers(val minimum: Int, val numbers: List\u003cInt\u003e)\n\nValidation\u003cNumbers\u003e {\n    dynamic { numbers -\u003e\n        Numbers::numbers onEach {\n            minimum(numbers.minimum)\n        }\n    }\n}\n```\n\n#### Subtypes\n\nYou can run validations only if the value is of a specific subtype, or require it to be specific subtype.\n\n```kotlin\nsealed interface Animal {\n    val name: String\n}\ndata class Cat(override val name: String, val favoritePrey: String) : Animal\ndata class Dog(override val name: String) : Animal\n\nval validateAnimal = Validation\u003cAnimal\u003e {\n    Animal::name {\n        notBlank()\n    }\n    // Only run this validation if the current Animal is a Cat and not null\n    ifInstanceOf\u003cCat\u003e {\n        Cat::favoritePrey {\n            notBlank()\n        }\n    }\n}\nval requireCat = Validation\u003cAnimal\u003e {\n    // This will return an invalid result is the current Animal is not a Cat or null\n    requireInstanceOf\u003cCat\u003e {\n        Cat::favoritePrey {\n            // ...\n        }\n    }\n}\n```\n\n#### Recursive validation\n\nIf you have a recursive type that you can validate, this requires\n\n1) an extra getter to get a self-reference to the validation, and\n2) dynamic to create an extra instance of the validation as-needed to avoid an infinite loop\n\n```kotlin\ndata class Node(val children: List\u003cNode\u003e)\nval validation = Validation\u003cNode\u003e {\n  // Use dynamic and a function to get the current validation again\n  Node::children onEach {\n    runDynamic { validationRef() }\n  }\n}\n// Type must be explicitly specified on either this or the val\nprivate val validationRef get(): Validation\u003cNode\u003e = validation\n```\n\n#### Fail-fast validations\n\nKonform is primarily intended to validate the complete data and return all validation errors.\nHowever, if you want to \"fail fast\" and not run later validations, you can do this with `andThen`\non `Validation` or `flatten` on a list of validations.\n\n```kotlin\nval fastValidation = Validation\u003cString\u003e { /* ... */ }\nval slowValidation = Validation\u003cString\u003e { /* ... */ }\n\nval runSlowOnlyIfFastValidationSucceeds = Validation\u003cString\u003e {\n    run(fastValidation andThen slowValidation)\n}\n```\n\n### Other validation libraries for Kotlin\n\n- Akkurate: https://akkurate.dev/docs/overview.html\n- Kalidation: https://github.com/rcapraro/kalidation\n- Valikator: https://github.com/valiktor/valiktor\n\n### Integration with testing libraries\n\n- [Kotest](https://kotest.io) provides various matchers for use with Konform. They can be used in your tests to assert that a given object\n  is validated successfully or fails validation with specific error messages.\n  See [documentation](https://kotest.io/docs/assertions/konform-matchers.html).\n\n##### Maintainer\n\n[David Hoepelman](https://hoepelman.dev/) (Current maintainer)\n[Niklas Lochschmidt](https://niklaslochschmidt.com) (Original author, co-maintainer)\n\n##### License\n\n[MIT License](https://github.com/konform-kt/konform/blob/main/LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkonform-kt%2Fkonform","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkonform-kt%2Fkonform","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkonform-kt%2Fkonform/lists"}