App icons + cover image + screenshots
How to ship a real icon (and optional cover image / screenshots) with your bundle so the catalog renders them at a versioned CDN URL.
You scaffold a project, you publish, you open the catalog — and the icon is the i99dash placeholder. This recipe is the fix.
TL;DR
// manifest.json
{
"icon": "./assets/icon.svg",
"coverImage": "./assets/cover.jpg",
"screenshots": ["./shots/01.png", "./shots/02.png"]
}Every path is relative to your bundle's dist root (whatever the catalog API ends up serving). The publish flow rewrites these to absolute, versioned CDN URLs at submit time:
./assets/icon.svg
→ https://miniapps.i99dash.app/<your-app>/<version>/assets/icon.svgYou don't upload icons separately. They ship in the same tarball as your code.
Where the file goes (per framework)
The manifest path ./X must resolve to dist/X after build. Different
frameworks lay out static files differently:
| Framework | Put your icon at | Manifest reads |
|---|---|---|
Vanilla (i99dash init) | src/assets/icon.svg | ./assets/icon.svg |
| Next.js | public/assets/icon.svg | ./assets/icon.svg |
| Vite | public/assets/icon.svg | ./assets/icon.svg |
| Nuxt | public/assets/icon.svg | ./assets/icon.svg |
The rule: files inside the framework's "static-assets" folder (the
one whose contents end up at the dist root) match the manifest path
verbatim. Next.js's public/X becomes dist/X, so manifest reads
./X.
Locked specs
Validated by i99dash validate (CLI side, dimension-sniff via
image-size) and re-validated by the publish backend (magic-byte
check on the extracted bundle — catches a bash script renamed to
icon.png that the CLI's extension-only check misses).
| Asset | Format | Dimensions | Max size | Required |
|---|---|---|---|---|
icon | PNG, SVG | 256×256 (square) | 100 KB | yes |
coverImage | PNG, JPG, WebP | 1280×720 (16:9) | 500 KB | no |
screenshots[] | PNG, JPG, WebP | ≤ 1920×1080 | 800 KB ea | no, ≤ 8 |
SVG icons skip the dimension check (they're scalable).
What the publish flow does
i99dash build— copies your tree (or runs the framework build); the asset-presence check verifies each manifest path resolves insidedist/.i99dash publish— uploads the tarball, the backend extracts it tohttps://miniapps.i99dash.app/<app>/<version>/....- Magic-byte check — server reads the first 16 bytes of every declared asset (captured inline during extraction; no extra roundtrip) and verifies the signature matches the declared format. Submit fails with a clear error citing the field if not.
- URL rewrite — relative paths in the manifest become absolute
https://miniapps.i99dash.app/<app>/<version>/...URLs, and that's what gets stored inmini_app_bundles.manifest_jsonand served byGET /api/v1/mini-apps/catalog.
The version is in the URL, so a v1.2.0 → v1.2.1 publish gets a
fresh URL even if the icon bytes are the same — cache invalidation
without a server-side hash dance.
Errors you might see
| Error | What's wrong |
|---|---|
icon must be a relative path… | Don't put a full URL — use ./assets/icon.svg |
icon extension must match … | Icon must be PNG or SVG (cover/screenshots: JPG/PNG/WebP) |
icon: file not found at … (build) | The framework didn't include the file — wrong folder |
<field> magic-byte check failed | The file has the right extension but the wrong contents — likely a Git-LFS pointer or a copy gone wrong |
extension must match (cover) | WebP / JPEG ok for cover; SVG isn't (size cap is on bytes, not vectors) |
Updating an icon
Bundle paths are version-pinned. To replace an icon:
- Update the file (
src/assets/icon.svgor wherever). - Bump
manifest.version(e.g.0.1.4). i99dash publish.
The new version's URL is fresh, the old version's URL still serves the old icon. No "edit storefront without rebuild" flow yet — that ships in a follow-up. For v1, ship a version bump.
Related
- Manifest reference — the full schema with every field.
- Categories — the closed enum that drives the head-unit chip rail.
Recipes
Copy-paste end-to-end builds and patterns. Each recipe is a full, runnable example.
Cabin dashboard with i99dash/react
A four-tile dashboard that combines car status, climate, AQI, and now-playing — using i99dash/react hooks. Shows the per-family fallback pattern + how `client.has` lets you hide tiles on hosts that don't ship a family.