diff --git a/src/content/docs/r2/examples/aws/aws-sdk-kotlin.mdx b/src/content/docs/r2/examples/aws/aws-sdk-kotlin.mdx
new file mode 100644
index 000000000000000..2041f9be0ced6d4
--- /dev/null
+++ b/src/content/docs/r2/examples/aws/aws-sdk-kotlin.mdx
@@ -0,0 +1,126 @@
+---
+title: aws-sdk-kotlin
+pcx_content_type: example
+---
+
+import { Render } from "~/components";
+
+
+
+
+This example uses the [aws-sdk-kotlin](https://github.com/aws/aws-sdk-kotlin). You must pass in the R2 configuration credentials when instantiating your `S3` client:
+
+## Basic Usage
+
+```kotlin
+import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider
+import aws.sdk.kotlin.services.s3.S3Client
+import aws.sdk.kotlin.services.s3.model.ListObjectsV2Request
+import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
+import aws.smithy.kotlin.runtime.net.url.Url
+import kotlinx.coroutines.runBlocking
+
+val ACCOUNT_ID = ""
+val ACCESS_KEY_ID = ""
+val SECRET_ACCESS_KEY = ""
+
+fun main() = runBlocking {
+ S3Client.fromEnvironment {
+ region = "auto" // Required by SDK, but not used by R2
+ endpointUrl = Url.parse("https://${ACCOUNT_ID}.r2.cloudflarestorage.com")
+ credentialsProvider = StaticCredentialsProvider(
+ Credentials(
+ accessKeyId = ACCESS_KEY_ID,
+ secretAccessKey = SECRET_ACCESS_KEY
+ ),
+ )
+ }.use { r2Client ->
+ println("Available buckets:")
+ r2Client.listBuckets().buckets?.forEach { bucket ->
+ println("* ${bucket.name}")
+ }
+
+ val bucketName = ""
+ println("\nObjects in bucket '${bucketName}':")
+ r2Client.listObjectsV2(ListObjectsV2Request { bucket = bucketName }).contents?.forEach {
+ println("* ${it.key} (size: ${it.size} bytes, modified: ${it.lastModified})")
+ }
+ }
+}
+```
+
+```sh output
+Available buckets:
+* my-bucket-1
+* my-bucket-2
+
+Objects in bucket 'my-bucket-1':
+* image1.png (size: 253167 bytes, modified: 2026-01-17T11:30:58.896Z)
+* image2.png (size: 247027 bytes, modified: 2026-01-17T11:30:57.779Z)
+```
+
+## Generate presigned URLs
+
+You can also generate presigned links that can be used to temporarily share public read or write access to a bucket.
+
+```kotlin
+import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider
+import aws.sdk.kotlin.services.s3.S3Client
+import aws.sdk.kotlin.services.s3.model.GetObjectRequest
+import aws.sdk.kotlin.services.s3.model.PutObjectRequest
+import aws.sdk.kotlin.services.s3.presigners.presignGetObject
+import aws.sdk.kotlin.services.s3.presigners.presignPutObject
+import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
+import aws.smithy.kotlin.runtime.net.url.Url
+import kotlinx.coroutines.runBlocking
+import kotlin.time.Duration.Companion.minutes
+
+val ACCOUNT_ID = ""
+val ACCESS_KEY_ID = ""
+val SECRET_ACCESS_KEY = ""
+
+fun main() = runBlocking {
+ S3Client.fromEnvironment {
+ region = "auto" // Required by SDK, but not used by R2
+ endpointUrl = Url.parse("https://${ACCOUNT_ID}.r2.cloudflarestorage.com")
+ credentialsProvider = StaticCredentialsProvider(
+ Credentials(
+ accessKeyId = ACCESS_KEY_ID,
+ secretAccessKey = SECRET_ACCESS_KEY
+ ),
+ )
+ }.use { r2Client ->
+ val bucketName = ""
+
+ val uploadUrl = r2Client.presignPutObject(
+ input = PutObjectRequest {
+ bucket = bucketName
+ key = "README.md"
+ },
+ duration = 15.minutes,
+ ).url
+ println(uploadUrl)
+
+ val getUrl = r2Client.presignGetObject(
+ input = GetObjectRequest {
+ bucket = bucketName
+ key = "README.md"
+ },
+ duration = 15.minutes,
+ ).url
+ println(getUrl)
+ }
+}
+```
+
+You can use these presigned URLs with any HTTP client. For example, to upload a file using the PUT URL:
+
+```bash
+curl -X PUT "https://" -H "Content-Type: application/octet-stream" --data-binary "@local-file.txt"
+```
+
+To download a file using the GET URL:
+
+```bash
+curl -X GET "https://" -o downloaded-file.txt
+```