Documentation

$fetch

Make HTTP requests with automatic JSON parsing and disk caching.

Basic Usage

The simplest form - just a URL:

val data = $fetch("https://api.example.com/data")

HTTP Methods

GET

val shows = $fetch.get(
  url = "https://api.example.com/shows",
  headers = { Authorization: "Bearer $token" },
  params = { page: "1", limit: "20" }
)

POST

val response = $fetch.post(
  url = "https://api.example.com/login",
  body = { email: email, password: password }
)

PUT

$fetch.put(
  url = "https://api.example.com/profile",
  body = { name: name, bio: bio }
)

DELETE

$fetch.delete(
  url = "https://api.example.com/posts/$id",
  headers = { Authorization: "Bearer $token" }
)

Disk Caching

A GET with cache is kept on disk for that long, keyed by method, full URL and the Authorization header — one user's cached response is never served to another. Only GET is cached; clearing is all or nothing.

// Cache for 1 day
val show = $fetch.get(
  url = "https://api.example.com/show/$id",
  cache = "1d"
)

// Cache forever (static data)
val countries = $fetch.get(
  url = "https://api.example.com/countries",
  cache = "forever"
)

Supported TTL Formats

  • "30s" - 30 seconds
  • "5m" - 5 minutes
  • "1h" - 1 hour
  • "1d" - 1 day
  • "7d" - 7 days
  • "forever" - Never expires

Clear Cache

$fetch.clearCache()

This is a different cache from a route's. The route cache is in memory, per route, and survives rotation but not a restart. The fetch cache is on disk, per URL, and survives a restart — the place for data that is fine slightly stale, or truly static.

See it happen

The method and the cache option each change what gets generated. Flip between these to see it.

ShowFetch.wh
suspend fun loadShow(id: String): String {
  return $fetch("https://api.example.com/show/${id}")
}

<Text>Show</Text>
Kotlin waiting
Scroll into view to compile.

The plain form is a single call to Ktor’s httpClient.get, with a private HttpClient generated once at file scope.

In a Screen Loader

Use $fetch in data loaders for automatic loading states:

// +screen.load.wh
suspend fun load(): Show {
  val id = $screen.params.id
  return $fetch("https://api.example.com/shows/$id")
}

// +screen.wh
<Column>
  <Text>{$data.name}</Text>
  <Text>{$data.description}</Text>
</Column>

Error Handling

Use try-catch for error handling:

var error: String? = $state(null)
var data: Show? = $state(null)

suspend fun loadData() {
  try {
    data = $fetch("https://api.example.com/show/$id")
  } catch (e: Exception) {
    error = e.message
  }
}

$onMount { loadData() }

@if (error != null) {
  <Text color="#ef4444">Error: {error}</Text>
} else if (data != null) {
  <Text>{data.name}</Text>
}

$fetch automatically parses JSON responses and handles serialization for request bodies.

Type Safety

Define data classes for type-safe responses:

data class Show(
  val id: String,
  val name: String,
  val description: String,
  val posterUrl: String
)

val show: Show = $fetch("https://api.example.com/shows/$id")

See Also