
Demonstrates publishing workflow and a simple Fibonacci generator, with GPG key setup, namespace registration, Gradle publishing plugin configuration, and GitHub Actions CI for Maven Central publication.
A Kotlin Multiplatform SDK for the Notion API, published on Maven Central. Write your Notion integration once and run it on Android, iOS, JVM, JavaScript, macOS, and Linux — no code duplication, no platform-specific workarounds.
| Platform | Target |
|---|---|
| Android | androidTarget |
| iOS |
iosX64, iosArm64, iosSimulatorArm64
|
| JVM (Desktop / Server) | jvm |
| JavaScript (Browser) | js |
| macOS |
macosX64, macosArm64
|
| Linux | linuxX64 |
The library is structured in three layers, all written in commonMain:
NotionClient
├── Repository (public API — suspend functions, clean domain types)
│ └── Service (HTTP communication via Ktor)
│ └── NotionHttp (auth injection, base URL, headers)
└── DI modules (Koin — optional, wired automatically)
PageRepository, BlockRepository, …).@Serializable DTOs.Authorization header via a pluggable TokenProvider, keeping auth decoupled from transport.CIO on JVM, Android on Android, Darwin on Apple, Js on JS), so the common code never touches platform internals.Add the dependency to your build.gradle.kts:
commonMain.dependencies {
implementation("io.github.tungnk123:notion-sdk-kmp:1.1.11")
}val client = NotionClientFactory.fromToken(token = "secret_...")
// Retrieve a page
val page = client.pageRepository.retrieve(pageId)
// Query a database
val results = client.databaseRepository.query(
id = databaseId,
req = QueryDatabaseRequest(/* filters, sorts … */)
)
// Full-text search
val pages = client.searchRepository.searchPages(query = "Books")Useful when the token is stored in your own secure storage or refreshed dynamically:
class MyTokenProvider : TokenProvider {
override fun token(): String = MySecureStorage.read("notion_token")
}
val client = NotionClientFactory.fromTokenProvider(MyTokenProvider())val repo = AuthRepository(
service = AuthServiceImpl(httpClient),
clientId = "...",
clientSecret = "...",
storage = InMemoryTokenStorage()
)
// 1. Redirect the user
val authorizeUrl = repo.authorizeUrl(redirectUri, state, ownerWorkspace = false)
// 2. Handle the callback
val token = repo.exchangeCodeForTokenAndSaveToken(code, redirectUri)
// 3. Build the client
val client = NotionClientFactory.fromAuthRepository(repo)val manager = NotionSessionManager(authFactory, tokenStore)
// Observe auth state reactively
manager.state.collect { state ->
when (state) {
is SessionState.SignedOut -> showLoginScreen()
is SessionState.Authorizing -> openBrowser(state.intent.url)
is SessionState.Authorized -> showHome(state.session)
}
}
// Switch between workspaces
manager.switchWorkspace(workspaceId)
// Sign out
manager.disconnectWorkspace(workspaceId)| Resource | Operations |
|---|---|
| Page |
create, retrieve, update, retrievePropertyItem
|
| Block |
retrieve, update, delete, listChildren, appendChildren, getAllChildren, getAllChildrenRecursive, updateTodoChecked
|
| Database |
create, retrieve, update, query
|
| DataSource |
create, retrieve, update, query
|
| User |
me, retrieve, list
|
| Search |
search, searchPages, searchDatabases
|
| Library | Role |
|---|---|
| Ktor Client | HTTP with per-platform engines (CIO / OkHttp / Darwin / Js) |
| kotlinx.serialization | JSON serialization in commonMain
|
| kotlinx.coroutines | Structured concurrency, StateFlow for session state |
| kotlinx.datetime | Multiplatform date/time parsing |
| Koin | Dependency injection wiring |
The project has two test scopes:
commonTest — unit tests using ktor-client-mock, covering all repositories without hitting the real API.jvmTest live tests — integration tests (PageLiveTest, DatabaseLiveTest, BlockLiveTest, …) that run against a real Notion workspace when NOTION_TOKEN and related IDs are set.# Unit tests (all platforms)
./gradlew allTests
# JVM live tests (requires credentials in local.properties)
./gradlew jvmTestReleases are published to Maven Central automatically via GitHub Actions when a GitHub release is created:
.github/workflows/publish.yml
The workflow signs all artifacts with GPG and uploads via the vanniktech/gradle-maven-publish-plugin. Credentials are stored as GitHub repository secrets.
notion-sdk-kmp/
├── library/
│ └── src/
│ ├── commonMain/ # All SDK logic — repositories, services, models, auth
│ ├── androidMain/ # Ktor Android engine
│ ├── jvmMain/ # Ktor CIO engine
│ ├── jsMain/ # Ktor JS engine
│ ├── appleMain/ # Ktor Darwin engine (shared by iOS + macOS)
│ ├── commonTest/ # Mock-based unit tests
│ └── jvmTest/ # Live integration tests
└── sample/
├── NotionClientFromToken.kt # Basic API token usage
├── NotionClientFromProvider.kt # Custom TokenProvider
└── NotionClientFromOAuth.kt # Full OAuth flow with embedded server
A Kotlin Multiplatform SDK for the Notion API, published on Maven Central. Write your Notion integration once and run it on Android, iOS, JVM, JavaScript, macOS, and Linux — no code duplication, no platform-specific workarounds.
| Platform | Target |
|---|---|
| Android | androidTarget |
| iOS |
iosX64, iosArm64, iosSimulatorArm64
|
| JVM (Desktop / Server) | jvm |
| JavaScript (Browser) | js |
| macOS |
macosX64, macosArm64
|
| Linux | linuxX64 |
The library is structured in three layers, all written in commonMain:
NotionClient
├── Repository (public API — suspend functions, clean domain types)
│ └── Service (HTTP communication via Ktor)
│ └── NotionHttp (auth injection, base URL, headers)
└── DI modules (Koin — optional, wired automatically)
PageRepository, BlockRepository, …).@Serializable DTOs.Authorization header via a pluggable TokenProvider, keeping auth decoupled from transport.CIO on JVM, Android on Android, Darwin on Apple, Js on JS), so the common code never touches platform internals.Add the dependency to your build.gradle.kts:
commonMain.dependencies {
implementation("io.github.tungnk123:notion-sdk-kmp:1.1.11")
}val client = NotionClientFactory.fromToken(token = "secret_...")
// Retrieve a page
val page = client.pageRepository.retrieve(pageId)
// Query a database
val results = client.databaseRepository.query(
id = databaseId,
req = QueryDatabaseRequest(/* filters, sorts … */)
)
// Full-text search
val pages = client.searchRepository.searchPages(query = "Books")Useful when the token is stored in your own secure storage or refreshed dynamically:
class MyTokenProvider : TokenProvider {
override fun token(): String = MySecureStorage.read("notion_token")
}
val client = NotionClientFactory.fromTokenProvider(MyTokenProvider())val repo = AuthRepository(
service = AuthServiceImpl(httpClient),
clientId = "...",
clientSecret = "...",
storage = InMemoryTokenStorage()
)
// 1. Redirect the user
val authorizeUrl = repo.authorizeUrl(redirectUri, state, ownerWorkspace = false)
// 2. Handle the callback
val token = repo.exchangeCodeForTokenAndSaveToken(code, redirectUri)
// 3. Build the client
val client = NotionClientFactory.fromAuthRepository(repo)val manager = NotionSessionManager(authFactory, tokenStore)
// Observe auth state reactively
manager.state.collect { state ->
when (state) {
is SessionState.SignedOut -> showLoginScreen()
is SessionState.Authorizing -> openBrowser(state.intent.url)
is SessionState.Authorized -> showHome(state.session)
}
}
// Switch between workspaces
manager.switchWorkspace(workspaceId)
// Sign out
manager.disconnectWorkspace(workspaceId)| Resource | Operations |
|---|---|
| Page |
create, retrieve, update, retrievePropertyItem
|
| Block |
retrieve, update, delete, listChildren, appendChildren, getAllChildren, getAllChildrenRecursive, updateTodoChecked
|
| Database |
create, retrieve, update, query
|
| DataSource |
create, retrieve, update, query
|
| User |
me, retrieve, list
|
| Search |
search, searchPages, searchDatabases
|
| Library | Role |
|---|---|
| Ktor Client | HTTP with per-platform engines (CIO / OkHttp / Darwin / Js) |
| kotlinx.serialization | JSON serialization in commonMain
|
| kotlinx.coroutines | Structured concurrency, StateFlow for session state |
| kotlinx.datetime | Multiplatform date/time parsing |
| Koin | Dependency injection wiring |
The project has two test scopes:
commonTest — unit tests using ktor-client-mock, covering all repositories without hitting the real API.jvmTest live tests — integration tests (PageLiveTest, DatabaseLiveTest, BlockLiveTest, …) that run against a real Notion workspace when NOTION_TOKEN and related IDs are set.# Unit tests (all platforms)
./gradlew allTests
# JVM live tests (requires credentials in local.properties)
./gradlew jvmTestReleases are published to Maven Central automatically via GitHub Actions when a GitHub release is created:
.github/workflows/publish.yml
The workflow signs all artifacts with GPG and uploads via the vanniktech/gradle-maven-publish-plugin. Credentials are stored as GitHub repository secrets.
notion-sdk-kmp/
├── library/
│ └── src/
│ ├── commonMain/ # All SDK logic — repositories, services, models, auth
│ ├── androidMain/ # Ktor Android engine
│ ├── jvmMain/ # Ktor CIO engine
│ ├── jsMain/ # Ktor JS engine
│ ├── appleMain/ # Ktor Darwin engine (shared by iOS + macOS)
│ ├── commonTest/ # Mock-based unit tests
│ └── jvmTest/ # Live integration tests
└── sample/
├── NotionClientFromToken.kt # Basic API token usage
├── NotionClientFromProvider.kt # Custom TokenProvider
└── NotionClientFromOAuth.kt # Full OAuth flow with embedded server