MainSDK
Search⌘K
Integrations

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.

4 interfaces4 driversWritten guide
Updated 2026-10-28

getqr.id is the upload and link service behind four MainSDK interfaces: files for uploads (its default driver), share for private, expiring links and QR codes, links for links that open the right screen in your app, and 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, Uploadthing or Vercel Blob); uploads still go through files (ADR-0013).

Interfaces it serves

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

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 install @getqr/react

Configure

Uploads

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 has no driver block; it uses getqr.id with the key from files.

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.

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.

Images and video

main.yaml
media:
  driver: getqr                  # the default

Output options beyond width and supported formats arrive with the media spec.

Use it

Upload a file

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.

Share a record

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".

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

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.
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");
  // [to be decided] 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 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, Share links and QR codes, App links and Images and video.

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. 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.
  • Links are on getqr.id; your own short domain is not in the spec.
  • 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):

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.

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

Open questions

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet