Sync guide & referenceBeta

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

start, then sync --watch

Updates

Auto (~2s with Vite plugin)

Files land in

outDir

Works with

Vite, Unity, Godot, GameMaker, …
Edit in MagicPixel
Vite plugin syncs
Browser hot-reloads

~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.
1

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.png

Working on more than one game? Right-click in the library → New project, then bind your sync key to it in step 2.

2

Create your sync key

  1. Open SettingsAPI Keys.
  2. Click Create Key (pick a project if you have several).
  3. Use the Copy setup prompt button next to the new key.

Revoke and replace anytime from Settings. Production keys are prefixed mp_live_.

3

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/magicpixel to get a typed MagicPixelAssets map (and MagicPixelAssetsById that 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.

EngineDetected whenDefault outDir
Godotproject.godotassets/magicpixel
UnityProjectSettings/ProjectVersion.txtAssets/MagicPixel
GameMaker*.yypdatafiles/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 --watch

start 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 --watch running 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 init time).
  • Verify setup: npx @magicpixelart/cli doctor --json reports "framework": "Godot" (or Unity / GameMaker) and your outDir.

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/vite

The 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 start

start 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 work

start 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:

ProjectDefault outDirNotes
Vite, Remix, TanStack Startsrc/assets/magicpixel
Next.js, Astro, Nuxt, CRApublic/magicpixel
SvelteKitstatic/magicpixel
Godotassets/magicpixelres://assets/magicpixel/…
UnityAssets/MagicPixelauto-imports under Assets/
GameMakerdatafiles/magicpixelIncluded Files — refresh after sync
Unknown / otherassets/magicpixelsrc/assets/magicpixel if src/ exists
ParameterTypeRequiredDefaultDescription
outDirstringNoproject-kind defaultWhere PNGs (and index.ts when emitIndex is on) are written.
includestring[]No["**/*"]Picomatch globs matched against "folder/slug".
excludestring[]No[]Globs to exclude.
connectstring[]No["**"] on Unity/Godot/GameMakerGame PNG globs to keep in Connected. Default is all sprites; narrow with magicpixel connect.
emitIndexbooleanNotrue (JS only)Emit <outDir>/index.ts with typed asset map. Off for Unity/Godot/GameMaker.
endpointstringNoproductionOverride 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

CommandWhat 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.
logoutRemove 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.
listPrint the matching manifest as a table.
statusConfig, last sync, diff vs remote.
whoamiVerify 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

FlagMeaning
-w, --watch [seconds]Poll for changes (default 2s; auto-slows to 5s after ~1min idle, 10s after ~5min).
--no-pruneKeep local files not in the manifest (default: prune them on full syncs).
--dry-runPrint plan, write nothing.
--fullIgnore lastSync; re-fetch the full manifest.
-c, --concurrency <n>Parallel downloads (1–16, default 6).
-q, --quietMinimal 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/v1

List assets (manifest)

GET/integration-assets/manifest

List the assets your key can see. Designed for diffing against disk before downloading only what actually changed.

Query parameters

ParameterTypeRequiredDefaultDescription
includestring[]NoGlobs matched against "folder/slug". Repeatable. Default: match all.
excludestring[]NoGlobs to exclude. Repeatable.
sinceISO8601NoOnly return assets updated after this timestamp.
cursorstringNoOpaque pagination cursor from the previous response.
limitnumberNo500Max 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

GET/integration-assets?key=folder/slug

Download an asset as a raw PNG. Address it by stable key (recommended) or by assetId.

Query parameters

ParameterTypeRequiredDefaultDescription
keystringNoStable key — "folder/slug" or just "slug". Recommended.
assetIduuidNoAsset 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)

POSThttps://magicpixel.art/api/public/integration/ingest

Push 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)

ParameterTypeRequiredDefaultDescription
keystringYesYour identifier, echoed back in the matching result.
pngBase64stringYesBase64 PNG bytes for the sprite as it exists on disk.
diskSha256stringYessha256 of the disk PNG; identical bytes short-circuit to unchanged.
assetIduuidNoDocument to write into. With layerIdx, this is an update.
layerIdxnumberNoArtboard index inside that document (required with assetId).
baseSha256stringNoSha the file was last synced from. Required for updates — a mismatch is refused as cloud-changed.
pathstring[]NoSlug path for adoption, e.g. ["props","rocks","rock-a"]. Folders are created as needed.
pathNamesstring[]NoDisplay names matching path, so disk casing survives folder creation.
flattenbooleanNofalseAllow 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.

StatusMeaning
304Not Modified — ETag matched (asset downloads only).
400Bad request — invalid or missing parameters.
401Unauthorized — missing or invalid API key.
403Forbidden — key is valid but lacks access to the requested asset.
404Asset not found in this project.
405Method not allowed.
429Rate limit / daily download cap exceeded — retry after a delay; CLI auto-backs off.
5xxServer 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?

From your game project root: 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.

Right-click the folder in your library and choose Sync to Unity — every asset and subfolder inside it inherits the opt-in, and new sprites you drop in later are included automatically. Child rows show which parent folder they inherited from, so you can always see why something is syncing.

Something's broken — where do I start?

Ask your AI tool: "Run npx @magicpixelart/cli doctor and tell me what it says." It pinpoints the API key, project binding, network, or stale CLI in one shot. If you need to share it with us, magicpixel doctor --json | jq has a stable schema and no ANSI codes.

My AI tool says the key is invalid.

The key probably wasn't saved as a secret. Ask: "Confirm MAGICPIXEL_API_KEY is set, then run npx @magicpixelart/cli doctor."

Nothing got downloaded.

Either no sprites are saved yet, or your key is bound to a project that has none. Check Settings → Projects, or run npx @magicpixelart/cli whoami.

I only want one folder, not everything.

For game sprites, run 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.

Pruning is on by default — a re-sync removes it. Pass --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.

Most likely today's per-key download quota — it resets at UTC midnight, or upgrade your plan to raise the cap. Transient 429/5xx failures auto-recover with backoff.

The index.ts file doesn't reflect a rename.

Run 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.

Your build system is rewriting the PNGs on disk, so the sha256 no longer matches. Sync into a directory your bundler reads but doesn't mutate (avoid putting them in a folder an image optimizer touches).

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.