MainSDK
Search⌘K
Integrations

Upstash

Use Upstash Redis for the MainSDK key-value store, cache and rate limits - setup, main.yaml, secrets, local testing and swapping.

1 interface1 driverWritten guide
Updated 2026-10-28

Upstash is serverless Redis reached over HTTP, and a driver behind two interfaces: kv, for small values and cached results, and limits, for rate limits, locks and quotas. Because it needs no long-lived connection, it fits serverless and edge functions where a classic Redis connection pool does not. Pick Upstash when you deploy on a serverless platform and want one store for your cache and your counters.

Interfaces it serves

Pick Upstash in main.yaml for any of these. Your calls stay the same, and you can swap later.

Before you start

You need an Upstash account.

  1. Create a Redis database. Choose a primary region close to where your app runs. Upstash can also replicate reads to more regions; use that only for data that may be copied to every region it lists.
  2. Copy the REST URL and token. Upstash shows them in the database's details. The token grants read and write access, so treat it as a secret.
  3. Decide on one database or two. You can share one database between kv and limits, or keep counters apart from cached data so a cache flush never resets a rate limit.

Store the URL and token in your secrets manager, never in code or a committed env file.

Configure

For the cache and key-value store:

main.yaml
kv:
  driver: upstash
  # token: secret:UPSTASH_REDIS_REST_TOKEN   # suggested name; key names are [to be decided] until the kv spec

For rate limits, locks and quotas:

main.yaml
limits:
  driver: upstash   # was builtin

How the limits driver gets its URL and token, and whether it can share the kv connection, are not decided yet. There is no default TTL for kv: you pass ttl on every write.

Use it

Cache a slow lookup:

lib/prices.ts
import { cache } from "mainsdk";

export async function pricesFor(market: string) {
  return cache.wrap(`prices:${market}`, () => loadPrices(market), {
    ttl: "5m",
    tags: ["prices"],
  });
}

Rate-limit an expensive endpoint:

app/api/export/route.ts
import { auth, limits } from "mainsdk";

export async function POST(req: Request) {
  const session = await auth.require(req);
  const ok = await limits.rate(`export:${session.user.id}`, { per: "1m", max: 5 });
  if (!ok) return new Response("Too many requests", { status: 429 });
  return Response.json({ started: true });
}

limits.lock(name, fn) runs fn on one worker at a time, and limits.quota(org, metric) reports where an organisation stands against its plan.

Local development and testing

  • No account: tests don't need Upstash. limits defaults to the builtin driver, so you can set Upstash in main.env.prod.yaml only. The local driver for kv is not named yet.
  • A real database: a separate Upstash database for development keeps test counters and cache out of production.
  • Tests: createTestApp() from @mainsdk/testing runs kv and limits on in-memory fakes. Use clock.advance() to pass a TTL or reset a rate window, and chaos.on("kv", { outage: true }) or chaos.on("limits", { outage: true }) to simulate an Upstash outage.
lib/export-limit.test.ts
import { it } from "node:test";
import assert from "node:assert/strict";
import { createTestApp, clock } from "@mainsdk/testing";
import { limits } from "mainsdk";

it("refuses the sixth export in a minute and allows it after", async () => {
  createTestApp({ main: "./main.yaml" });
  const rule = { per: "1m", max: 5 };
  for (let i = 0; i < 5; i++) assert.equal(await limits.rate("export:u1", rule), true);
  assert.equal(await limits.rate("export:u1", rule), false);
  clock.advance("1m");
  assert.equal(await limits.rate("export:u1", rule), true);
});

What MainSDK adds on top

  • kv keys scoped per tenant, a TTL on every entry, and no personal data in shared caches without a label.
  • Sign-in, sign-up, password reset and AI calls rate limited by default, whichever limits driver you pick.
  • Abuse and bot protection as part of limits.
  • The same calls on every driver, so you can move counters between the built-in driver and Upstash with one line.

See Key-value store and cache and Rate limits and quotas.

Limits and notes

  • upstash is a planned driver for kv and limits, both phase 1, and neither interface has a spec yet.
  • Pick database regions that match your data rules. Cached values that hold personal data follow the same residency rules as the records they came from (US, EU or India); see Data and regions.
  • Limit by user or organisation ID rather than by an email address, so personal data stays out of counter keys.
  • Use db, not kv, for data you must keep.

Swap to or from it

main.yaml
kv:
  driver: upstash   # was redis
limits:
  driver: upstash   # was builtin

Cached entries refill as they expire, and rate-limit counters start fresh on the new driver. No migration is needed.

FAQ

Upstash or plain Redis? Upstash suits serverless functions because it is reached over HTTP. Use Redis when you already run a server in your own network.

Do I need Upstash for rate limits? No. limits works on its builtin driver. Move to Upstash when you want counters shared across many instances in a store you control.

Can one Upstash database serve both kv and limits? Whether the two drivers share one connection is not decided yet; two databases always work.

Next

Open questions

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet