RORK LABJP
EVENT — Apple holds its Surprise and Shine event today, September 9, starting at 10:00 Pacific. That lands in the small hours of September 10 in JapanEXPECT — Expected are the iPhone 18 Pro and Pro Max, a foldable, the 2nm A20 Pro chip, and release dates for iOS 27 and its sibling updatesWAIT — As this is written the event has not happened yet. Rumor-stage writing and post-announcement writing look identical once they are mixed togetherMAX — Since Rork Max generates native Swift, Apple news is not somebody else's problem. Worth repeating that the standard product still writes React NativeSIMULATOR — Rork Max compiles on cloud Macs and lets you check the result in a streaming iOS simulator inside the browser, with no Xcode and no Mac hardwareSEASON — A new OS is when automated build pipelines wobble most. An article selling convenience owes its readers a word about that wobbleEVENT — Apple holds its Surprise and Shine event today, September 9, starting at 10:00 Pacific. That lands in the small hours of September 10 in JapanEXPECT — Expected are the iPhone 18 Pro and Pro Max, a foldable, the 2nm A20 Pro chip, and release dates for iOS 27 and its sibling updatesWAIT — As this is written the event has not happened yet. Rumor-stage writing and post-announcement writing look identical once they are mixed togetherMAX — Since Rork Max generates native Swift, Apple news is not somebody else's problem. Worth repeating that the standard product still writes React NativeSIMULATOR — Rork Max compiles on cloud Macs and lets you check the result in a streaming iOS simulator inside the browser, with no Xcode and no Mac hardwareSEASON — A new OS is when automated build pipelines wobble most. An article selling convenience owes its readers a word about that wobble
Articles/App Dev
App Dev/2026-09-09Beginner

Those 14 Days of Closed Testing Are for Gathering What the Production Form Asks

Google Play production access starts from a closed test with 12 testers opted in continuously for 14 days. Here is how the count actually works, the order the tracks open in, what the application form asks, and the records worth keeping during those two weeks.

Google Play34closed testingproduction accessEAS Submit2Rork558

The morning after I put a wallpaper app update on a staged rollout, I sat looking at the track list in Play Console. Internal, Closed, Open, Production — for me it is just the order I walk through every time.

The same screen means something else to someone who opened their account recently. The Production page is sitting right there, and it cannot be clicked.

Let me say this up front. My own account predates November 13, 2023, so this requirement does not apply to it. I cannot tell you a story about the struggle of recruiting twelve testers. What I can write about is the counting and the record-keeping, seen from someone who runs the same closed track for staged rollouts on every release.

The greyed-out button has nothing to do with how good your app is

Google Play requires personal developer accounts created after November 13, 2023 to test before an app is eligible for distribution. You need a closed test with at least 12 testers who have been opted in continuously for at least 14 days, and then you apply for production access from the Dashboard in Play Console.

Until you meet that, the Production and Pre-registration pages stay disabled. Organization accounts, and personal accounts created before November 13, 2023, are exempt. And the requirement applies per app. The wording lives in App testing requirements for new personal developer accounts (Play Console Help).

For a long stretch I read every store-side blocker as a problem with the app itself. That habit cost me evenings. When the build uploads fine and the button is still grey, the thing to fix is not in the code.

Getting through review and unlocking the right to distribute are two separate tracks of work. They sit on the same screen, which makes them easy to conflate, and rushing one does not move the other.

What is counted is not headcount, it is the unbroken stretch of days

This is where most people misread the rule. It is not "how many people have joined at some point." It is how many testers were opted in for the whole 14 days immediately before you apply.

The official FAQ is direct about it: someone who opts in, stays for fewer than 14 days, and opts out does not count, and if a tester opts out and comes back, the 14 days have to be consecutive to count. So a departure is not "one fewer tester." It resets that person's clock to zero.

Which means running with exactly twelve is thin ice. One person turns off notifications, deletes the app, and drops out of the test — and your application date slides two weeks. I would invite more than twelve, so the number that remains after a couple of departures is still enough.

There is one more thing. After you apply, Google may ask you to keep testing if fewer than twelve testers were opted in, or if tester engagement during the period was insufficient. Getting the shape of the number right does not carry you past that part.

Open the tracks in order, or the 14 days never start

Each track has a different job, and they unlock in a fixed order.

TrackWhat it is forWhat it takes to use
Internal testingPush a build to a handful of trusted people, right awayNothing. Works before app setup is complete
Closed testingShare with a group you choose. This is where the 14 days are countedCompleted app setup
Open testingA public test anyone can joinProduction access already granted
ProductionAvailable to everyone on Google PlayMeet the closed-test criteria, apply, and be approved

If you were planning to open the Open testing track first and recruit from there, that page will not open until production access exists. The order is Internal, then Closed, then the application, then Production.

In practice this sequence keeps things calm:

  1. Ship to Internal testing and confirm launch plus the main screens on your own device and a friend's
  2. Complete app setup — store listing, Data safety, content rating
  3. Create the Closed testing track and hand out the tester list or the opt-in link
  4. Write down the join dates and start counting the 14 days from there
  5. Reply to the feedback that arrives, fix things, and push the fixes back to the closed track
  6. Apply for production access from the Dashboard

If you are shipping a project exported from Rork through EAS, you can stop track mix-ups at the config level.

{
  "submit": {
    "internal": {
      "android": {
        "track": "internal"
      }
    },
    "closed": {
      "android": {
        "track": "alpha"
      }
    }
  }
}
# Straight to the people close to you (works before app setup is done)
eas build -p android --profile production
eas submit -p android --profile internal --latest
 
# Into the track where the 14 days are counted (after app setup)
eas submit -p android --profile closed --latest

The values track accepts are internal, alpha, beta and production. If your closed test lives on a custom-named track, upload to alpha and promote the release to that track inside Play Console. Either way, open the Console afterwards and look at which track the build actually landed in. A version-number collision will stall you in a different place, so the number in "Version code 1 has already been used" belongs to either app.json or EAS is worth reading first.

The application form asks what you did during those 14 days

You start the application from "Apply for production" on the Dashboard. The form has three sections, and the questions are knowable in advance.

SectionWhat it asks
About your closed testHow easy recruiting testers was / whether testers used all the features / whether their usage matched what you expect from production users, and any differences you saw / a summary of the feedback and how you collected it
About your app/gameYour target audience, as specifically as you can put it / the value your app gives users / your estimated install range for the first year
About your production readinessWhat you changed based on what the closed test taught you / how you decided it was ready

Reading that list first changes how you spend the two weeks. Whether testers touched every feature, what they said, what you changed in response — none of that can be reconstructed from memory afterwards.

The 14 days are not a waiting period. They are when you write the answers. Of everything I would prepare, this reframing is the one that pays.

Two small mechanics. Each section only saves when you press "Next", so a half-written page disappears if you navigate away. And if your app needs a login, leave working test credentials in the Console. Review usually comes back within seven days.

A closed test shows you real device behaviour and stops there; the store build is a different animal. I wrote about closing that gap in I don't call a screen finished just because it ran in Rork Companion.

One file and one script are enough for those two weeks

The records do not need to be elaborate. Join date and drop-out date per tester, a one-line summary of each piece of feedback with where it came from, and what you changed with the version you pushed it in. With those three, most of the form fills itself.

Counting join dates by hand goes wrong, so I hand that to a small script.

"""Count how many testers have been opted in continuously for 14+ days.
Usage: python3 closed_test_status.py testers.csv --as-of 2026-09-09
CSV columns: tester,opted_in,opted_out (empty opted_out means still in)
"""
import argparse, csv, sys
from datetime import date, timedelta
 
REQUIRED_DAYS = 14
REQUIRED_TESTERS = 12
 
def parse_day(value):
    if not value or not value.strip():
        return None
    return date.fromisoformat(value.strip())
 
def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("csv_path")
    ap.add_argument("--as-of", default=date.today().isoformat())
    args = ap.parse_args()
    as_of = parse_day(args.as_of)
 
    active, dropped = [], []
    with open(args.csv_path, newline="", encoding="utf-8") as fh:
        for row in csv.DictReader(fh):
            joined, left = parse_day(row["opted_in"]), parse_day(row["opted_out"])
            if joined is None or joined > as_of:
                continue                      # has not joined yet
            if left is not None and left <= as_of:
                dropped.append((row["tester"], (left - joined).days))
                continue                      # a departure resets the clock
            active.append((row["tester"], (as_of - joined).days,
                           joined + timedelta(days=REQUIRED_DAYS)))
 
    qualified = [t for t in active if t[1] >= REQUIRED_DAYS]
    print(f"as of: {as_of}  opted in: {len(active)}  dropped: {len(dropped)}")
    print(f"{REQUIRED_DAYS}+ continuous days: {len(qualified)} / {REQUIRED_TESTERS} needed")
    for name, days, _ in sorted(active, key=lambda t: t[1], reverse=True):
        mark = "OK " if days >= REQUIRED_DAYS else "   "
        print(f"  {mark}{name:<12} {days:>3} days")
    for name, days in dropped:
        print(f"  OUT {name:<12} left after {days} days (rejoining restarts the 14)")
 
    if len(qualified) >= REQUIRED_TESTERS:
        print("-> criteria for applying for production access are met")
        return 0
    eligible_dates = sorted(t[2] for t in active)
    if len(eligible_dates) < REQUIRED_TESTERS:
        need = REQUIRED_TESTERS - len(eligible_dates)
        print(f"-> {len(active)} opted in. invite {need} more to reach the minimum")
        return 1
    print(f"-> if nobody leaves, criteria are met on {eligible_dates[REQUIRED_TESTERS - 1]}")
    return 1
 
if __name__ == "__main__":
    sys.exit(main())

Run it against thirteen invitations where one person left after nine days, and it answers like this.

as of: 2026-09-09  opted in: 12  dropped: 1
14+ continuous days: 7 / 12 needed
  OK t01           20 days
  OK t02           20 days
  OK t03           19 days
  OK t04           18 days
  OK t05           18 days
  OK t06           16 days
  OK t07           15 days
     t08           12 days
     t09            8 days
     t10            7 days
     t11            6 days
     t12            6 days
  OUT t13          left after 9 days (rejoining restarts the 14)
-> if nobody leaves, criteria are met on 2026-09-17

That last line is what you plan the application date around. Looking only at the headcount, you would read "twelve are in, so I can apply." Looking at the days, you can see it is not there yet.

Deadlines on the store side rarely arrive one at a time, either. If you have an update coming, put dates like the ones in August 31 for target API 36 means different things depending on whether you ship an update on the same calendar.

What to do today

Before you send the invitations, make one note file. Three headings, taken straight from the form: about your closed test, about your app, why it is ready for production. Leave the bodies empty.

Fourteen days from now, whether you can fill those blanks decides between applying once and waiting another fortnight. As an indie developer, I have no way to buy back a lost fortnight — so I keep making the place to write before there is anything to write in it.

Thank you for reading. I hope the next step is a shorter one for you than it looks from in front of that greyed-out button.

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

App Dev2026-08-23
Bumping targetSdk to 36 surfaced a 16 KB warning. These are two different deadlines
The August 31 target API 36 requirement and the February 1, 2027 16 KB page size requirement are separate conditions. Here is how to inspect your AAB without installing the NDK, and why a LOAD misalignment and a zip boundary miss need completely different fixes.
App Dev2026-08-18
The three places I had to fix before a Rork project actually targeted API level 36
My app.json said targetSdkVersion 36. The value my build actually read was 35. Here is the script that reports the effective value, and how I split my apps between raising, leaving alone, and requesting an extension.
Getting Started2026-08-19
You Cannot Shorten Review, But You Can Still Choose the Moment Your App Goes Live
Review time is out of your hands. The moment your app appears in the store is not. Here is how the three App Store Connect release options, Google Play managed publishing, and eas submit fit together.
📚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