An AI agent that builds, signs, and ships your Flutter app — APK, App Bundle, and IPA — from one terminal. Talk to it in plain English.
fluttership "build apk aab ipa for DEV and LIVE"One command. Parallel builds. No forgotten version bumps, no wrong keystore passwords, no babysitting.
If FlutterShip saves you time, please ⭐ star the repo — it genuinely helps the project reach more Flutter devs and keeps it moving.
- What is FlutterShip?
- Quick start (free — uses a Gemini API key)
- How you actually use it
- Command reference
- ⚡ Parallel builds
- 🔐 The secure vault
- 📦 Publishing to the stores — full credential guide
- 👤 Solo vs 👥 Team mode
- Requirements
- Security model
- 🧯 Troubleshooting
- 🤝 Contributing
- Uninstalling
- License
FlutterShip is a Python CLI with an AI agent at its core. You point it at your Flutter project and tell it what you want — "build a LIVE apk", "release 2.0.0 to TestFlight" — and it does the rest:
- Reads your project automatically — version, flavors,
dart-definekeys, bundle id, signing config. No config file to write. - Detects your build pattern — flavors,
--dart-define,--dart-define-from-file, or plain. It figures out the rightflutter buildcommand for you. - Builds in parallel — APK + AAB + IPA across multiple flavors at once, each in an isolated sandbox.
- Signs and submits — uploads AAB to Google Play and IPA to TestFlight/App Store, with credentials kept in an encrypted vault the AI never sees.
- Explains failures in plain English — when a build breaks, it reads the log and tells you exactly what to fix.
- Keeps a release history — every build and upload is logged outside your repo.
You can drive it conversationally (the agent decides which tools to run) or with plain
subcommands (fluttership doctor, fluttership status, …).
FlutterShip needs an LLM API key to power the agent. The easiest free option is Google Gemini — the free tier is plenty for normal use.
- Go to aistudio.google.com and sign in.
- Click Get API key → Create API key.
- Copy the key (looks like
AIza...).
Want another free option? FlutterShip also supports
groq— free and very fast (Llama / open models). Get a key at console.groq.com/keys. Prefer Claude or GPT?anthropic(console.anthropic.com) andopenai(platform.openai.com) work too. Pick your provider during setup, or switch anytime in-session with/model.
Recommended — with pipx (installs the CLI in its own isolated environment and puts it on your PATH automatically — the cleanest way to install any Python command-line tool):
pipx install fluttershipDon't have pipx? (one-time setup)
python3 -m pip install --user pipx
python3 -m pipx ensurepath # adds pipx's bin dir to PATH
# then open a new terminal and run: pipx install fluttershipOr with pip:
python3 -m pip install fluttership💡 Use
python3 -m piprather than a barepip/pip3. It works the same on every machine regardless of whether the command is calledpiporpip3, and installs into the Python you're actually running.
The install is intentionally lean and fast — it pulls in only what the default (free Gemini) provider needs. Other providers and Play Store uploads are optional add-ons you install only if you use them:
python3 -m pip install "fluttership[anthropic]" # use Claude as the provider
python3 -m pip install "fluttership[openai]" # use GPT as the provider
python3 -m pip install "fluttership[play]" # upload AAB to Google Play
python3 -m pip install "fluttership[all]" # everythingIf your shell can't find the command right after a pip install, the Python
scripts directory isn't on your PATH (common with pip install --user). Two fixes:
-
Easiest: install with
pipxinstead (see above) — it handles PATH for you. -
Always works: run it through Python, which never depends on PATH:
python3 -m fluttership # identical to running `fluttership` python3 -m fluttership doctorTo add the scripts dir to your PATH permanently, append the folder printed by
python3 -m site --user-base(+/bin, or\Scriptson Windows) to your shell profile (~/.zshrc,~/.bashrc, …).
Install from source (for development)
git clone https://github.com/AscEmon/fluttership.git
cd fluttership
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
python3 -m pip install -e . # core only
python3 -m pip install -e ".[all]" # with every provider + Play uploadFrom inside any Flutter project, run:
cd /path/to/your/flutter/app
fluttershipOn first run a ~30-second wizard walks you through:
- Mode — solo (just you) or team (a lead shares credentials).
- AI provider + model — pick
geminiand the default model. - Your API key — paste it and FlutterShip saves it to
~/.fluttership/.env(permissions600), or set it yourself (see below).
After that, you're talking to the agent. That's it.
Instead of pasting it into the wizard, you can put it in a .env file in your project
root, or in ~/.fluttership/.env:
# Pick one, matching your chosen provider
GEMINI_API_KEY=your_gemini_api_key
# GROQ_API_KEY=your_groq_api_key
# ANTHROPIC_API_KEY=your_anthropic_api_key
# OPENAI_API_KEY=your_openai_api_key
# Optional — override provider/model without re-running the wizard
# (or just run /model in-session)
# LLM_PROVIDER=gemini
# GEMINI_MODEL=gemini-2.5-flashProject
.envwins;~/.fluttership/.envfills the gaps. Both are git-ignored.
Verify everything is wired up:
fluttership doctorThere are two ways to drive FlutterShip — mix them however you like.
Just run fluttership and type what you want. Or pass it inline:
fluttership "build LIVE apk"
fluttership "build apk aab ipa for DEV and LIVE" # parallel matrix
fluttership "release version 2.0.0 to production"
fluttership "what was the last release?"The agent reads your project, shows a short build plan, asks for confirmation, builds, and offers the right next step (e.g. "Submit to TestFlight?" only after an IPA is built).
Inside an interactive session, type / to open the command palette
(/status, /doctor, /vault, /mode, /new, /exit, …). Press Esc to
interrupt the agent mid-thought and redirect it.
fluttership status # version, bundle id, detected build pattern
fluttership doctor # health checks — tools, signing, vault, API key
fluttership history # build & release history for this project
fluttership clean # delete build artifacts (fluttership-dist/)
fluttership uninstall # remove FlutterShip + all its data from your machine| Command | What it does |
|---|---|
fluttership |
Start the interactive AI agent |
fluttership "<request>" |
Run a one-off request, e.g. "build LIVE apk" |
fluttership setup |
Re-run the setup wizard (mode / provider / model) |
fluttership mode |
Show or switch solo/team mode |
fluttership mode solo |
Switch to solo mode |
fluttership mode team lead|member |
Switch to team mode with a role |
fluttership config |
Show current configuration |
fluttership doctor |
Environment + project health checks |
fluttership status |
Project info — version, bundle id, build pattern |
fluttership history |
Build & release history |
fluttership clean |
Delete build artifacts (fluttership-dist/) |
fluttership vault setup |
Create the encrypted credentials vault (team: lead only) |
fluttership vault verify |
Test the vault password + show what's configured |
fluttership vault status |
Vault info (no password needed) |
fluttership vault log |
Credential-access audit log |
fluttership vault export [dest] |
Share the encrypted vault with your team (lead only) |
fluttership vault import <file> |
Install a vault shared by your team lead |
fluttership uninstall |
Remove FlutterShip completely. --keep-data keeps vault/history, --yes skips the prompt |
fluttership help |
Full usage |
Slash commands (inside an interactive session): /status, /history, /doctor,
/clean, /mode, /model, /config, /vault, /setup, /new, /exit.
FlutterShip's headline feature. Ask for several targets and/or flavors at once and it builds them concurrently, each job in its own isolated git worktree sandbox:
fluttership "build apk aab ipa for DEV and LIVE"A live dashboard shows every job's status as it runs:
⚡ Building 6 jobs · v2.0.0+9
┌──────────────┬──────────────┬────────┬──────────────────────────────┐
│ Job │ Status │ Time │ Detail │
├──────────────┼──────────────┼────────┼──────────────────────────────┤
│ apk·DEV │ ✅ done │ 1m42s │ myapp_DEV_2.0.0(9).apk │
│ apk·LIVE │ ✅ done │ 1m48s │ myapp_LIVE_2.0.0(9).apk │
│ aab·LIVE │ 🔨 building │ 0m55s │ logs/aab-LIVE.log │
│ ipa·LIVE │ 🍎 archiving │ 2m10s │ logs/ipa-LIVE.log │
└──────────────┴──────────────┴────────┴──────────────────────────────┘
Why it's worth it:
- Faster releases — wall-clock time is your slowest build, not the sum of all of them.
- No collisions — each job runs in a fresh, isolated worktree, so Gradle's project lock and the shared
build/directory never clash. iOS archives are automatically serialized (Xcode's global state can't take twoRunnerarchives at once), while Android builds stay fully parallel. - One shared version — every artifact in a run carries the same version+build number, so a matrix release is consistent.
- Artifacts collected safely — finished builds land in
fluttership-dist/(outsidebuild/), soflutter cleancan't wipe what you just built. Per-job logs go tobuild/fluttership-logs/. - Different flavors per platform — "LIVE and DEV apk, plus LIVE ipa only" builds exactly those pairs, no wasted builds.
Run fluttership clean to reclaim space from fluttership-dist/ when you're done
(your release history, kept in ~/.fluttership, is never touched).
Store credentials (App Store .p8 key, Play Store service-account JSON, IDs) are
sensitive. FlutterShip keeps them in an encrypted vault — never in your repo, never
in plain text, and never shown to the AI model.
How it's protected:
- AES-256-GCM authenticated encryption with a memory-hard key derivation. The key is derived from your password and never stored — the file holds only salt, nonce, and ciphertext.
- Lives in
~/.fluttership/vault.enc(chmod 600) — outside your project. - Strong password required: the file is safe to share, so its only defense is the password.
- Decrypted in memory only; temp files written to
/tmp(chmod 600) are deleted immediately after each upload. - The LLM only ever sees success/fail status — never a key, password, or token.
- Every access is written to an audit log (
fluttership vault log).
Set it up once:
fluttership vault setupIt asks for your Play Store service-account JSON, App Store .p8 key, and the matching
IDs (the next section shows exactly how to get each one), then asks you to set a vault
password.
fluttership vault verify # test your password + see what's configured
fluttership vault status # info only — no password neededChoose a strong vault password and store it in a password manager. If you share the vault with a team, never send the password over chat or email — use a shared password-manager item.
To upload builds, FlutterShip needs store credentials in the vault. This section is a
complete, click-by-click guide to finding every value. Run fluttership vault setup
once you have them ready.
You need an App Store Connect API key, which gives you four things: a .p8 key file,
a Key ID, an Issuer ID, and your app's Bundle ID.
Who can do this? You need the Admin or Account Holder role in App Store Connect to create an API key. If you don't have it, ask whoever owns your Apple Developer account to follow these steps and hand you the four values.
- Go to appstoreconnect.apple.com and sign in.
- Click Users and Access (top menu).
- Open the Integrations tab, then select App Store Connect API in the sidebar (on some accounts this is shown directly as a Keys tab).
At the top of the Keys page you'll see Issuer ID — a long UUID like
69a6de70-1234-47e3-e053-5b8c7c11a4d1.
➡️ This is your Issuer ID. Copy it.
- Click the (+) button to generate a new key.
- Give it a name (e.g.
FlutterShip). - Set Access to App Manager (enough to upload builds).
- Click Generate.
- The new key appears in the list with a 10-character Key ID like
6A3W84GZKK.
➡️ This is your Key ID. Copy it.
Next to your new key, click Download API Key. You'll get a file named
AuthKey_XXXXXXXXXX.p8.
⚠️ Apple lets you download this file only once. Save it somewhere safe (a password manager or secure folder). If you lose it, you must revoke the key and create a new one. FlutterShip will copy it into the encrypted vault, so you don't need to keep it in your project — and you should never commit it to git.
➡️ This is your .p8 key file.
Your Bundle ID is your app's identifier, e.g. com.yourcompany.yourapp. It must match:
- Xcode → Runner target → Signing & Capabilities → Bundle Identifier, and
- the app record in App Store Connect → My Apps.
Run fluttership status and FlutterShip prints the bundle id it detected — use that.
➡️ This is your Bundle ID.
Before your first upload, the app has to exist in App Store Connect:
My Apps → (+) → New App, using the same Bundle ID. Uploads go to TestFlight via
xcrun altool, so iOS publishing requires macOS + Xcode.
| Value | Example | Where it came from |
|---|---|---|
.p8 key file path |
~/Downloads/AuthKey_6A3W84GZKK.p8 |
Step 4 |
| Key ID | 6A3W84GZKK |
Step 3 |
| Issuer ID | 69a6de70-1234-47e3-… |
Step 2 |
| Bundle ID | com.yourcompany.app |
Step 5 |
You need a service-account JSON with Google Play Developer API access. This is a two-place setup: you create the account in Google Cloud Console and grant it permission in the Play Console.
Who can do this? Creating and linking a service account needs the Play Console account owner (or an admin the owner has allowed to manage API access). If that's not you — for example your company's Android lead owns the account — ask them to do Steps 1–4 and send you the downloaded JSON file. They never have to touch FlutterShip; you just add the JSON to your vault.
- Go to play.google.com/console and sign in.
- In the left sidebar, open Setup → API access.
- If prompted, accept the terms and link a Google Cloud project (Play will create one for you, or you can pick an existing one).
- On the API access page, find Service accounts and click Create new service account.
- Follow the link to Google Cloud Console that Play opens for you.
- In Google Cloud Console: Create Service Account → give it a name
(e.g.
fluttership-uploader) → Done. You don't need to grant Cloud roles here.
- Still in Google Cloud Console, open your new service account.
- Go to the Keys tab → Add key → Create new key.
- Choose JSON and click Create. A file like
myapp-uploader-1a2b3c4d5e6f.jsondownloads automatically.
➡️ This is your service-account JSON. Keep it secret — FlutterShip stores it in the encrypted vault, and it must never be committed to git.
Back in Play Console → Setup → API access, your new service account now appears in the list. Click Manage Play Console permissions (or Grant access) next to it and give it:
- Releases → Release apps to testing tracks ✅ (needed for
internal/alpha/beta) - Releases → Release to production ✅ (only if you'll push to production)
- Release → Manage store presence ✅ (recommended)
Click Invite user / Apply and wait a few minutes for permissions to propagate.
Granting access only in Google Cloud is not enough — the service account must be granted access inside Play Console too. Missing this is the #1 cause of the
403 The caller does not have permissionerror (see Troubleshooting).
In console.cloud.google.com → APIs & Services → Library, search for Google Play Android Developer API and click Enable (for the same Cloud project Play linked in Step 1).
The app has to exist in Play Console with your package name, and Google requires at least one manual upload through the browser before the API will accept uploads for a brand-new app. After that first manual AAB/APK, FlutterShip can take over.
| Value | Example | Where it came from |
|---|---|---|
| Service account JSON path | ~/Downloads/myapp-uploader-1a2b3c.json |
Step 3 |
| Package name | com.yourcompany.app |
your app's applicationId |
Play uploads default to the
internaltrack (safest). Change it withfluttership configor by telling the agent "release to production". The Play Store rejects duplicate version codes, so FlutterShip prompts you to confirm the version + build number for every store-bound build.
fluttership "release 2.0.0 to TestFlight and Play Store"FlutterShip unlocks the vault (you enter the vault password), uploads, and records it as a release in your history. A build that was only compiled — never uploaded — stays a "build", not a "release".
Solo — everything lives on your machine. You own the vault and the releases.
Team — a shared-vault workflow:
- The lead runs
vault setuponce, thenvault exportto produce the encryptedfluttership-vault.encfile and hands it to members. - Members run
vault import <file>and can build + submit, but never manage or re-export credentials. - The vault file is safe to share (it's AES-256-GCM encrypted and useless without the password). Share the password only through a team password manager.
fluttership mode team lead # you manage credentials
fluttership mode team member # you import the lead's vault- Python 3.9+
- Flutter SDK (on your
PATH) - git and rsync (rsync powers the isolated parallel-build sandboxes)
- For iOS builds/uploads: macOS + Xcode + CocoaPods
- An LLM API key (Gemini free tier works great)
Run fluttership doctor any time to check all of the above.
- Credentials are never committed — the vault lives in
~/.fluttership/, outside your repo, and.p8/.jks/.json/.envfiles are git-ignored by default. - The AI never sees secrets — it receives only success/fail status and file paths, never key material.
- Encryption at rest — AES-256-GCM with a memory-hard key derivation; the key is derived from your password and never stored. Decrypted only in memory; temp files wiped after use.
- Strong password required — the encrypted file's security reduces to password strength.
- Audit trail — vault unlocks, exports, imports, and uploads are logged.
- Built artifacts can embed compiled code and
dart-definevalues, sofluttership-dist/is git-ignored automatically — never commit your builds.
If you fork or publish this repo, scrub any API keys from your git history before making it public — a key committed even once stays in the history until rewritten.
Most problems fall into one of these. If yours isn't here, please open an issue — and if you solve something new, a PR that adds it to this list is hugely appreciated.
Play Store: 403 The caller does not have permission
The service account can authenticate but isn't allowed to touch this app. Check, in order:
- Permission granted inside Play Console, not just Google Cloud — Play Console → Setup → API access → your service account → grant Release apps to testing tracks (and Release to production if needed). Wait ~5–10 minutes after granting.
- The Play Developer API is enabled for the linked Cloud project (Cloud Console → APIs & Services → Library → Google Play Android Developer API → Enable).
- Package name matches exactly — compare
fluttership vault verifywithfluttership statusand with the app in Play Console. - The app exists and had one manual upload — a brand-new app needs its first AAB/APK uploaded through the browser before the API will accept uploads.
App Store: Failed to load AuthKey file (-43)
xcrun altool looks for the key in specific folders. FlutterShip handles this by copying
your vault's .p8 to ~/.appstoreconnect/private_keys/AuthKey_<KeyID>.p8 for the upload
and deleting it right after. If you hit this error:
- Make sure the Key ID in the vault matches the
.p8file you uploaded during setup (re-runfluttership vault setupif unsure). - Confirm your Issuer ID is correct (top of the App Store Connect Keys page).
App Store: bundle id does not match / no suitable application records
The app must already exist in App Store Connect with the same Bundle ID as your Xcode project. Create it under My Apps → (+) New App, then retry.
Gemini: 429 RESOURCE_EXHAUSTED or limit: 0
Your Gemini free-tier quota is exhausted or unavailable for that model/region. Options:
- Switch model in-session with
/model, or setGEMINI_MODELin~/.fluttership/.env. - Use a different provider (
groqis free and fast): setLLM_PROVIDER=groqandGROQ_API_KEY=....
Gemini: 403 API key ... reported as leaked
Google auto-disables any key that was ever pushed to a public repo. Create a new key
at aistudio.google.com, update your .env, and make sure
.env is git-ignored. If it was committed, rewrite history to remove it.
--flavor build fails: "does not define any product flavors"
Your project doesn't use flavors — it may use --dart-define, --dart-define-from-file,
or plain builds. Run fluttership status to see the detected pattern. FlutterShip picks
the right command automatically; if it guessed wrong, tell the agent the values explicitly
(e.g. "build LIVE apk with flavorType=LIVE").
fluttership: command not found
The Python scripts dir isn't on your PATH. Install with pipx, or run
python3 -m fluttership. See Quick start for the
permanent PATH fix.
FlutterShip is open source and built for the whole Flutter community — contributions of every size are welcome, from fixing a typo to adding a new provider. If you're reading this far, you're exactly the kind of person the project needs. 💙
A quick ⭐ on the repo is the single most helpful thing you can do — it's how other Flutter devs discover the project.
- Add a fix you discovered to the Troubleshooting section.
- Improve error messages or the plain-English build-failure explanations.
- Test on a build setup we don't cover yet (different flavor/
dart-definelayouts) and report what happened. - Improve docs — clearer wording, screenshots, translations.
# 1. Fork the repo on GitHub, then clone your fork
git clone https://github.com/<your-username>/fluttership.git
cd fluttership
# 2. Create an isolated environment
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 3. Install in editable mode with all extras
python3 -m pip install -e ".[all]"
# 4. Sanity-check your setup
fluttership doctorBecause it's installed with -e (editable), your code changes take effect immediately —
no reinstall needed.
fluttership/
├── agent/
│ ├── agent.py # the AI agent — orchestrates tools via the LLM
│ ├── llm_provider.py # provider abstraction (gemini/groq/anthropic/openai)
│ └── tools/
│ ├── runner.py # safe subprocess + live-streaming build output
│ ├── reader.py # reads pubspec / gradle / key.properties / xcodeproj
│ ├── validator.py # pre-flight checks
│ ├── builder.py # build-pattern detection + build engine
│ ├── ios_checker.py # iOS pre-flight (Xcode/cert/pods)
│ ├── vault.py # encrypted credential vault
│ └── submitter.py # App Store + Play Store submission
├── cli/
│ └── main.py # CLI entry point + subcommands
└── setup.py
New to the codebase? A great way in is to read agent/agent.py (how the agent decides
what to do) and then the tool it calls in agent/tools/.
- Create a branch:
git checkout -b fix/short-description - Make your change. Keep it focused — one logical change per PR.
- Match the existing style: clear names, docstrings on functions, small focused functions.
- Test it against a real Flutter project (
fluttership doctor, then a build). - Commit with a clear message:
git commit -m "Fix: <what and why>" - Push and open a Pull Request against
main, describing what you changed and why, plus how you tested it. Screenshots/terminal output are very welcome.
Open an issue with:
- What you ran (the exact command) and what you expected.
- What actually happened — paste the terminal output (redact any keys/passwords).
- Your environment: OS,
python3 --version,flutter --version, FlutterShip version.
- Never commit secrets — API keys,
.p8/.jks/.jsonfiles, passwords. Double-check your diff before pushing. - Be kind and constructive. We're all here to make Flutter releases less painful.
To remove FlutterShip completely from your machine:
fluttership uninstallThis deletes ~/.fluttership/ — your config, the encrypted vault (signing
credentials, store API keys), and your release history — and then uninstalls the
pip package. It asks you to type uninstall to confirm, because wiping the vault
is irreversible.
fluttership uninstall --keep-data # uninstall the tool, keep your vault + history
fluttership uninstall --yes # skip the confirmation promptWant to remove only build artifacts from a project (not the whole tool)? Use
fluttership cleaninstead.
MIT
Built by Abu Sayed Chowdhury — a Flutter dev who got tired of spending 3 hours on a 10-minute release.
If FlutterShip helps you, please ⭐ star the repo and share it with your team.