Ship12 min read
GitHub + Developer playground
Link a GitHub repo so every push becomes a private build, play those builds on your own phone with test ads, an FPS meter and an SDK log, then submit the one you like.
On this page9 sections
Uploading a zip by hand works fine. Once you change your game often, two tools save you time:
- GitHub import. Link a public GitHub repo to your game. Every push (or every release) becomes a new private version on this website. No zipping, no uploading.
- The Developer playground. A hidden part of the Twiddle Play app where you play those private builds on your own phone, with test ads, a frame-rate meter and a log of every SDK call.
Nothing you push is ever public by itself. Players only get a version after you submit it and a reviewer approves it.
- Push You push your game to GitHub.
- Sync Twiddle Play downloads it and runs the normal upload checks.
- Private build It becomes a new version that only you can see.
- Play on your phone The playground installs it, with test ads.
- Submit Happy with a build? Submit it for review.
- Live An approval makes it public. Players get the update.
New to GitHub? It's a website that stores code and its history. A repo (repository) is one project. A push sends your latest changes to GitHub. A branch is one line of work, usually called main. A release is a named snapshot that can carry files. A webhook is GitHub calling another website (here, Twiddle Play) whenever something happens in your repo.
Before you start#
- A game on this website. Create it first with New game on your Dashboard (see the quick start). One repo links to one game.
- A public GitHub repo with your game in it. Private repos aren't supported yet.
- Your game as files, with
index.htmlat the top of a folder: the repo's top level, or a folder likegame/,public/ordist/. The same rules as a zip apply: relative paths, allowed file types, nothing from the internet. See Packaging and upload. - For the playground: the Twiddle Play app on an Android phone.
1. Add twiddleplay.json with your builder code#
Anyone can paste any public repo's address. So Twiddle Play only imports a repo that names you in a small file, twiddleplay.json. That file holds your builder code: a public code that stands for your account.
- Find your builder code. On this website, open your Profile page. Your code looks like
tp_k3x9m2q7ab:tp_and 10 letters and digits. Your game's GitHub panel shows it too. - Add the file to your repo, at the top of the repo (or in the game folder you'll link). Put your own code in it:
{ "builder": "tp_k3x9m2q7ab" }- Commit and push it. On github.com you can do this in the browser: open your repo, click Add file → Create new file, name it
twiddleplay.json, paste the line above with your code, and click Commit changes.
Good to know:
- Your builder code isn't a secret. It only proves the repo is yours, so committing it is fine. Your password and your webhook secret (step 3) are secrets.
- Where it's read from: the folder you link, or else the top of the repo. In Release mode (step 2), Twiddle Play looks inside your
twiddleplay.zipfirst, then in the repo at that release. - It isn't part of your game. Twiddle Play leaves it out of the build.
2. Link the repo#
- Open your game on this website and find its GitHub panel.
- Fill in the form:
| Field | What to type | Example |
|---|---|---|
| Repo URL | Your repo's address: https://github.com/ + owner + / + repo. |
https://github.com/asha/space-hopper |
| Branch | The branch to build from. Leave it empty for the repo's default branch. | main |
| Folder | The folder that holds index.html. Leave it empty for the top of the repo. |
game |
| Mode | Files or Release (see below). | Files |
- Press Link & build. Twiddle Play runs the first sync straight away and shows the result: a new private version, or an error with its fix (see Sync errors).
Files or Release?#
- Files mode (the default, and the simplest). Twiddle Play downloads the newest commit on your branch, keeps only your folder, and checks it like a zip upload. Use it when your repo holds the finished game: plain HTML and JavaScript, or a build folder you commit.
- Release mode. Twiddle Play takes a file named
twiddleplay.zipfrom your repo's newest GitHub release. Use it when your game needs a build step: a command likenpm run buildthat turns your source code into the files players need (Vite, webpack, many Phaser templates). GitHub can run that build for you; see step 4.
In Files mode, Twiddle Play leaves out hidden files and folders (.git, .github, .DS_Store) and files like LICENSE or README that have no extension. Everything else in your folder must be an allowed file type. The whole repo download must stay under 50 MB; if your repo is bigger (say, it holds source art), use Release mode.
3. Sync: now, or on every push#
Sync now#
Press Sync now on the GitHub panel. If nothing changed since the last sync (the same commit in Files mode, or the same release in Release mode), nothing new is made. Otherwise a new version appears on your Versions tab:
- it's marked as coming from GitHub, with its commit (or release tag);
- its notes are your commit message (or release name);
- it's private: only you see it, until you submit it.
Automatic: the webhook#
A webhook makes GitHub tell Twiddle Play about every push, so you never press Sync now again.
- Make a webhook secret. On the GitHub panel, press Make a secret (or Make a new secret to replace one). The panel shows a Payload URL and a Secret. Copy both. The secret is shown only once; if you lose it, make a new one (the old one stops working).
- On GitHub, open your repo, then Settings → Webhooks → Add webhook.
- Fill it in:
- Payload URL: the URL from the panel. It looks like
https://play.twiddlelabs.com/api/github/webhook/<your game id>. - Content type:
application/json. - Secret: paste the secret.
- Which events: choose Let me select individual events, then tick Pushes (Files mode) or Releases (Release mode).
- Leave Active ticked and click Add webhook.
- Payload URL: the URL from the panel. It looks like
- GitHub sends a test called a "ping" right away. Open the webhook's Recent Deliveries tab: a green tick means Twiddle Play heard it.
From now on, a push to your linked branch (Files mode) or a newly published release (Release mode) starts a sync. Pushes to other branches, and other events, are ignored.
4. Games with a build step: the GitHub Action#
If your game needs npm run build, the files players need only exist after the build. Twiddle Play never runs your build (the FAQ explains why), but GitHub can, with a GitHub Action: a small script that GitHub runs for you on every push. It's free for public repos.
Our template does this on every push to main:
- installs your packages and builds the game (
npm ci && npm run build); - zips what's inside your build folder as
twiddleplay.zip, soindex.htmlsits at the top of the zip; - publishes a new GitHub release with that zip attached, which Release mode picks up.
To use it:
- For Vite, set
base: './'invite.config.js, so the game works from any folder. See relative paths. - In your repo, create the file
.github/workflows/twiddleplay-release.ymlwith this content (on github.com: Add file → Create new file, then type the whole path as the name):
# Twiddle Play: build your game on GitHub and publish it as a release.
#
# Use this when your game needs a build step (npm, Vite, webpack…).
# 1. Save this file in your repo as .github/workflows/twiddleplay-release.yml
# 2. Set BUILD_DIR below to the folder your build writes (Vite: dist).
# For Vite, also set base: './' in vite.config.js, so the game works from any folder.
# 3. On Twiddle Play, link the repo in "Release" mode.
# Every push to main then builds the game, zips it as twiddleplay.zip and publishes a new release.
# Twiddle Play installs the zip from the newest release as a private build.
name: Twiddle Play release
on:
push:
branches: [main]
workflow_dispatch: # adds a "Run workflow" button on the Actions tab
permissions:
contents: write # lets the workflow create releases
env:
BUILD_DIR: dist # the folder that holds your built index.html
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install and build
run: |
npm ci
npm run build
# Zip what is INSIDE the build folder, so index.html sits at the top of the zip.
- name: Make twiddleplay.zip
run: |
test -f "$BUILD_DIR/index.html" || { echo "No index.html in $BUILD_DIR. Check BUILD_DIR."; exit 1; }
cd "$BUILD_DIR"
zip -r -X "$GITHUB_WORKSPACE/twiddleplay.zip" . -x '*.DS_Store' -x '*.map' # source maps left out: players don't need them
# A new release with a new tag on every push (build-1.1, build-2.1…; the .1 is the attempt, so re-runs work).
# Twiddle Play takes the newest release and skips a tag it has already seen.
- name: Publish the release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "build-${{ github.run_number }}.${{ github.run_attempt }}" twiddleplay.zip \
--title "Build ${{ github.run_number }}" \
--notes "Built from ${{ github.sha }}" \
--latest- Check
BUILD_DIR. It must be the folder your build writes. Vite usesdist; some tools usebuild. - Commit and push. On GitHub, the Actions tab shows the run. A minute or two later, Releases shows "Build 1" with
twiddleplay.zipattached. - Link the repo in Release mode (leave Folder empty), and set up the webhook with Releases ticked.
Good to know:
- Keep
twiddleplay.jsonat the top of the repo. Release mode reads it from there. - Every push makes a new release (
build-1.1,build-2.1…). Twiddle Play builds each new release once. Old releases can pile up on GitHub; delete them whenever you like, because Twiddle Play keeps its own copy of each build. - It uses only GitHub's own parts:
actions/checkout,actions/setup-node, and theghtool that GitHub's machines already have. - It needs a
package-lock.json(that's whatnpm ciinstalls from). If you use Yarn or pnpm, change the install line. - Want to run it without pushing? The Actions tab has a Run workflow button for it.
5. Play builds on your phone: the Developer playground#
Link your phone#
- In the app: open Twiddle Play on your phone, then Settings → Developer (under "For makers"). A sheet called Link your developer account opens, with a box for a 6-digit code.
- On the website: log in, then open Developer › Phones (at play.twiddlelabs.com/dev) and click Link a phone. A 6-digit code appears.
- Back in the app: type the code and tap Link this phone. The app says "Linked to" and your name.
A code works once, for 10 minutes. You can link up to 3 phones. The website's Phones list shows each one; remove a phone there, or tap Unlink in the app.
Play a private build#
Now Settings → Developer opens the Playground: your games, each with its newest 5 builds, newest first. Each build has a label:
| Label | Meaning |
|---|---|
| Private | Uploaded or synced, but not submitted. Only you can see it. |
| In review | Submitted and waiting for a reviewer. |
| Live | Approved. Players have this version. |
| Rejected | A reviewer sent it back. The note is on your Versions tab. |
- Tap Install the first time, then Play. When a newer build exists, the button says Update to vN.
- Pull down on the list to refresh after a push.
- Playground copies are separate. If you also installed your live game from the store, that copy isn't touched. Playground builds never show in your Library or under "Continue playing".
What's different in a playground build#
- Test ads only. Ads come from Google's test ads, so you can check every ad moment as often as you like. They earn nothing, and they can't get your game flagged. (Watching real ads in your own live game can: see what counts as fraud.)
- No stats. Playground sessions don't count in your stats or earnings.
- The dev overlay. A small panel on top of your game. Tap it to open or close it.
- FPS (frames per second): how many times a second your game redraws. 60 is smooth (fast screens do 90 or 120). Drops below about 50 during play feel like stutter; see Keep your game smooth.
- SDK log: the last 50 or so SDK calls (
ready(),showInterstitial(),pause…) and errors from your game. It's the portal's SDK activity panel, on a real phone.
6. Submit for review#
A synced build is a normal version. On your game's Versions tab it shows its GitHub commit or release tag. When you're happy with one:
- Click Submit for review next to it, check the list in the dialog, and confirm.
- A reviewer plays it on a phone. Approved: it goes live and players get it. Rejected: you get a note; push a fix, and the next sync makes a new build to submit.
- Only a reviewer's approval makes a build public. Pushing to GitHub never changes what players have.
- Old private builds are cleaned up. Twiddle Play keeps your newest 10 GitHub builds that were never submitted and deletes older ones. Submitted, live and rejected versions are always kept.
- The review guidelines list what reviewers check.
Sync errors and how to fix them#
When a sync fails, the GitHub panel shows the error with its fix. Every error:
| Error | What it means | How to fix it |
|---|---|---|
not_public |
The repo is private. | Make the repo public on GitHub (Settings → General → Change visibility), then press Sync now. Private repos aren't supported yet. |
not_found |
The repo, branch or folder doesn't exist. In Release mode: the repo has no published release yet, or the newest release has no twiddleplay.zip. |
Check the repo URL, branch and folder. In Release mode, publish a release with a twiddleplay.zip attached (the GitHub Action does this). |
no_ownership_file |
No twiddleplay.json in your folder or at the top of the repo, or it isn't valid JSON, or it has no "builder", or it's over 64 KB. |
Add twiddleplay.json with {"builder":"<your code>"} to your folder or the top of the repo, push, then sync. |
wrong_builder_code |
twiddleplay.json holds someone else's code (or a typo). |
Copy your code from your Profile page into "builder", push, then sync. |
too_big |
Files mode: the repo download is over 50 MB. Release mode: twiddleplay.zip is over 50 MB, or the game unpacks to over 150 MB. |
Files mode: point Folder at your built game, or switch to Release mode. Release mode: shrink images and sounds. See limits. |
invalid_bundle |
The upload checks rejected the files, for example no index.html at the top of the folder, or a file type that isn't allowed. The message names the problem. |
Point Folder at the folder that holds index.html (often dist or public), or fix the file named, then push. The upload errors explain each message. |
rate_limited |
Too many syncs: one per minute per game, 30 builds per game per day, or GitHub's own limit for our server. | Wait a little, then press Sync now. |
github_unavailable |
GitHub didn't answer in time. | Press Sync now again in a minute. |
game_suspended |
The game is suspended, so it can't take new builds. | Contact support if you think this is a mistake. |
FAQ#
Can I link a private repo?#
Not yet; only public repos for now. If your code must stay private, keep uploading zips by hand: see Packaging and upload.
Why doesn't Twiddle Play run my build?#
A build script can run any command. Running thousands of builders' scripts on our server would be a security risk for everyone. So the server only downloads files and checks them, exactly like a zip upload, and never runs your code. Builds run on GitHub instead, in a machine of your own (that's what the GitHub Action is for).
How often can it sync?#
- At most 1 sync per minute per game. Pushes that arrive within that minute aren't lost: one follow-up sync runs when the minute is up, and it builds your newest commit.
- At most 30 builds per day per game. Syncs that find nothing new don't count.
- GitHub also limits how often our server may ask it for files. If that limit is hit, you see
rate_limited; wait a little and press Sync now.
Can I still upload zips by hand?#
Yes. Uploads and GitHub builds sit side by side on your Versions tab, in one list of version numbers.
My game lives in a folder of a bigger repo. Does that work?#
Yes: set Folder to its path, like games/space-hopper/dist. In Files mode the whole repo download still has to be under 50 MB.
How do I unlink a repo?#
Use Unlink on the GitHub panel. Your versions stay. Also delete the webhook on GitHub (Settings → Webhooks), because Twiddle Play ignores its calls from then on.
My game is made with AI. Can I use this?#
Yes. Put your index.html, twiddleplay-sdk.js and twiddleplay.json in a public repo, and use Files mode. Start with Make a game with AI.