Make an app store-ready from your coding agent
Your agent reads the app in front of it — SDKs, permissions, bundle id — and drafts every compliance page from what it finds. You read the drafts and publish them. The agent cannot.
Before you start
You need Claude Code and an AppFoyer account. The free plan is enough: create one. There is no key to create — the agent asks you to approve it in the browser the first time it connects.
No coding agent at all? Six fields in the browser give you the same five pages without the plugin.
Not on Claude Code? The plugin needs it, but the privacy and terms pages do not: their editors in the dashboard have a prompt, filled in with your app's details, that you run in any assistant and paste back — printed in full in the privacy policy guide and the terms guide.
Install
claude
/plugin marketplace add appfoyer/claude-plugin
/plugin install appfoyer@appfoyer No account is needed to start: the agent reads your build, shows what it found and asks its questions on your machine, and asks you to connect only when there is something to save. The first time it calls AppFoyer, your browser opens on an approval page: sign in or create a free account, check the name and where the browser returns to, and approve. Claude Code keeps the connection and renews it on its own; nothing is copied, nothing is stored in your shell. If the browser does not open, run /mcp, pick appfoyer and choose Authenticate. In the Claude Code desktop app, connect once this way from a terminal, then start a new session there: a session that was already running does not see the new connection. Disconnect it on the dashboard's Agent page and the next call fails immediately. Claude Code does not refresh a third-party marketplace by itself: in /plugin → Marketplaces → appfoyer, choose Enable auto-update to receive new versions.
No browser on that machine — a remote server, a container, a scripted run? Use an API key instead: create one under Agent → API key — for CI and headless use, install the plugin as above for the workflow, and add the server once more with the key in its header:
export APPFOYER_API_KEY=cpk_…
claude mcp add --transport http appfoyer-key https://app.appfoyer.com/mcp --header "Authorization: Bearer $APPFOYER_API_KEY" The plugin reads the key from that environment variable only — the skill never sees it, and it travels only in the Authorization header Claude Code fills in.
Another MCP client — claude.ai, Claude Desktop, Cursor, VS Code — can connect to https://app.appfoyer.com/mcp the same way, with the same approval page. It gets the tools; the step-by-step workflow that reads your build files ships with the Claude Code plugin.
What to say
Run these inside your app's repository, not in an empty directory:
| Say | What happens |
|---|---|
Make this app store-ready | Scans the build files, confirms the facts it could not derive, drafts every page, returns a review link. |
Is this app store-ready? | Read-only audit against the compliance checklist. Writes nothing. |
Wire the store-ready links into the app | After you publish: proposes the in-app privacy and support links as a diff you review. |
Add the AppFoyer drift check | Proposes a GitHub workflow that flags pull requests adding an SDK or permission the pages may not cover. How it works. |
Examples
Say it in your own words; these are the cases the skill handles.
One app, first run
> Make this app store-ready - Lists what it found in the build files — SDKs, permissions, bundle id — each with file and line.
- Asks only what it could not find: company name, support email, jurisdiction, store links. At most two rounds.
- Creates the app, uploads the icon from the repository and drafts all five pages.
- Ends with a review link. Nothing is public until you publish there.
Run it again after a change
> Make this app store-ready - Finds the existing app by its application id and compares the new facts with the stored ones.
- Redrafts only the pages the change touches, such as privacy after a new analytics SDK.
- Leaves the icon and the address alone unless you ask.
Several flavors, named in the prompt
> Make com.acme.weather.free and com.acme.weather.pro store-ready - Creates two apps and asks nothing about the selection.
- Reads each flavor's own folder, name and dependencies. An ad SDK used only by the free flavor is not put in the pro flavor's privacy policy.
- Returns a review link and a table of console URLs for each flavor.
Several flavors, not named
> Make this app store-ready - Lists the flavors with their application ids, all pre-selected, and you pick which ones get a site.
- Build types such as debug and staging are listed as excluded, not offered.
Brand × environment dimensions
> Make every brand store-ready - Lists combinations (brandAProd, brandBProd), not single flavors.
- Offers only the production environment. dev and qa never ship to a store.
- Reads the app name from Gradle (resValue, manifestPlaceholders) or moved source sets when the build puts it there.
- If it cannot tell whether a dimension is environments or brands, it asks.
Custom address (Pro and Advanced)
> Make this app store-ready - Builds the address from the app's English name, for example weather-now.
- If that one is taken or too close to a brand name, it tries the next one — weather-now-app, acme-weather — at most five times, then asks you.
- On the free plan it keeps the app-… address and says the custom one comes with Pro.
- The app-… address keeps serving the same site.
Change an address you already use
> Change the address to weather-radar - Asks first. The old custom address is released at once, and a store listing that still names it stops working.
- Changes it only after you say yes.
Update the icon
> Update the app icon - Finds the launcher icon: the Play 512 icon, the largest mipmap image, or the iOS AppIcon marketing image — the flavor's own one first.
- If it is over 512 KB, it shrinks a copy in a temporary folder. Your repository is not touched.
- Tells you which file it uploaded. On a published app the new icon is live at once.
An app with ads
> Make this app store-ready. AdMob publisher id pub-0000000000000000 - Writes app-ads.txt from the lines you give. The publisher id is not in your code, so it never guesses one.
- Reminds you to set the site as the developer website in the store listing.
- AdMob has verified app-ads.txt files on our addresses; allow it up to 24 hours to crawl.
Keep the pages in sync with the build
> Add the AppFoyer drift check - Every run of “make this app store-ready” writes appfoyer.json at the repository root: the SDKs, data types and permissions the pages were written from. It is written by the same scanner the check runs, never by hand.
- This prompt shows a GitHub workflow for the free appfoyer/check-action and writes .github/workflows/appfoyer.yml when you say yes. It never overwrites one you already have.
- From then on every pull request lists what it added or removed since the pages were written, with file and line. Running the plugin again updates the drafts and appfoyer.json.
- Nothing is committed for you. The guide on keeping your privacy policy in sync has the details and the setup for other CI systems.
Check without changing anything
> Is this app store-ready? - Runs the checklist for each matching app and reports what is missing. Writes nothing.
- A flavor with no app yet is reported as missing.
Put the links into the app
> Wire the store-ready links into the app - Needs published pages.
- Proposes a diff for the privacy and support links in your settings screen or strings. Applies it only when you say yes, and never commits.
What leaves your machine
Only derived facts:
- the app's name, platform and bundle or application id;
- the names of the SDKs and permissions found in your build files;
- the answers you confirm — company name, support email, jurisdiction, store URLs;
- the page text the agent drafts, which is stored on your account as a draft;
- the app's own icon, sent once through a single-use upload link.
No other file, never source code, never secrets or keys found in the repository. What the agent sends is what a privacy policy has to state anyway.
One file is written into your repository and stays there: appfoyer.json, the record of what the pages were written from, created by the scanner bundled in the plugin. It is not sent anywhere. The agent never commits it, or anything else — you do.
What the agent cannot do
Whether it connects through the browser or with an API key, the plugin holds an agent credential — so publishing is not available to it at all. This is enforced on the server, not by asking the agent nicely:
- There is no publish tool. The agent writes drafts; your live pages do not change.
- Two things have no draft: the icon and the custom address. On a published app they change the live site at once, and the agent tells you when it set them. It cannot replace a custom address you already use without your yes.
- The REST route that publishes a page refuses agent credentials outright — session only.
- Every page still passes the same content screening at publish time as a hand-written edit.
- Every agent call is recorded in the account's audit log with the connection or key that made it.
So the worst case for a compromised connection or key is drafts to discard, a wrong icon or a new address — not a page going public in your name.
The tools it gets
The plugin connects to an MCP endpoint that exposes exactly these:
| Tool | What it does | |
|---|---|---|
list_apps | Read | Every app on the account with its slug, public URL, and whether anything is published. |
get_app | Read | One app: settings, stored data-collection facts, and each page's live text, publish state and pending draft. |
create_app | Write | Creates an app with template pages. Nothing is public. |
create_apps | Write | Creates up to ten apps in one call, one per product flavor. Each starts unpublished. |
update_app | Write | Changes settings, the stored data-collection facts or the SDK list. Never edits page text. |
set_page | Write | Writes the draft for one page. The live page is untouched. |
set_ad_lines | Write | Replaces app-ads.txt. Only used when an ad SDK is present and you supplied publisher ids. |
get_checklist | Read | The server-computed store-readiness checklist, plus which pages still have unpublished drafts. |
get_store_forms | Read | Draft answers for Google Play's Data safety form and Apple's App Privacy details, from the facts and the SDK list. You enter them in the console yourself. |
request_publish | Write | Says drafting is finished and returns the review URL. Publishes nothing. |
set_icon | Write | Returns a one-time upload URL for the app's icon. Live at once — an icon has no draft. |
check_slug | Read | Checks a custom address (Pro and Advanced) against the same rules as the dashboard. |
set_slug | Write | Claims a custom address. Live at once. Refuses to replace one you already have unless you agreed. |
When something goes wrong
| Symptom | Cause |
|---|---|
| The tools show "needs authentication" | The connection was never approved, or it was disconnected. Run /mcp, pick appfoyer and choose Authenticate. |
| The approval page says the request cannot be used | The link expired or was opened twice. Start again from the editor; nothing was connected. |
unauthorized with an API key | The key was revoked in the dashboard, or it belongs to a different account. Create a new one and export it again. |
| Publishing returns a quota error | The plan's published-app limit is used up. The app and its drafts are kept — unpublish another app or upgrade, then publish from the review screen. |
| A draft comes back rejected or held | Content screening matched something. The reasons list what matched and how to fix it; edit and re-send. Nothing public changed. |
One thing this does not change
The drafts are templates completed from your code, not legal advice. An agent writing them quickly does not make them true — read what it wrote before you publish, especially the list of what your app collects. You are the one the store holds responsible.
Questions: support@appfoyer.com.