I got my tvOS app working without a Mac and I want to help you do it too! I spent like $400 in tokens on this and thousands of Github actions minutes and almost gave up multiple times, so let me save you the headaches and the money.
As a homeschool mom of 5 (ages 2-11) I'm hyper aware of all the AI slop out there. I set out to make my kids a slop-free TV experience that I could cater to our homeschool learning goals and my kids' pet projects. We already use the AppleTV for our regular screen time and for our Jellyfin server, so a tvOS app seemed like a natural place for this newbie to start. Wow was I wrong! I do not have a Mac, I've got a raspberry pi and a windows laptop.
I burned through GitHub's free 2,000 macOS runner minutes faster than I expected, spent hundreds of dollars in AI tokens, and got fooled by green CI runs that had not actually produced a usable TestFlight build.
If you are trying to do this from Windows, the docs get fuzzy right where the expensive mistakes start.
The internet got very unhelpful very quickly. Most advice either pointed back to Expo docs that skipped the ugly parts, or assumed I already owned a Mac and knew my way around Xcode.
A lot of the advice is either:
- too vague to trust
- written for people who already own a Mac
- technically true but missing the part that was actually breaking
So here is the version I wish I could have found first.
The actual goal
I was building a real kids TV app with Expo and React Native.
The goal was pretty simple:
- get the app building
- get it uploaded
- get it into TestFlight
The reality was... not that.
Where this went sideways
The biggest obstacle is that tvOS sits in an annoying middle ground.
It is close enough to iOS that people talk about it like it should mostly work.
But it is different enough that the weird parts matter a lot.
That means you can lose a lot of time in places like:
- build tooling
- asset requirements
- App Store metadata
- upload verification
- credentials
- versioning
And the worst part is that you can think you are done when you are not.
The trap that wasted the most time
A green CI run is not always a successful submission.
That one deserves to be said twice.
A green CI run is not always a successful submission.
I had runs that looked successful even though the upload step had not actually landed the build where it needed to go.
So if you are only watching the big green checkmark, you can waste a ridiculous amount of time celebrating the wrong thing.
What actually mattered for me
1. Treat tvOS like its own platform
Not like iOS with a couch.
That mindset shift helps a lot.
Because if you assume everything should behave like iOS, you keep debugging the wrong layer.
2. Clean native regeneration mattered
Stale native state can poison later builds.
If you are changing app metadata, build numbers, assets, or submission flow, be suspicious of old generated native files.
3. Upload verification mattered more than the workflow summary
This was the false-green problem.
I needed to verify the upload output itself, not just trust the workflow result.
4. Version and build number are not the same problem
Apple is very happy to let you learn this the annoying way.
5. Asset requirements are picky enough to ruin your afternoon
If your tvOS assets are wrong, Apple will absolutely let you find that out late.
The path that finally made sense
I am not putting the full copy-paste workflow in this free post, but I do want to tell you the shape of the path that finally stopped the bleeding:
- Use a GitHub Actions macOS runner, not a Linux runner
- Use Xcode 26+ on that runner, because that was the fix that finally got me past the Swift 6 wall
- Regenerate the native project cleanly before the build
- Make sure
EXPO_TV=1is actually set so you are building the tvOS variant - Build on the runner with local EAS, so you can control the environment
- Expect EAS to still use the
iosplatform flag for this part, because tvOS is still awkward in the tooling - Upload with
xcrun altoolusing--type appletvos - Fail the workflow unless the upload log explicitly confirms success
That was the high-level path.
For me, GitHub Actions plus xcrun altool was the successful path. The more generic EAS-only path created more confusion than it solved.
The exact workflow yaml, config snippets, and troubleshooting flow are what I put in the paid version.
What I would tell anyone doing this from Windows
If you already have an Expo or React Native app and you are trying to get to TestFlight without buying a Mac first, this is the order I would think about it in:
1. Make sure the app actually belongs on tvOS
This sounds obvious, but it matters.
If your app is mostly forms, settings screens, or touch-first interactions, tvOS may not be worth the build pain.
If your app is media-first, learning-first, or browse-and-play, it makes a lot more sense.
2. Get App Store Connect set up correctly before your first real build
This is one of the easiest ways to waste time.
Before you trigger expensive builds, make sure you have:
- a tvOS app created in App Store Connect, not an iOS app
- the correct bundle identifier
- the numeric App Store Connect app ID
- an App Store Connect API key
- the API key's Key ID and Issuer ID
- the
.p8file saved somewhere safe, because Apple only lets you download it once
If any of that is wrong, you can burn a lot of build time chasing a problem that was never in your app code.
3. Get the testing flow settled early
This helped more than I expected.
Before you obsess over app code, make sure the delivery path is at least understandable on a real Apple TV:
- install the TestFlight app on the Apple TV
- add or redeem the tester access early
- watch where the build shows up
- pay attention to the version and build numbers Apple is actually showing you
When my app showed up as incompatible, that was useful information. It ruled out some guesses and pointed back toward platform, provisioning, and submission issues. And when it finally moved from incompatible to installable, that was one of the clearest signs that the path was finally right. When the build number was not changing the way I expected, that was also a clue that the new build was not actually getting through the path I thought it was.
That is why I would work on the flow earlier than feels natural. Sometimes the bug is in the delivery pipeline not in the app.
4. Protect yourself from GitHub Actions minute burn
This is where I got hurt.
GitHub offers 2,000 free GitHub Actions minutes. Those will disappear faster than you think on macOS runners. A full run can easily take around 15 to 20 minutes, and when you are iterating on workflow syntax, auth, file paths, signing, invalid eas.json fields, or tvOS-specific config, the minutes pile up fast.
A few things I would do immediately:
- use
workflow_dispatchwhile you are stabilizing the workflow - set a GitHub billing limit before you start experimenting
- do as much validation locally as you can before kicking off another macOS run
- do not leave a broken poll loop chewing runner time for no reason (ask me how I know!)
The mistake was not just "GitHub is expensive." The mistake was using expensive macOS time to discover fixable config problems one run at a time.
5. Clean-regenerate native files when you change important tvOS config
If you change assets, metadata, bundle IDs, versioning, or native plugin config, do not assume the old generated iOS project is still trustworthy.
In my case, clean native regeneration mattered. Old native state can keep confusing you long after you think you fixed the real issue.
6. Treat version and build number as separate levers
This one is sneaky.
If Apple has already seen a version string, changing only the build number may not save you.
You need to understand both:
-
versionis the release version users see -
buildNumberis the internal increment for that version
The safe rule, if you are trying to stop wasting time, is this:
if Apple has already touched that version, bump the version before the next serious submission attempt.
7. Get the asset requirements right early
These are published in pieces elsewhere, but it helps to see them in one place.
Apple validates exact pixel dimensions. Close is not good enough.
And yes, the 2x assets matter.
tvOS app icons
| Asset | 1x | 2x | Required |
|---|---|---|---|
| App Icon Small | 400x240 | 800x480 | Yes |
| App Icon Large | 1280x768 | 2560x1536 | Yes |
tvOS top shelf images
| Asset | 1x | 2x | Required |
|---|---|---|---|
| Top Shelf | 1920x720 | 3840x1440 | Deprecated, but may still be checked |
| Top Shelf Wide | 2320x720 | 4640x1440 | Yes |
The practical takeaway:
- create both 1x and 2x assets
- use the exact dimensions above
- do not assume reusing one file for multiple slots will pass validation
- verify your asset catalog is actually generated correctly in the app bundle
8. Do not trust the workflow summary, trust the upload proof
If your workflow turns green but the upload step does not clearly prove success, you are not done.
For me, the important thing was checking the upload output itself.
The workflow needed to fail unless altool actually confirmed the archive upload succeeded, not just finish without an obvious crash.
If your logs do not contain a clear success message like No errors uploading archive, treat that run as suspect.
That one change saves real time, because it kills the false-green problem early.
What this post is and is not
This is not a full end-to-end course.
It is the honest summary of where the real pain showed up.
If you are looking for:
- a broad introduction to tvOS development
- a beginner coding tutorial
- a claim that this whole process is smooth
This is not that.
If you are looking for:
- a realistic path
- hard-earned gotchas
- the places the docs get fuzzy
- a faster route than brute force trial and error
Then yes. This is for you.
If you want the cleaned-up kit
After fighting through this, I cleaned up the working path into a paid kit with:
- the exact GitHub Actions workflow yaml
- the config snippets
- the asset checklist
- the upload verification pattern
- troubleshooting notes
- the common mistakes I hit so you do not have to hit all of them yourself
If you are in the exact situation I was in, that version is for you.
If this free post got you most of the way there, great. Truly.
If it saved you time and you do not need the full kit, the coffee Diet Dr Pepper link is there too.
Final thought
This whole corner of the stack is still weirdly under-documented.
That means two things can be true at once:
- yes, it is possible
- yes, it is still more annoying than it should be
If you are in the middle of it right now, I get it. Dang.
I will keep cleaning this up as I go so the next person does not have to reconstruct the path from build logs and vibes.
Top comments (0)