---
title: "getqr.id"
description: "Use getqr.id as the default MainSDK files driver and for share links, QR codes, app links and media, with the API key, main.yaml, @getqr/react and tests."
section: "integrations"
canonical: "https://mainsdk.dev/docs/integrations/getqr-id"
updated: "2026-10-28"
---

# getqr.id

> Use getqr.id as the default MainSDK files driver and for share links, QR codes, app links and media, with the API key, main.yaml, @getqr/react and tests.

Canonical: https://mainsdk.dev/docs/integrations/getqr-id · Updated: 2026-10-28

getqr.id is the upload and link service behind four MainSDK interfaces: [`files`](https://mainsdk.dev/docs/interfaces/files) for uploads (its default driver), [`share`](https://mainsdk.dev/docs/interfaces/share) for private, expiring links and QR codes, [`links`](https://mainsdk.dev/docs/interfaces/links) for links that open the right screen in your app, and [`media`](https://mainsdk.dev/docs/interfaces/media) for resized images and transcoded video. MainSDK uses it through the public `@getqr/sdk` and its OpenAPI contract, like any outside developer, and the UI comes from `@getqr/react`. It is the default `files` driver, and the only layer for share links, links and QR codes: every share link in a MainSDK app goes through it (ADR-0009), and `mainsdk check` fails if your app uses another sharer or shortener. For uploads you can choose another `files` driver ([Amazon S3](https://mainsdk.dev/docs/integrations/amazon-s3), [Uploadthing](https://mainsdk.dev/docs/integrations/uploadthing) or [Vercel Blob](https://mainsdk.dev/docs/integrations/vercel-blob)); uploads still go through `files` (ADR-0013).

## Interfaces it serves

### File uploads (`files`)

- Status: Building for 0.3 · ships as a stable API
- Driver: `getqr` (getqr.id, default)
- Docs: https://mainsdk.dev/docs/interfaces/files.md

```yaml
files:
  driver: getqr
```

Swap to another provider: `s3`, `uploadthing`, `vercel-blob`, `local`.

### Share links and QR codes (`share`)

- Status: Building for 0.3 · ships as a stable API
- Driver: `getqr` (getqr.id, default)
- Docs: https://mainsdk.dev/docs/interfaces/share.md

```yaml
share:
  driver: getqr
```

Swap to another provider: `local`.

### Invite, deep and sign-in links (`links`)

- Status: Building for 0.4 · ships as a beta API
- Driver: `getqr` (getqr.id, default). links uses the share driver
- Docs: https://mainsdk.dev/docs/interfaces/links.md

```yaml
links:
  driver: getqr
```

Swap to another provider: `local`.

### Images, video and audio (`media`)

- Status: Building for 0.4 · ships as a beta API
- Driver: `getqr` (getqr.id, default)
- Docs: https://mainsdk.dev/docs/interfaces/media.md

```yaml
media:
  driver: getqr
```

Swap to another provider: `mux`, `cloudinary`, `ffmpeg`, `local`.

## Before you start

1. **Create a getqr.id account** for your app.
2. **Create an API key.** Use a sandbox key for development and stage and a production key for production; the files conformance suite runs against getqr.id's sandbox.
3. **Install the packages** you use directly: `@getqr/react` for the `<Upload/>`, `<ShareBar/>` and `<QRCode/>` components. `mainsdk` uses `@getqr/sdk` for you.

Store the key in your secrets manager, never in code or a committed env file.

npm:

```bash
npm install @getqr/react
```

pnpm:

```bash
pnpm add @getqr/react
```


## Configure

### Uploads

```yaml title="main.yaml"
files:
  driver: getqr
  key: secret:GETQR_API_KEY      # suggested name; the getqr.id API key
  scan: true                     # scan every upload before it becomes readable
  max-size: 25MB                  # largest file accepted
```

A literal key fails validation with `MAIN-CONFIG-0004`.

### Share links and QR codes

`share` has no driver block; it uses getqr.id with the key from `files`.

```yaml title="main.yaml"
share:
  default-expiry: 7d             # expiry when a call passes no expiresIn
  max-expiry: 90d                # longest expiry a call may ask for
```

Which key `share` uses is not stated in the spec; the docs assume the `files` key [TBD].

### App links

```yaml title="main.yaml"
links:
  driver: getqr                  # the only driver
```

Which key `links` uses and how screens map to web routes and native screens are not decided yet [TBD].

### Images and video

```yaml title="main.yaml"
media:
  driver: getqr                  # the default
```

Output options beyond `width` and supported formats arrive with the `media` spec [TBD].

## Use it

### Upload a file

```ts title="app/api/avatar/route.ts"
import { auth, files } from "mainsdk";

export async function POST(req: Request) {
  const session = await auth.require(req);
  const file = (await req.formData()).get("file") as File;

  const ref = await files.upload(file, { owner: session.user, label: "personal" });
  const url = await files.url(ref, { for: session.user, expiresIn: "1h" });
  return Response.json({ ref, url });
}
```

On the browser side, `<Upload/>` from `@getqr/react` handles resumable parts and shows "Powered by getqr.id". Its props come from `@getqr/react` [TBD].

### Share a record

```ts title="app/orders/[id]/actions.ts"
"use server";
import { auth, db, share } from "mainsdk";

export async function shareOrder(req: Request, orderId: string) {
  const session = await auth.require(req);
  const order = await db.orders.for(session.user).get(orderId);
  const { url, qr, id } = await share(order, { expiresIn: "7d", access: "read" });
  return { url, qr, id };   // url is on getqr.id
}
```

Render it with `<ShareBar/>` and `<QRCode/>` from `@getqr/react`; both show "Powered by getqr.id".

### Link to a screen

```ts title="app/orders/notify-shipped.ts"
import { links } from "mainsdk";

export async function orderLink(orderId: string) {
  return links.create({ screen: "order", params: { id: orderId } });
}
```

### Resize an image

```ts title="lib/avatar.ts"
import { media } from "mainsdk/media";

export async function thumbnail(photoId: string) {
  return media.image(photoId, { width: 320 });
}
```

## Local development and testing

- **Uploads without an account:** set `files.driver: local` in `main.env.dev.yaml` to keep files on disk while you build. It passes the same files conformance suite as getqr.id.
- **getqr.id sandbox:** use a sandbox key to try real uploads, links and QR codes before production.
- **Tests:** `createTestApp()` from `@mainsdk/testing` runs `files`, `share` and `links` on in-memory fakes, with owner scope and scanning enforced. `clock.advance()` expires signed URLs and share links without waiting. `media` ships in phase 2 and its fake arrives with its spec.
- **Contract tests:** `tools/check fast` replays 20 recorded getqr.id calls offline and checks every request and answer against the pinned `openapi.yaml`. Refresh them with `mainsdk contract record --vendor getqr`: with a `gq_test_` key in `GETQR_SANDBOX_KEY` it records the sandbox, without one it builds them from the contract's examples. `tools/check stage` calls the sandbox itself and fails without the key.
- **Outages:** `chaos.on("files", { outage: true })` simulates getqr.id being unreachable for uploads.

```ts title="app/orders/share.test.ts"
import { it } from "node:test";
import assert from "node:assert/strict";
import { createTestApp, clock } from "@mainsdk/testing";
import { share } from "mainsdk";

it("files-share.AC5 makes a getqr.id link that expires", async () => {
  createTestApp({ main: "./main.yaml" });
  const { url } = await share({ id: "ord_1" }, { expiresIn: "7d" });
  assert.equal(new URL(url).host, "getqr.id");
  clock.advance("8d");
  // [TBD] the fake's helper for resolving a link and reading its state
});
```

## What MainSDK adds on top

- Files readable only by their owner or whom [`access`](https://mainsdk.dev/docs/interfaces/access) allows (`MAIN-FILES-0002`, 403 otherwise), and scanned before they become readable (`MAIN-FILES-0005` for an infected file).
- Resumable, idempotent uploads, so a retry never stores a file twice.
- Share links private by default, expiring and read-only, with no personal data in the URL; open counts only with consent.
- Every share sheet, QR code and link page shows "Powered by getqr.id".
- Universal Links and App Links set up for you, and location data stripped from media.

See [File uploads](https://mainsdk.dev/docs/interfaces/files), [Share links and QR codes](https://mainsdk.dev/docs/interfaces/share), [App links](https://mainsdk.dev/docs/interfaces/links) and [Images and video](https://mainsdk.dev/docs/interfaces/media).

## Limits and notes

- `files` and `share` come with the `files-share` spec (phase 1, milestone 0.3). `links` and `media` have no spec yet, so their pages are provisional; `media` is phase 2.
- Sharing depends on getqr.id being available; uploads and links fail while it is down. Your app itself keeps serving.
- Whether getqr.id or the SDK scans files is an open question in the spec [TBD]. Either way, a file is never readable before the scan passes.
- Which region getqr.id stores files in, and how that maps to MainSDK's US, EU and India regions, is not stated in the spec [TBD].
- Links are on `getqr.id`; your own short domain is not in the spec [TBD].
- Sign-in QR codes may be drawn with `<QRCode/>`, but their tokens never become getqr.id links.
- `files.raw()` returns the `@getqr/sdk` client for features MainSDK does not wrap. `mainsdk check` lists every use.

## Swap to or from it

`share` and `links` have one driver, getqr.id, by design. For `files`, getqr.id is the default; the others are `s3`, `uploadthing`, `vercel-blob` and `local` (disk, for development and tests):

```yaml title="main.env.dev.yaml"
files:
  driver: local   # was getqr
```

`media` lists Cloudinary and Mux as planned drivers. They work on files from any `files` driver (ADR-0013); whether they stay is not decided, because `media` has no spec.

> **Note:** Files uploaded with the getqr.id driver live in getqr.id. Changing `files.driver` does not move them; the migration path between stores is not defined yet [TBD]. Share links keep working for files that stay in getqr.id.

## FAQ

**Can I keep using S3 or Vercel Blob for user uploads?** Yes, as the `files` driver. `mainsdk check` fails with `MAIN-FILES-0010` only when route code imports their SDK instead of going through `files`.

**Can a share link let someone edit?** No. Links grant read-only access, and a write through one is refused with 403.

**Why "Powered by getqr.id" on every share sheet?** Every share in a MainSDK app goes through getqr.id, and the components show it so people know where the link lives.

## Next

- [File uploads](https://mainsdk.dev/docs/interfaces/files), [Share links and QR codes](https://mainsdk.dev/docs/interfaces/share), [App links](https://mainsdk.dev/docs/interfaces/links) and [Images and video](https://mainsdk.dev/docs/interfaces/media)
- [Cloudinary](https://mainsdk.dev/docs/integrations/cloudinary) and [Mux](https://mainsdk.dev/docs/integrations/mux)
- [Built in](https://mainsdk.dev/docs/integrations/built-in)
