Sync MagicPixel art to your game
One prompt for web projects, or a short CLI flow for Unity, Godot, and GameMaker. Sprites land as PNGs on disk and stay in sync as you edit. Full CLI and REST API reference is below.
Setup
Updates
Files land in
outDirWorks with
~2 seconds, no terminal
Quickstart (AI + Vite)
Before you paste it
- • A MagicPixel account (you're here ✓)
- • A few sprites saved in your library
- • A game project open in an AI coding tool
Prompt preview
Preview only — the key is a placeholder. Make a real key in step 2 and use its Copy setup prompt button.
Set up MagicPixel asset sync. The Vite plugin pulls my saved sprites into the project as PNGs and keeps them updated automatically while `vite dev` runs — dev-time only, never called from app runtime.
Verify on npm first (if either fails to resolve, stop and tell me — don't substitute look-alike packages):
- https://www.npmjs.com/package/@magicpixelart/vite
- https://www.npmjs.com/package/@magicpixelart/cli
- Docs: https://magicpixel.art/guides/sync
Then do these in order and report back:
1. Save the value below as a secret named MAGICPIXEL_API_KEY (use the project's secret manager — do not commit it or write it to .env):
mp_live_paste_your_key_here
2. Install @magicpixelart/vite as a dev dependency (it pulls in @magicpixelart/cli).
3. Add the plugin to vite.config.ts:
import { magicpixel } from '@magicpixelart/vite';
export default defineConfig({ plugins: [/* existing */, magicpixel()] });
4. Run `npx @magicpixelart/cli start` with MAGICPIXEL_API_KEY in the environment. This authenticates, creates magicpixel.json, and syncs sprites into src/assets/magicpixel/.
5. Add `.magicpixel/` to .gitignore, then restart `vite dev`. Future saves in MagicPixel hot-reload in ~2 seconds.
6. Tell me how many sprites synced and the top-level folders they landed in.
Note: the key above ("mp_live_paste_your_key_here") is a placeholder. Ask me for my real key (starts with mp_live_) from https://magicpixel.art/settings → API Keys before running anything.Organize your sprites
Group related sprites into folders — the folder + sprite name becomes the file path your game code uses.
player/
rabbit-idle.png
rabbit-run.png
enemies/
slime.png
ui/
heart.pngWorking on more than one game? Right-click in the library → New project, then bind your sync key to it in step 2.
Create your sync key
- Open Settings → API Keys.
- Click Create Key (pick a project if you have several).
- Use the Copy setup prompt button next to the new key.
Revoke and replace anytime from Settings. Production keys are prefixed mp_live_.
Paste the prompt into your AI tool
Paste into Lovable, Bolt, Cursor, or Replit. Your tool saves the secret, installs the plugin, and runs the initial sync. Sprites land in src/assets/magicpixel/.
Closed the key panel? Revoke that key and create a new one — it's one click.
After it's working
- Use a sprite: render from
src/assets/magicpixel/<folder>/<name>.png— pixel-perfect, no smoothing. - Typed imports: import from
@/assets/magicpixelto get a typedMagicPixelAssetsmap (andMagicPixelAssetsByIdthat survives renames). - Update later: save here, browser hot-reloads in ~2s. No commands.
Game engines (Unity, Godot, GameMaker)
The CLI detects your engine from marker files in the project root and picks a sensible outDir automatically. No Vite plugin, no package.json, and no typed index.ts — just PNGs where your engine expects them.
| Engine | Detected when | Default outDir |
|---|---|---|
| Godot | project.godot | assets/magicpixel |
| Unity | ProjectSettings/ProjectVersion.txt | Assets/MagicPixel |
| GameMaker | *.yyp | datafiles/magicpixel |
Unity: how syncing works
CLI watcher (any browser, any OS). Run npx @magicpixelart/cli start in your Unity project folder, then leave npx @magicpixelart/cli sync --watch running. That is the whole loop: game sprites import into Connected, and every save writes back to the original PNG. Also the right choice for CI and for teammates sharing one project. The walkthrough lives in Settings → Unity sync.
Nothing syncs until you pick it. Only things marked Sync to Unity land in Assets/MagicPixel, so 100 drafts can live in a document while just the three finished sprites sync. Game files already on disk stay put when you un-check — they are yours, not library copies. Right-click an artboard to mark it, or right-click a library folder to mark everything inside it — a folder opt-in cascades to its assets and subfolders, and the badge on a child shows which parent folder it inherited from. Un-check a library artboard and the next sync deletes its PNG and .meta from Assets/MagicPixel. If nothing is marked, the CLI skips the pull (working-set game files still upload). Set "unitySyncAll": true in magicpixel.json to sync every artboard instead.
Your library folders become Unity folders. Synced assets mirror the folder tree you organized in the library, so Props/Rocks/rock-a in MagicPixel writes to Assets/MagicPixel/Props/Rocks/rock-a.png. Rename a folder in MagicPixel and the next sync moves the files rather than duplicating them.
Two-way with sync --watch. Leave it running. Saves in Connected overwrite the original game files a couple of seconds later; edits to those files come back into MagicPixel on the same tick. npx @magicpixelart/cli push is upload-only if you want that without a pull. Start with push --dry-run to see the list first. If a sprite changed in MagicPixel since your last sync, push reports a conflict instead of overwriting — run sync first, then push again. Multi-layer or animated artboards are skipped unless you pass --flatten, which replaces them with the flat image on disk.
Both paths write pixel-art-correct .meta sidecars — point filtering, no compression, and deterministic GUIDs, so scene and prefab references never break. Existing .meta files are left untouched, so importer tweaks you make in Unity survive re-syncs. Set "unityPpu" in magicpixel.json to change pixels-per-unit (default 32).
Run these from your game project root (where the marker file lives). The package is @magicpixelart/cli and the command it installs is magicpixel:
npx @magicpixelart/cli@latest start
npx @magicpixelart/cli sync --watchstart creates the config, logs you in, and runs the first sync (including your game sprites). Always pass the full package name: magicpixel is the binary the package installs, not a package on npm, so the short magicpixel … form only works after npm i -D @magicpixelart/cli in that project. The first npx run asks to install the package — answer yes, or use npx -y @magicpixelart/cli … in CI.
- Godot & Unity: PNGs auto-import when files change — leave
sync --watchrunning in a terminal while you edit in MagicPixel. - GameMaker: files land in Included Files, but the IDE does not auto-refresh — use Refresh Included Files after each sync (the CLI reminds you at
inittime). - Verify setup:
npx @magicpixelart/cli doctor --jsonreports"framework": "Godot"(or Unity / GameMaker) and youroutDir.
To sync only some folders
By default Unity, Godot, and GameMaker sync every game PNG. npx @magicpixelart/cli connect 'Sprites/Hero/**' narrows that. Daily work is still sync --watch.
Use sprites in-engine via each engine's normal PNG path (e.g. Godot res://assets/magicpixel/…, Unity Assets/MagicPixel/…). For Godot and GameMaker the CLI leaves sidecar metadata (.import, .yy) to the engine on first import; for Unity both the CLI and the in-app sync write tuned .meta files for you.
Manual setup (web projects)
For Vite / React / Next and other JavaScript projects — without the AI quickstart prompt. Game engines: see Game engines.
Option A — Vite plugin (recommended)
npm i -D @magicpixelart/viteThe plugin bundles @magicpixelart/cli. Add it to your Vite config:
// vite.config.ts
import { defineConfig } from 'vite';
import { magicpixel } from '@magicpixelart/vite';
export default defineConfig({
plugins: [/* your existing plugins */, magicpixel()],
});Then authenticate once:
npx @magicpixelart/cli startstart detects your framework, writes magicpixel.json, prompts for your API key, runs the first sync, and offers a magicpixel:watch npm script. After that, every save in MagicPixel hits your dev server within ~2 seconds — Vite HMR picks it up automatically.
Option B — CLI only (non-Vite, CI)
npm i -D @magicpixelart/cli
npx @magicpixelart/cli start # one-time auth + initial sync
npx @magicpixelart/cli sync --watch # keep assets fresh while you workstart stores your key in .magicpixel/credentials (mode 0600, auto-gitignored). For CI, set MAGICPIXEL_API_KEY in the environment instead — it takes precedence over the credentials file.
magicpixel.json config
Lives at the project root. Edit by hand or via magicpixel add <glob> / magicpixel remove <glob>.
{
"outDir": "src/assets/magicpixel",
"include": ["rabbit/**", "ui/icons/*"],
"exclude": ["**/wip-*"],
"emitIndex": true
}init detects your project kind and suggests outDir. Defaults:
| Project | Default outDir | Notes |
|---|---|---|
| Vite, Remix, TanStack Start | src/assets/magicpixel | — |
| Next.js, Astro, Nuxt, CRA | public/magicpixel | — |
| SvelteKit | static/magicpixel | — |
| Godot | assets/magicpixel | res://assets/magicpixel/… |
| Unity | Assets/MagicPixel | auto-imports under Assets/ |
| GameMaker | datafiles/magicpixel | Included Files — refresh after sync |
| Unknown / other | assets/magicpixel | src/assets/magicpixel if src/ exists |
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| outDir | string | No | project-kind default | Where PNGs (and index.ts when emitIndex is on) are written. |
| include | string[] | No | ["**/*"] | Picomatch globs matched against "folder/slug". |
| exclude | string[] | No | [] | Globs to exclude. |
| connect | string[] | No | ["**"] on Unity/Godot/GameMaker | Game PNG globs to keep in Connected. Default is all sprites; narrow with magicpixel connect. |
| emitIndex | boolean | No | true (JS only) | Emit <outDir>/index.ts with typed asset map. Off for Unity/Godot/GameMaker. |
| endpoint | string | No | production | Override the API base (must be HTTPS). Testing only. |
State (.magicpixel/state.json) tracks lastSync. Add .magicpixel/ to .gitignore (init offers to do this).
CLI commands
| Command | What it does |
|---|---|
| start [--force] | Bootstrap: init + login + first two-way sync. Works for Unity, Godot, GameMaker, and JS. |
| init [-y] [--force] | Config wizard. Detects Vite/Next/Unity/Godot/GameMaker and suggests outDir. |
| login [--key <key>] | Save your API key to .magicpixel/credentials. Validates against the server first. |
| logout | Remove the stored API key. |
| sync [...flags] | Two-way: pull MagicPixel edits and push changed game sprites. Use --watch while you work. |
| push [--dry-run] [--flatten] | Upload-only: send local PNG edits without pulling. |
| connect <glob> | Limit which game folders sync (default is all sprites). |
| search <query> | Search indexed game PNGs (no network). |
| add <glob> / remove <glob> | Manage include patterns for MagicPixel → disk. |
| list | Print the matching manifest as a table. |
| status | Config, last sync, diff vs remote. |
| whoami | Verify API key, report visible assets. |
| doctor [--json] [--offline] | One-screen diagnostic. Paste it to your AI agent when something breaks. |
| repair [--dry-run] [-y] | Self-heal: validate key → quarantine state.json → prune empty dirs → full re-sync. |
sync flags
| Flag | Meaning |
|---|---|
| -w, --watch [seconds] | Poll for changes (default 2s; auto-slows to 5s after ~1min idle, 10s after ~5min). |
| --no-prune | Keep local files not in the manifest (default: prune them on full syncs). |
| --dry-run | Print plan, write nothing. |
| --full | Ignore lastSync; re-fetch the full manifest. |
| -c, --concurrency <n> | Parallel downloads (1–16, default 6). |
| -q, --quiet | Minimal output (for CI). |
Each successful sync prints a per-file change list (+ added, ~ modified, ↪ renamed, - pruned). sync --watch exits 2 after 5 consecutive auth failures so process supervisors can detect a revoked key.
REST API (advanced)
The CLI is built on a small REST API — reads for pulling sprites down, plus a write-back endpoint for pushing disk edits home. Use it directly when you can't run Node (game-engine plugins, scripts in other languages, etc.).
Authentication
All requests need an API key in the Authorization header. Create keys in Settings → API Keys.
Authorization: Bearer mp_live_abc123...Each key is bound to a single project and only sees that project's assets. Revoke anytime from Settings.
Base URL
https://sddsilidjhvtvejzvolx.supabase.co/functions/v1List assets (manifest)
/integration-assets/manifestList the assets your key can see. Designed for diffing against disk before downloading only what actually changed.
Query parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| include | string[] | No | — | Globs matched against "folder/slug". Repeatable. Default: match all. |
| exclude | string[] | No | — | Globs to exclude. Repeatable. |
| since | ISO8601 | No | — | Only return assets updated after this timestamp. |
| cursor | string | No | — | Opaque pagination cursor from the previous response. |
| limit | number | No | 500 | Max items per page (1–1000). |
Response (200 OK)
{
"items": [
{
"id": "e5f6a7b8-...",
"key": "rabbit/ear-left",
"name": "Ear Left",
"folder": "rabbit",
"slug": "ear-left",
"width": 64,
"height": 64,
"sha256": "a1b2c3...",
"size_bytes": 482,
"updated_at": "2025-01-15T12:00:00Z",
"download_url": "https://sddsilidjhvtvejzvolx.supabase.co/functions/v1/integration-assets?key=rabbit%2Fear-left"
}
],
"nextCursor": "MjAyNS0w...",
"count": 1
}When nextCursor is non-null, pass it back as ?cursor= to fetch the next page. Compare sha256 against the version on disk to decide whether to re-download.
Download asset
/integration-assets?key=folder/slugDownload an asset as a raw PNG. Address it by stable key (recommended) or by assetId.
Query parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| key | string | No | — | Stable key — "folder/slug" or just "slug". Recommended. |
| assetId | uuid | No | — | Asset UUID. Either key or assetId is required. |
Example
# Address by key (recommended):
curl -o ear-left.png \
-H "Authorization: Bearer mp_live_abc123..." \
"https://sddsilidjhvtvejzvolx.supabase.co/functions/v1/integration-assets?key=rabbit/ear-left"
# Or by asset ID:
curl -o sprite.png \
-H "Authorization: Bearer mp_live_abc123..." \
"https://sddsilidjhvtvejzvolx.supabase.co/functions/v1/integration-assets?assetId=e5f6a7b8-..."Save bandwidth with ETags
Every PNG response includes an ETag equal to the asset's sha256. Send it back as If-None-Match on subsequent requests to get a 304 Not Modified when nothing changed. The CLI uses this on every poll, so a no-op watch tick downloads zero PNG bytes.
Write sprites back (beta)
https://magicpixel.art/api/public/integration/ingestPush a PNG edited on disk back into MagicPixel — the write half of two-way Unity sync. Send up to 20 sprites per request (16 MB of PNG data total, 4096px per side). Each sprite either updates a known artboard (assetId + layerIdx) or adopts a new file into the library by its disk path (path). Sprites that target the same document are applied together in one write.
Body parameters (per sprite)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| key | string | Yes | — | Your identifier, echoed back in the matching result. |
| pngBase64 | string | Yes | — | Base64 PNG bytes for the sprite as it exists on disk. |
| diskSha256 | string | Yes | — | sha256 of the disk PNG; identical bytes short-circuit to unchanged. |
| assetId | uuid | No | — | Document to write into. With layerIdx, this is an update. |
| layerIdx | number | No | — | Artboard index inside that document (required with assetId). |
| baseSha256 | string | No | — | Sha the file was last synced from. Required for updates — a mismatch is refused as cloud-changed. |
| path | string[] | No | — | Slug path for adoption, e.g. ["props","rocks","rock-a"]. Folders are created as needed. |
| pathNames | string[] | No | — | Display names matching path, so disk casing survives folder creation. |
| flatten | boolean | No | false | Allow overwriting a multi-layer or animated artboard with one flat PNG. |
Response (200 OK)
{
"results": [
{ "key": "props/rock-a", "status": "updated", "assetId": "e5f6...", "layerIdx": 1, "sha256": "a1b2..." },
{ "key": "props/rock-b", "status": "unchanged", "assetId": "e5f6...", "layerIdx": 2, "sha256": "c3d4..." },
{ "key": "props/rock-c", "status": "conflict", "reason": "cloud-changed" }
]
}Per-sprite status is updated, created, unchanged, conflict, or error — the request itself still returns 200, so read every result. Conflict reasons: cloud-changed (someone saved in the editor — re-sync down first), would-flatten (layered or animated artboard; pass flatten to accept the flattening), legacy-document, and not-found. A lost conflict never writes: your cloud document is left byte-for-byte intact.
The CLI does this for you
npx @magicpixelart/cli push walks your synced folder, hashes every PNG, and calls this endpoint with the right shape — including the conflict baseline. Use the raw endpoint only for plugins and scripts.
Error handling
All error responses use a consistent JSON shape:
{
"error": "Human-readable error message"
}Every response also carries an X-Request-Id header — include it when reaching out for support and we can grep the edge logs in one shot.
| Status | Meaning |
|---|---|
| 304 | Not Modified — ETag matched (asset downloads only). |
| 400 | Bad request — invalid or missing parameters. |
| 401 | Unauthorized — missing or invalid API key. |
| 403 | Forbidden — key is valid but lacks access to the requested asset. |
| 404 | Asset not found in this project. |
| 405 | Method not allowed. |
| 429 | Rate limit / daily download cap exceeded — retry after a delay; CLI auto-backs off. |
| 5xx | Server error — CLI retries with backoff and reports the request id. |
Troubleshooting
Most issues are diagnosable in one command: npx @magicpixelart/cli doctor (add --json for a machine-readable report, --offline behind strict proxies). For a clean-slate recovery, run npx @magicpixelart/cli repair --dry-run first to preview, then drop the flag.
I'm using Unity, Godot, or GameMaker — where do I start?
npx @magicpixelart/cli start, then npx @magicpixelart/cli sync --watch. See the Game engines section for default folders. After npm i -D @magicpixelart/cliyou can type the short magicpixel sync --watch.I want a whole folder to sync to Unity, not one artboard at a time.
Something's broken — where do I start?
magicpixel doctor --json | jq has a stable schema and no ANSI codes.My AI tool says the key is invalid.
Nothing got downloaded.
npx @magicpixelart/cli whoami.I only want one folder, not everything.
npx @magicpixelart/cli connect 'Sprites/Hero/**' or edit connect in magicpixel.json. For MagicPixel → disk, edit include or use npx @magicpixelart/cli add 'player/**'.I renamed or deleted a sprite, but the old file is still there.
--no-prune if you want to keep stale files. If things still look out of sync, npx @magicpixelart/cli repair wipes local state and runs a full re-sync.Sync was working, then started failing.
The index.ts file doesn't reflect a rename.
npx @magicpixelart/cli sync --full once. Renames are detected against the previous sync's snapshot; a full pass guarantees the new key lands. Prefer MagicPixelAssetsById['…'] for assets you don't want to chase imports for — it survives every rename.Files keep getting re-downloaded every sync.
Telemetry
Unexpected CLI failures (5xx server errors, uncaught exceptions) are reported fire-and-forget so we can fix issues before you have to file them. We send: the error message + stack, command name, CLI version, Node version, OS platform, and the request id from the failed call. We never send file paths, asset names, configuration, environment variables, or your API key.
Opt out with MAGICPIXEL_TELEMETRY=0. Reporting is also automatically skipped when no API key is configured or when endpoint in magicpixel.json points at a non-canonical host.