How to automate app icon export with ci/cd scripts for ios and android
- Step 1Pair the runner and confirm Developer tier — Install and pair
@jadapps/runner. App icon generation requires the Developer plan. In CI, run the runner as a service on the build agent (it listens locally, e.g.127.0.0.1:9789). Free/Pro tiers cannot run this tool. - Step 2Introspect the schema — Call
GET /api/v1/tools/svg-app-icon-gen(with your API key). AssertrunnerBacked: true,apiAvailable: false,output.type: zip,minTier: developer, and thatinput.optionsis empty. This makes your pipeline fail fast if the contract changes. - Step 3Detect brand-SVG changes — In GitHub Actions, gate the job on a path filter:
on: push: paths: ['assets/brand.svg']. Or hash the SVG and skip when unchanged. Icons only need regenerating when the mark moves. - Step 4Run generation through the runner — POST the SVG to your local runner at
127.0.0.1:9789/v1/tools/svg-app-icon-gen/runas multipart form-data with afilesfield (the runner mirrors the in-browser job; theoptionsfield is unnecessary because the schema has none). The runner returns the ZIP. - Step 5Unpack into the project tree — Extract
ios/AppIcon.appiconset/over<App>/Assets.xcassets/AppIcon.appiconset/, andandroid/mipmap-*/ic_launcher.pngintoapp/src/main/res/mipmap-*/. Uploadplay-store-icon.pngto Play Console via your existing release step (or commit it as an artifact). - Step 6Add the manual post-steps the tool skips — Flatten
icon_1024x1024.pngif your SVG is transparent (App Store rejects alpha there), and if you ship adaptive icons, leave your hand-authoredic_launcher.xml+ foreground/background untouched — overwrite only the legacyic_launcher.png. Then commit with a message likechore: regenerate app icons from brand.svg.
The runner-backed API contract
What GET /api/v1/tools/svg-app-icon-gen reports and how a job is actually executed. Values reflect the tool's registered schema.
| Field / step | Value | Meaning for your pipeline |
|---|---|---|
runnerBacked | true | Execution goes through your paired runner, not a JAD-hosted endpoint |
apiAvailable | false | The public /run endpoint will not process uploads — it returns pairing instructions |
runnerMode | headless-browser | The runner launches a short-lived Chromium session to do Canvas rasterisation + ZIP |
input.options | [] (empty) | Nothing to parameterise — the job is just the SVG file |
output.type | zip | Expect a ZIP with ios/, android/, and the source .svg |
minTier | developer | Free/Pro keys are blocked; pair a Developer-tier runner |
ZIP-to-project path mapping
Deterministic extraction targets — no renaming required.
| ZIP path | Destination | Notes |
|---|---|---|
ios/AppIcon.appiconset/ | <App>/Assets.xcassets/AppIcon.appiconset/ | Replace the folder wholesale; Contents.json is included |
android/mipmap-<density>/ic_launcher.png | app/src/main/res/mipmap-<density>/ic_launcher.png | Overwrite only the legacy PNG; keep any adaptive XML you authored |
android/play-store-icon.png | Play Console upload / release artifact | 512×512 high-res listing icon |
<stem>.svg | (optional) drop or archive | Original source; exclude it from the app bundle |
Cookbook
Pipeline snippets. They assume a Developer-tier runner is reachable on the build agent.
Assert the contract before running
A defensive first step so the job fails loudly if the tool's execution model ever changes.
curl -s -H "Authorization: Bearer $JAD_API_KEY" \
https://jadapps.com/api/v1/tools/svg-app-icon-gen \
| jq '{runnerBacked: .execution.runnerBacked,
apiAvailable: .execution.apiAvailable,
mode: .execution.runnerMode,
options: .input.options,
out: .output.type,
tier: .minTier}'
# expect: runnerBacked true, apiAvailable false,
# mode "headless-browser", options [], out "zip",
# tier "developer"Run the job through the local runner
The runner mirrors the in-browser job. No options field is needed because the schema has none.
# runner listens locally after pairing (default 127.0.0.1:9789) curl -s -X POST \ -F "files=@assets/brand.svg;type=image/svg+xml" \ http://127.0.0.1:9789/v1/tools/svg-app-icon-gen/run \ -o out/icons.zip unzip -o out/icons.zip -d out/icons
GitHub Actions: regenerate on SVG change only
Path-filtered workflow so unrelated pushes skip icon work.
name: app-icons
on:
push:
paths: ['assets/brand.svg']
jobs:
icons:
runs-on: self-hosted # agent with a paired Developer runner
steps:
- uses: actions/checkout@v4
- run: |
curl -s -X POST -F "files=@assets/brand.svg" \
http://127.0.0.1:9789/v1/tools/svg-app-icon-gen/run \
-o icons.zip
unzip -o icons.zip -d generated
- run: |
cp -r generated/ios/AppIcon.appiconset/* \
ios/App/Assets.xcassets/AppIcon.appiconset/
cp -r generated/android/mipmap-* \
android/app/src/main/res/Post-step: flatten the 1024 alpha
The generator preserves transparency, so the App Store 1024 may need flattening. Do this in your pipeline, not in the tool.
# App Store rejects a transparent 1024 marketing icon magick generated/ios/AppIcon.appiconset/icon_1024x1024.png \ -background "#0B0B0F" -alpha remove -alpha off \ ios/App/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png # (or bake an opaque <rect> into brand.svg upstream instead)
Commit generated icons as version-controlled assets
Icons are binary build inputs; commit them rather than regenerating at build time.
git add ios/App/Assets.xcassets/AppIcon.appiconset \
android/app/src/main/res/mipmap-*
git commit -m "chore: regenerate app icons from brand.svg"
# rationale: a build must not depend on a live runner being up;
# regenerate only when assets/brand.svg changesEdge cases and what actually happens
Calling the public /run endpoint with files
400 / no uploadThe hosted /run endpoint is not an upload API — it returns pairing instructions, because JAD never receives your files. Always POST to your local paired runner (127.0.0.1:9789/v1/tools/svg-app-icon-gen/run). The GET schema reports apiAvailable: false to signal this.
Runner paired on a Free or Pro key
Tier blockedApp icon generation requires the Developer plan (minTier: developer). A lower-tier key is rejected (the runner surfaces a tier-limit signal). Pair a Developer-tier runner on the build agent.
Passing an options payload
IgnoredThe schema's option list is empty, so there is nothing to configure. Sending an options field does nothing useful; omit it. If you build the request generically from the schema, you will correctly produce no options.
SVG references remote fonts or bitmaps
May failRasterisation loads the image with crossOrigin="anonymous"; external resources can taint the canvas and abort the PNG encode. Inline everything before the job — outline text with svg-font-to-path and base64-embed any bitmaps so the SVG is self-contained in CI.
Build depends on a live runner
FragileIf you generate icons inside the app build and the runner is down, the build fails. Decouple: regenerate on SVG change, commit the PNGs, and let normal builds consume the committed assets.
Adaptive icon XML overwritten by the legacy PNG
Watch closelyThe generator only emits legacy ic_launcher.png. If your project ships an adaptive icon (mipmap-anydpi-v26/ic_launcher.xml + foreground/background), copy only the density PNGs and leave the adaptive files alone, or your adaptive icon reverts to legacy.
Headless rasterisation differs from local Xcode preview
ExpectedThe runner uses Chromium's SVG renderer (the same engine as the in-browser tool). Subtle anti-aliasing or filter rendering can differ from a native preview. Treat the runner output as the source of truth and review the 1024 at full size.
Schema drift breaks an assumption
Fail fastIf a future change altered the size set or execution mode, a blind pipeline would silently ship wrong assets. The introspection step (asserting runnerBacked, output.type, empty options) makes the job fail loudly instead.
Frequently asked questions
Is there a hosted API that takes my SVG and returns icons?
No. The tool is runner-backed: GET /api/v1/tools/svg-app-icon-gen reports apiAvailable: false and runnerBacked: true, and the public /run endpoint returns pairing instructions rather than processing uploads. Execution happens through your paired @jadapps/runner locally, so files never reach JAD servers.
Where do I actually POST the job?
To your local runner, e.g. http://127.0.0.1:9789/v1/tools/svg-app-icon-gen/run, as multipart form-data with a files field containing the SVG. The runner launches a headless browser, runs the same in-browser tool, and returns the ZIP.
What options should I send?
None. The schema's option list is empty — the job is just the SVG and always yields the full iOS + Android set. You can omit the options field entirely.
How do I know the schema before coding the request?
GET /api/v1/tools/svg-app-icon-gen with your API key returns execution (runnerBacked, apiAvailable, runnerMode), input.options (empty), output.type (zip), and minTier (developer). Assert these in a pre-flight step so drift fails the job.
Which tier is required to automate this?
Developer. App icon generation is a Developer-plan feature; pair a Developer-tier runner on your CI agent. Lower tiers are rejected with a tier-limit signal.
Should icons be committed or generated at build time?
Committed. They are binary build inputs your IDE expects at fixed paths. Generating during the app build couples your build to the runner being up. Regenerate only when assets/brand.svg changes, commit the PNGs, and build against them.
Does the runner output need any manual fix-ups?
Two, depending on your needs: flatten icon_1024x1024.png if your SVG is transparent (App Store rejects alpha there), and preserve any hand-authored Android adaptive files (overwrite only the legacy ic_launcher.png). The generator does neither for you.
Can the runner fail on certain SVGs?
Yes — SVGs that pull in external fonts/bitmaps can taint the canvas and abort PNG encoding. Inline all assets first; outline text with svg-font-to-path and embed bitmaps as base64 so the SVG is self-contained.
Can I integrate this into a Fastlane lane?
Yes — wrap the runner call in a custom action that POSTs assets/brand.svg to the local runner, unzips into the catalog and res tree, and runs your flatten step. Chain it before your upload lane. The transport is a plain multipart POST, so any task runner works.
Is the brand SVG ever uploaded to JAD?
No. Because the tool is runner-backed, processing happens on your machine/agent via the runner's local headless browser. JAD's servers only expose the schema; they never receive your artwork.
Should I normalise or shrink the SVG in CI before generating?
It can help reproducibility. Square a non-square brand mark with svg-viewbox-fixer and shrink a heavy export with svg-pro-minifier as runner steps upstream of the icon job — both are also runner-backed, so the whole chain stays local.
Can the same pipeline also produce web favicons?
Not from this tool — it is launcher-only. Add a parallel runner step for svg-favicon-master if your release also needs a favicon.ico (16/32/48), web PNGs, and <link> tags. It shares the same runner-backed, in-browser rasterisation model.
Privacy first
Every JAD SVG tool runs entirely in your browser using the DOM API and Canvas. Your SVG files never leave your device — verified by zero outbound network requests during processing.