RORK LABJP
PRICING — Standard Rork's tiers are now clear: Junior at $25 a month for 100 credits, Middle at $50 for 250, and Senior at $100 for 500CREDITS — Billing is per message. One prompt to the AI costs one credit, whether you are asking it to build an entire screen or just to change a button colorPRACTICE — Which means credits drain fastest when you iterate in small nudges. Bundle the requirements, then handle the fine details in your own editor. That split moves the real cost more than anythingSTACK — Standard Rork generates React Native through Expo and reaches both iOS and Android. Rork Max is a separate product that writes native Swift insteadMAX — Max starts at $200 a month. It compiles on a cloud Mac fleet, streams a live simulator to your browser, and publishes to the App Store in two clicks, with no Xcode requiredFOUNDATION — In the model layer beneath it, Gemini 3.8 Flash went generally available on September 2 and reached GitHub Copilot on September 3. Its introductory pricing expires December 31PRICING — Standard Rork's tiers are now clear: Junior at $25 a month for 100 credits, Middle at $50 for 250, and Senior at $100 for 500CREDITS — Billing is per message. One prompt to the AI costs one credit, whether you are asking it to build an entire screen or just to change a button colorPRACTICE — Which means credits drain fastest when you iterate in small nudges. Bundle the requirements, then handle the fine details in your own editor. That split moves the real cost more than anythingSTACK — Standard Rork generates React Native through Expo and reaches both iOS and Android. Rork Max is a separate product that writes native Swift insteadMAX — Max starts at $200 a month. It compiles on a cloud Mac fleet, streams a live simulator to your browser, and publishes to the App Store in two clicks, with no Xcode requiredFOUNDATION — In the model layer beneath it, Gemini 3.8 Flash went generally available on September 2 and reached GitHub Copilot on September 3. Its introductory pricing expires December 31
Articles/Dev Tools
Dev Tools/2026-09-06Intermediate

There is no --json on eas env:list, so I rebuilt my environment variable inventory

eas env:list does not accept a --json flag. Here is how I turn --format short output into safe JSON, and how I spot a missing variable across three environments in one line.

EAS8Expo201environment variables4CI7

One evening, before pushing a preview build, I had the EAS dashboard open and I was counting environment variables by eye. Three environments — development, preview, production — with project scope and account scope layered on top. After a few round trips through the interface, I had lost track of which ones I had already checked.

So I tried to stop counting by hand, and typed eas env:list --json. It did not work.

The first thing I want to say is that this is not a typo on your side. eas env:list has no --json flag. If you have stopped at the same place, here is what to use instead, and how to handle the output without quietly breaking something.

eas env:list accepts five flags

These are the flags defined in the eas-cli v22.0.0 source, in packages/eas-cli/src/commands/env/list.ts.

FlagDefaultWhat it does
--environment(prompted)Target environment. Can be passed more than once
--formatshortOutput format. The only choices are long and short
--scopeprojectProject-wide or account-wide variables
--include-sensitivefalsePrint sensitive values instead of masking them
--include-file-contentfalsePrint the contents of file variables

You can also pass the environment as a plain argument, as in eas env:list production. The format choices are long and short, and JSON is not among them, so --json is simply treated as a flag that does not exist.

I took a longer route than I needed to here. Looking at the dashboard table, I assumed there had to be a JSON export, and I guessed at three option names before I checked the flag list. Reading static override flags in the source first would have taken me a minute.

Why --format short is not a .env file

--format short prints one NAME=value per line, which looks close enough to a .env file that reusing it feels safe. Three things get in the way.

  • A heading line for the environment comes first, in the shape of Environment: production
  • An environment with no variables returns the single sentence No variables found for this environment.
  • Variable names are printed in bold, so depending on the terminal you may get styling control characters mixed in

The harder problem is that some values cannot be read at all. A variable whose visibility is sensitive stays masked unless you pass --include-sensitive, and a secret variable never leaves the EAS servers. Write a mask back as if it were a value, and the mask overwrites the real thing.

How the visibility levels relate to the EXPO_PUBLIC_ prefix is a separate axis, and I have written that up in EAS secrets are not a "keep it out of the app" setting. For this piece, one fact is enough: readable and unreadable values arrive in exactly the same line shape.

About thirty lines to turn it into JSON

Drop the heading lines, strip the control characters, and split on the first = only. Anything that is nothing but a mask gets readable: false, so the record keeps the fact that it is a marker rather than a value.

#!/usr/bin/env python3
# eas env:list --format short --environment production | python3 eas_env_to_json.py production
import json
import re
import sys
 
ANSI = re.compile(r"\x1b\[[0-9;]*m")
HEADER = re.compile(r"^(Environment|Variables for this project|Account-wide variables)\b")
MASKED = re.compile(r"^\*+(\s*\(.*\))?$")
 
 
def parse(lines):
    out = []
    for raw in lines:
        line = ANSI.sub("", raw).rstrip("\n").strip()
        if not line or HEADER.match(line):
            continue
        if line.startswith("No variables found"):
            continue
        if "=" not in line:
            continue
        name, value = line.split("=", 1)          # keep values that contain "="
        name = name.strip()
        if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name):
            continue                              # drop styling leftovers and prose lines
        out.append({
            "name": name,
            "value": value,
            "readable": not MASKED.match(value.strip()),
        })
    return out
 
 
if __name__ == "__main__":
    env = sys.argv[1] if len(sys.argv) > 1 else None
    json.dump(
        {"environment": env, "variables": parse(sys.stdin.readlines())},
        sys.stdout, ensure_ascii=False, indent=2,
    )
    print()

Here is the result of running it against input that reproduces the short format emitted by the v22.0.0 source.

{
  "environment": "production",
  "variables": [
    { "name": "APP_VARIANT", "value": "production", "readable": true },
    { "name": "EXPO_PUBLIC_API_URL", "value": "https://api.example.com", "readable": true },
    { "name": "SENTRY_AUTH_TOKEN", "value": "*****", "readable": false }
  ]
}

An environment with no variables produces an empty variables array rather than an error. A Base64 value with = inside it survives, because only the first = is used as the separator.

Line up three environments and the gap shows in one row

Generate one JSON file per environment, then line them up.

#!/usr/bin/env python3
# python3 eas_env_matrix.py env-development.json env-preview.json env-production.json
import json
import sys
 
envs, table = [], {}
for path in sys.argv[1:]:
    with open(path, encoding="utf-8") as f:
        data = json.load(f)
    env = data.get("environment") or path
    envs.append(env)
    for v in data["variables"]:
        table.setdefault(v["name"], {})[env] = "secret" if not v["readable"] else "ok"
 
missing = 0
print("NAME".ljust(28) + "".join(e.ljust(14) for e in envs))
for name in sorted(table):
    row = [table[name].get(e, "-") for e in envs]
    missing += row.count("-")
    print(name.ljust(28) + "".join(c.ljust(14) for c in row))
print(f"\nmissing cells: {missing}")
sys.exit(1 if missing else 0)      # fail the job when something is missing

Running it locally gives this.

NAME                        development   preview       production
APP_VARIANT                 ok            ok            ok
EXPO_PUBLIC_API_URL         ok            ok            ok
SENTRY_AUTH_TOKEN           -             secret        secret
 
missing cells: 1

The - is the gap. Since I moved to this shape, I no longer bounce between dashboard tabs. It exits with 1, so dropping it into a pre-build job also stops a preview from going out with a variable that was never added.

What I had confused was "cannot read" with "not there"

For a while I counted masked rows as variables I had not created yet. I went to add one that I thought was missing, and nearly overwrote the production value with a fresh one. The mechanism was not at fault. My reading of it was.

A mask means the value cannot be read, not that the value is absent. That one distinction I check first, even on days when I am in a hurry.

The same care applies to eas env:pull --environment production. It writes a .env.local file, but secret variables come out as commented lines rather than values. Judge by the shape of the line alone and you may recreate a value that is sitting safely on the server already.

This kind of inventory also pays off before an SDK bump. I traced where native configuration quietly disappears in Before upgrading to Expo SDK 57, find the native changes prebuild will erase, if that is where you are heading next.

For now, dump production to JSON once, stamp it with a date, and keep it. Diff it against the same output next week, and you will have your own record of what was added and when. That is where I started.

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

Dev Tools2026-09-04
EAS secret visibility does not keep a value out of your app — deciding prefix and visibility separately
The EXPO_PUBLIC_ prefix decides what ships inside your app; EAS visibility decides who can read it. Why stacking them blanks a value on OTA updates, and how to check your build.
Dev Tools2026-09-02
I counted how many versions expo install starts recommending after one patch bump
What expo install recommends is tied to the patch version of expo itself. I measured the ledger diff against the npm registry, how far npm install drifts, and the one check worth adding to a generated project.
Dev Tools2026-07-30
What Renovate may bump in an Expo app, and what it must never touch
Turning on automated dependency updates in a Rork-generated app also hands Renovate the 123 packages Expo SDK 57 pins. Measured on 2026-07-30, six of them sit a full major version behind npm latest. Here is how to generate the ignore list from the SDK instead of maintaining it by hand.
📚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
See all →