Manage Shop Items Workflow
Overview
This skill guides you through creating, managing, and integrating shop items for games on the Remix platform.
Prerequisites
- A game must already exist.
- Reuse
gameIdfrom task context first, then.remix-cli.json, then legacy.remix-mcp.jsonwhen available. - Use the official CLI or MCP toolchain when possible instead of handwritten HTTP.
Steps
1. Check for Existing Game ID
Read task context or prior tool results first. Otherwise, read .remix-cli.json or legacy .remix-mcp.json in the current directory. If either exists and contains a gameId, use that value.
Only if neither file exists or neither has a gameId should you follow the upload-game workflow to create one.
2. Manage Shop Items
List All Items
Always list current items first so you reuse the right itemId, avoid duplicate slugs, and see whether an item is already PENDING, ACTIVE, or INACTIVE.
Create an Item
POST https://api.remix.gg/v1/games/{gameId}/items
Authorization: Bearer $REMIX_API_KEY
Content-Type: application/json
{
"name": "Extra Life",
"slug": "extra-life",
"itemType": "CONSUMABLE",
"bitsCost": 50,
"description": "Spend one to restore a life after game over.",
"iconUrl": "https://asset-url.png"
}itemTypeis one ofCONSUMABLE,ONE_TIME, orTIER_UNLOCKbitsCostis required forCONSUMABLEandONE_TIMEtieris required forTIER_UNLOCK- new items start as
PENDING - when using MCP,
createShopItemcan auto-generate an icon ificonUrlis omitted forCONSUMABLEorONE_TIME
Update an Item
POST https://api.remix.gg/v1/games/{gameId}/items/{itemId}
Authorization: Bearer $REMIX_API_KEY
Content-Type: application/json
{ "bitsCost": 75, "name": "Extra Life Pack" }Accepts a partial body -- only include fields you want to change.
Delete an Item
DELETE https://api.remix.gg/v1/games/{gameId}/items/{itemId}
Authorization: Bearer $REMIX_API_KEY3. Integrate Shop Items in Game Code
Add the RemixSDK shop integration to your game:
const sdk = window.RemixSDK
await sdk.ready()
function getConsumableBalance(slug, spentKey) {
const purchased = sdk.getItemPurchaseCount(slug)
const spent = Number(sdk.gameState?.[spentKey] || 0)
return Math.max(0, purchased - spent)
}
async function buyExtraLife() {
const result = await sdk.purchase({ item: 'extra-life' })
if (!result.success) showPurchaseError()
}
sdk.onPurchaseComplete(({ success, item }) => {
if (!success || item !== 'extra-life') return
const currentState = sdk.gameState || {}
const spent = Number(currentState.extraLivesSpent || 0)
sdk.singlePlayer.actions.saveGameState({
gameState: {
...currentState,
extraLivesSpent: Math.max(0, spent - 1),
},
})
})Wrong Patterns
Do NOT do any of the following:
- Granting items without listening to
onPurchaseComplete. Always wait for the callback before giving the player anything. The purchase may fail or be cancelled. - Using
singlePlayer.actions.purchase(...)in new code. Callsdk.purchase({item: 'slug'}). - Using
localStorageto track purchases. The platform handles purchase state. UsehasItem(),getItemPurchaseCount(),inventory, andshopItems. - Hardcoding prices or item metadata in game state. Read item metadata from the platform and only persist usage counters in
gameState.
Tips
- Use
CONSUMABLEfor repeatable purchases,ONE_TIMEfor permanent unlocks, andTIER_UNLOCKfor progression-style items. - Run
sdk.onPurchaseComplete(...)even if you also awaitsdk.purchase(...). - Use
sdk.getShopItem(slug)orsdk.shopItemswhen the game needs current price or icon metadata.