Updating object properties in an array on a document with the Sanity client

9 replies
Last updated: Mar 17, 2021
Is it possible to update the property of an object in an array on a document with the Sanity client? Previously I have done simple mutations, like so:
const promises = data.overview.cartContents.map(async (item, index) => {
  return await client.patch(item._id)
  .dec({stock: item.quantity})
  .commit()
});
but I need to do something like this:

const promises = data.overview.cartContents.map(async (item, index) => {
  return await client.patch(item._id)
  .dec({`variants[_key == \"${item.variant}\"].quantity`: item.quantity})
  .commit()
});
(which fails with a
400
error…
AI Update

Unfortunately, dec() (and inc()) operations don't support JSONMatch path syntax with array selectors like variants[_key == "..."].quantity. These operations only work with simple dot-notation paths like stock or nested.field. That's why you're getting a 400 error.

The patches documentation shows that inc and dec work with simple paths, while the JSONMatch syntax (like arrayField[_key == "value"].property) is supported by set, unset, and setIfMissing operations.

Here's the solution using set with the JSONMatch path syntax:

const promises = data.overview.cartContents.map(async (item) => {
  // First, fetch the current quantity
  const doc = await client.fetch(
    `*[_id == $id][0]{"currentQty": variants[_key == $variantKey][0].quantity}`,
    { id: item._id, variantKey: item.variant }
  );
  
  // Then set the new decremented value
  return await client
    .patch(item._id)
    .set({ 
      [`variants[_key == "${item.variant}"].quantity`]: doc.currentQty - item.quantity 
    })
    .commit();
});

The key changes:

  1. Use set() instead of dec(): The JSONMatch path syntax works with set, not with inc/dec
  2. Computed property names: The square brackets [] around the template literal are required to create a dynamic property name
  3. Fetch first, then calculate: Since you can't use dec() directly, fetch the current value, calculate the new value, and set it

Here's a cleaner syntax example from the array patching documentation:

await client
  .patch('document-id')
  .set({ 
    'variants[_key == "abc123"].quantity': 42 
  })
  .commit();

Alternative: Use a transaction for atomic updates

If you're concerned about race conditions between the fetch and set operations, you could use Sanity Functions or handle the calculation server-side where you have better control over concurrency.

The limitation exists because inc and dec are designed as atomic operations that work on simple paths, while JSONMatch expressions with array filtering require more complex evaluation that isn't supported with these numeric operations.

What does the 400 error message say?
It’s variations of
'The mutation(s) failed: Cannot increment "variants[].quantity" because it is not present'

I’m losing track of things I’ve tried, but it’s more my uncertainty for lack of an example in the Sanity docs for the Javascript client. I saw this with the HTTP API and wondered if something similar could be done, but think I’m fudging the syntax:


{ "set": "body[_type==\"cta\"].text": "Do the thing!" }

I need to decrement a value in an object in an array, e.g. in my array of
productVariants
find the
productVariant
with
_key == item._key
and then decrement the
stock
property 🙂
I wondered if it could be done something like:

`.dec({`variants[_key == ${item.variant}].quantity`: item.quantity})`

or


.dec({[variants[_key == \"${item.variant}\"].quantity]: item.quantity})

Thanks
user Z
🙂
Just trying some other ideas:

.dec({'variants.0.stock': item.quantity})

which yields:


The mutation(s) failed: Syntax error. (Illegal token "0."

or…


.dec({'variants[].stock': item.quantity})

which yields:


'The mutation(s) failed: Cannot increment "variants[].stock" because it is not present',

So I’m not even managing to adjust all array items, which would be disastrous in reality but a good start!

Is this just not possible with the Javascript client?
OK so this works!

.dec({'variants[0].stock': item.quantity})

Just need to test dynamically…
OK so if I try… `.dec({`variants[${item.variantIndex}].stock`: item.quantity})` I get an
unexpected template string
error
It sounds like the item within the array either didnt exist, or maybe it didn't have a
quantity
already?

.setIfMissing({
  [`variants[_key == \"${item.variant}\"].quantity`]: 0
})
.dec({
  [`variants[_key == \"${item.variant}\"].quantity`]: item.quantity
})
.commit()
Might work if the item exists but doesnt have a quantity
!!!!!! Thank you so much
user Z
I was literally just ham-fistedly working my way towards that one.
`[
variants[${item.variantIndex}].stock
]: item.quantity` is perfect.
This also works too:

`[
variants[_key == \"${item.variant}\"].stock
]: item.quantity`
Your edited example above also works great! –– but only with the square brackets around the template string for me

Combination of me being really dumb (it was
stock
not
quantity
but also not quite having the right syntax anyway – I’m not sure if I’m just generally lacking in essential JS knowledge here (probably :-)) but it seems like a confusing mish-mash of styles to me
Ah, yeah. The square brackets are always a bit confusing
Glad you got it working 🙂

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?