RORK LABJP
iOS 27 — It ships tomorrow, September 14. The days right after a new OS lands are when AI-generated apps stand on the least stable groundSUBMISSIONS — App Store Connect has been accepting builds for iOS 27 and macOS 27 since September 10, and Xcode 27 is available as a release candidateAPRIL 2027 — From then on, anything uploaded to App Store Connect must be built with the iOS 27 or iPadOS 27 SDK. Worth checking early if you cannot pick your own SDK versionSWIFT 6 — Xcode 27's new build system defaults to Swift 6 mode, and iOS 27 ships with Swift 6.4. Existing projects may meet concurrency checks they have not met beforeAGE RATINGS — New age rating questions tied to features like Time Allowances now need answers. This one can hold up a submission, so handle it earlyWAITING — Standard Rork waits on Expo and React Native; Rork Max waits on Xcode and the SDK. What a tool waits for decides how fast it catches upiOS 27 — It ships tomorrow, September 14. The days right after a new OS lands are when AI-generated apps stand on the least stable groundSUBMISSIONS — App Store Connect has been accepting builds for iOS 27 and macOS 27 since September 10, and Xcode 27 is available as a release candidateAPRIL 2027 — From then on, anything uploaded to App Store Connect must be built with the iOS 27 or iPadOS 27 SDK. Worth checking early if you cannot pick your own SDK versionSWIFT 6 — Xcode 27's new build system defaults to Swift 6 mode, and iOS 27 ships with Swift 6.4. Existing projects may meet concurrency checks they have not met beforeAGE RATINGS — New age rating questions tied to features like Time Allowances now need answers. This one can hold up a submission, so handle it earlyWAITING — Standard Rork waits on Expo and React Native; Rork Max waits on Xcode and the SDK. What a tool waits for decides how fast it catches up
Articles/AI Models
AI Models/2026-09-13Intermediate

Is That API Key Sitting Inside Your App? Rork Environment Variables vs. Supabase Edge Functions

Rork gives you two places to put an API key: its own environment variables, or a secret inside a Supabase Edge Function. Here is how I decide between them based on what I can revoke and who gets the bill, plus three checks to run after the key is in place.

Rork562API keys2Supabase34Edge Functions3Environment variables

I was rotating a key for an external service used by one of my wallpaper apps late one night, and I stopped halfway through. The new key was already issued. What I could not answer was when it would be safe to kill the old one.

The reason was simple enough. I could not remember which apps that key was in, or at which layer. If it only lives on a server, you disable the old one the moment you swap it. If it shipped inside an installed app, you cannot disable it until the update reaches everyone.

Builders starting out with Rork ask me a version of the same question. "Where do I put my OpenAI key?" The real question underneath is not the name of a storage location. It is whether you can revoke it later, on your own schedule.

Rork's documentation lists two ways to connect an external API: Rork's own environment variables, or a secret registered inside a Supabase Edge Function. Neither is the correct answer. They simply differ in how revocation behaves.

A key you shipped does not stop on your schedule

Rork Pro exports a React Native and Expo project. Expo has its own environment variable system, and any name prefixed with EXPO_PUBLIC_ gets inlined directly into your code at build time.

Once inlined, that value stays in the app after you ship it. This is not encryption. It is a string that anyone holding the binary can read.

The practical consequence shows up when you rotate. A key that lives on a server is swapped in a dashboard, the old one is disabled, and you are done. A key baked into the app requires a new build, a review pass, and enough time for users to update — and the old key has to stay alive that entire time.

As an indie developer shipping to the store, I get reminded every release that review time is the one part of the schedule I cannot compress. Key incidents keep moving while you wait.

Rork points you at two places

The first is Rork's environment variables. The docs describe them as Rork's native support for APIs without needing to connect to a backend like Supabase. You click add variable, give it a name that describes the API, paste the key as the value, and then prompt Rork to use the edge function.

The second is a Supabase Edge Function. You connect Supabase first: copy the Publishable key and the Project URL from your Supabase project, paste them into the chat with "Connect Supabase," then open Edge Functions, add a secret with a name and your API key as its value, and read it by name from inside the function.

One line is worth drawing here, because this is where confusion starts. The Publishable key and the Project URL are values that belong in the app. What actually protects your data is row level security on each table, not the secrecy of those two strings. Treating them as secrets tends to distort every later decision about keys that genuinely must stay hidden.

There is also Rork Backend. Rork's FAQ describes it as serverless functions that securely call third-party APIs, noting that your API keys stay on the server, never in the app bundle, and that Rork hosts the backend for paid users. Read the other way around: on the free plan, that server-side landing spot does not exist yet. If you are in a hurry to wire up an API at that stage, the only place left for the key is inside the app.

One more thing worth checking before you decide anything. The FAQ also says that on paid plans Rork hosts the AI for chat, voice transcription, and image generation, so no API keys are needed for those. It is worth asking whether you need to ship your own key at all before you decide where to put it.

Where the key livesWhere it ends upWhat rotation looks likeRequires
Inside the app (e.g. EXPO_PUBLIC_)Every user's deviceNew build, review, staged updateOnly for values meant to be public
Rork env vars + Rork BackendServer (hosted by Rork)Re-register the value, effective immediatelyPaid plan
Supabase Edge Function secretServer (your own Supabase)Update the secret, redeploy the functionYour Supabase account and billing
Rork-hosted AI featuresYou hold no key at allNot applicablePaid plan, and a matching use case

My dividing line is who receives the bill

"Is this secure enough" is not a question you can answer reliably when you are new to this. I sort keys by who pays when they get abused instead.

The first group is values meant to be public. Supabase's Publishable key and Project URL belong here, and they are fine inside the app — provided row level security is actually configured. Ship them without it and your tables are readable from the outside.

The second group is metered keys: generative AI, maps, translation, speech. This is where it hurts, because a leaked key gets spent by someone else and the invoice arrives at your address. If the bill lands on my desk, the key only goes somewhere I can shut off myself.

The third group is admin-level credentials — anything equivalent to Supabase's service_role, or a payment provider's secret key. Those stay server-side, and specifically in a place only a function can read. The sites I run sit on Cloudflare Workers, and the payment secrets live only in the Worker's own configuration, out of reach of anything that renders a page.

Moving the second group server-side buys you something extra: you can now count usage, because every call passes through a function you own. Once you are there, the ceilings and logging described in How I Cut My Rork App's AI Costs from $350 to $35/Month with Cloudflare AI Gateway apply directly.

The smallest useful Edge Function

Once the decision is made, here is the concrete piece. The device calls only this function, and the provider key is read inside the function alone. In one sentence: this is a relay that returns the API result without ever handing the key to the device.

// supabase/functions/summarize/index.ts
// The device calls only this function; the provider key is read here and never returned
const API_KEY = Deno.env.get("PROVIDER_API_KEY"); // the name you registered in Supabase Secrets
 
const CORS = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, content-type",
};
 
function json(body: unknown, status: number) {
  return new Response(JSON.stringify(body), {
    status,
    headers: { ...CORS, "Content-Type": "application/json" },
  });
}
 
Deno.serve(async (req) => {
  if (req.method === "OPTIONS") return new Response("ok", { headers: CORS });
 
  // A missing key is a configuration gap, not a crash. Saying so shortens the diagnosis
  if (!API_KEY) return json({ error: "PROVIDER_API_KEY is not set" }, 500);
 
  let text = "";
  try {
    ({ text } = await req.json());
  } catch {
    return json({ error: "invalid JSON body" }, 400);
  }
 
  if (typeof text !== "string" || text.length === 0) return json({ error: "text is required" }, 400);
  // This limit blocks abuse, and it is also the ceiling on your invoice
  if (text.length > 4000) return json({ error: "text is too long" }, 413);
 
  const res = await fetch("https://api.example-provider.com/v1/summarize", {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ text }),
  });
 
  // Never pass the upstream body through as-is; it can carry key or account details
  if (!res.ok) return json({ error: "upstream failed", status: res.status }, 502);
 
  const data = await res.json();
  return json({ summary: data.summary }, 200);
});

A successful call returns a single line, {"summary":"..."}. When it fails you get either {"error":"PROVIDER_API_KEY is not set"}, meaning the secret was never registered, or {"error":"upstream failed","status":401}, meaning the key itself is wrong. Those two messages separate the two most common causes without any further digging.

A note on why it is shaped this way. Reading the key in exactly one place means you have one spot to touch when you rotate. The length check sits at the entrance because it caps abuse and caps spending at the same time. And withholding the upstream body keeps stray details out of whatever you hand back to the device.

After deploying, hand the endpoint to Rork in chat.

Use this Edge Function to summarize the text the user enters.
The endpoint is https://<project-ref>.supabase.co/functions/v1/summarize
Send the body as JSON in a "text" field and display the "summary" from the response.

The documentation itself warns that this route takes trial and error: data that will not parse, functions that keep failing, a mistyped key. Expecting the first attempt to fail makes the process much easier to sit through. Early on I kept assuming my architecture was wrong every time a call failed. Almost always it was a typo, or a function I had edited but never redeployed.

Three checks to run once the key is in place

Deciding where a key goes is not the same as confirming where it landed. Look for yourself, once.

First, search the exported code. On paid plans you can sync it both ways through the GitHub integration, so pull it down and look.

# Look for the key itself, or names that suggest a key was pasted in
grep -rniE "api[_-]?key|secret|token" . --include="*.ts" --include="*.tsx" | grep -v "YOUR_API_KEY"
 
# Count the names that reach the device. A sensitive key showing up here is in the wrong place
grep -rn "EXPO_PUBLIC_" . --include="*.ts" --include="*.tsx" --include="*.env*"

Second, search by value rather than by name. Take the first few characters of the key and confirm you get zero hits. Renaming a variable hides nothing if the value is still sitting there.

Third — and this is the one I trust most — disable a test key and watch what breaks. If the function is the only thing holding the key, the function returns an error and the screen simply shows no summary. A device calling the provider directly fails too, so what you are really reading is where the failure was recorded. Failures in your function logs mean the key lives in the function. Nothing in the logs, with only the device failing, means the key is on the device.

I worked through a similar split between device and server in The Three-Minute Video That Kept Failing — Moving Rork's Gemini Uploads Off the Worker. Heavy data and sensitive keys pull in different directions, and they deserve different answers.

One thing to do today

Open one project you are actively working on and write down how many external service keys it uses. Once you know the count, going through them one at a time and asking "does the bill for this one land on me?" settles the placement question on its own.

I started doing this inventory first only after that night when rotation stopped me in my tracks. Thank you for reading this far.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

AI Models2026-06-17
Making Credits Add Up in a Rork AI Image App — Field Notes on Atomic Ledgers and Moderation
Credit billing in a Rork AI image and video app breaks in production because of the order between generation and deduction. Here are the field notes — atomic ledger consumption, idempotency, refunds on failure, and moderation — with Supabase code you can ship.
AI Models2026-05-06
7 Real Challenges When Building a Tarot App with Rork (And How to Solve Them)
A practical guide to building a daily tarot and oracle card app with Rork. Covers card data architecture, AI interpretation generation, daily reset logic, monetization design, and App Store review pitfalls specific to fortune-telling apps.
AI Models2026-05-05
Build an AI-Powered Certification Exam App with Rork: Adaptive Learning That Targets Your Weak Spots
Build a certification exam prep app with Rork and Gemini API. Learn to implement adaptive quiz logic, AI-driven weakness analysis, and Supabase-backed progress tracking — from first prompt to App Store.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links