How to auto-increment orderNumber field when creating new order document?

4 replies
Last updated: Dec 20, 2022
Dear support, comunity,what is easiest way to get/write next number of array - field "orderNumber"
Example of object order:
{
"_createdAt": "2022-12-14T19
:04:58Z", "_id": "4db5796f-ed83-411d-b419-bf668206b3ce",
"_rev": "j1tWZCfjdAwUaXU7wQg1DG",
"_type": "order",
"_updatedAt": "2022-12-14T19
:14:25Z", "note": "erer",
"orderNumber": 2300000001
}

I would like to write next number of field "orderNumber" in the moment when i run function "createOrder"
function createOrder() {

const doc = {
_type: 'order',
note: 'První poznámka objednávky',
order_number: novaObj,
}
console.log(novaObj);
client.create(doc).then((res) => {
console.log(
Objednávka byla vytvořena , document ID is ${res._id}
) });
}
----

What i found
https://www.sanity.io/docs/groq-functions#03c444f2caf3
math::sum(<array[number]>) <number|null>
and
https://www.sanity.io/docs/initial-value-templates
Thank you
AI Update

The best approach for auto-incrementing an orderNumber field is to use a dedicated counter document with atomic transactions. Here's the pattern that works reliably:

Solution: Counter Document with Atomic Transactions

Create a separate counter document to track your order numbers, then use transactions with patch, setIfMissing, and inc operations to atomically increment and retrieve the next number:

async function createOrder() {
  const COUNTER_ID = 'order-counter'; // A fixed document ID for your counter
  
  // Atomic transaction to get next order number
  const result = await client
    .transaction()
    .patch(COUNTER_ID, patch => 
      patch
        .setIfMissing({ _type: 'counter', current: 2300000000 }) // Initialize if missing
        .inc({ current: 1 }) // Increment atomically
    )
    .commit({ returnDocuments: true });
  
  const nextOrderNumber = result[0].current;
  
  // Now create your order with the guaranteed unique number
  const doc = {
    _type: 'order',
    note: 'První poznámka objednávky',
    orderNumber: nextOrderNumber,
  };
  
  const orderResult = await client.create(doc);
  console.log(`Objednávka byla vytvořena, document ID is ${orderResult._id}`);
  console.log(`Order number: ${nextOrderNumber}`);
}

Why This Works

  1. setIfMissing ensures the counter document exists and is initialized on first use
  2. inc atomically increments the counter - this is thread-safe even with concurrent requests
  3. returnDocuments: true gives you the updated counter value immediately
  4. The transaction ensures atomicity - no race conditions even if multiple orders are created simultaneously

Alternative: Single Transaction (More Efficient)

You can also create both the counter update and order document in a single transaction:

async function createOrder() {
  const COUNTER_ID = 'order-counter';
  const orderId = `order-${Date.now()}`; // Generate unique ID
  
  // First get the next number
  const counterResult = await client
    .transaction()
    .patch(COUNTER_ID, patch => 
      patch
        .setIfMissing({ _type: 'counter', current: 2300000000 })
        .inc({ current: 1 })
    )
    .commit({ returnDocuments: true });
  
  const nextOrderNumber = counterResult[0].current;
  
  // Then create the order with that number
  const orderResult = await client.create({
    _type: 'order',
    note: 'První poznámka objednávky',
    orderNumber: nextOrderNumber,
  });
  
  console.log(`Objednávka byla vytvořena, document ID is ${orderResult._id}`);
  return orderResult;
}

Don't Forget Your Schema

Add a counter document type to your schema:

export default {
  name: 'counter',
  type: 'document',
  title: 'Counter',
  fields: [
    {
      name: 'current',
      type: 'number',
      title: 'Current Value',
    },
  ],
}

Why Not Use GROQ math::sum?

While GROQ's math::sum can calculate totals, it's not suitable for generating unique sequential numbers because:

  • There's a race condition between querying the max value and creating the new document
  • Multiple concurrent requests could get the same "next" number
  • GROQ queries are read-only and can't atomically increment

The counter document with inc is the proper solution because it's atomic and handles concurrency correctly. The ifRevisionId parameter you might find in older examples is not necessary with this approach since inc is inherently atomic within a transaction.

Show original thread
4 replies
Hey
user M
! What are you basing your order numbers on? If you're looking to create a sequential order number, it'd probably be easiest to use the JS client to fetch the most recent order number, then set the order number in your document to the previous order number + 1. You can also use something like the
uuid
npm package to generate unique numbers if that's what you're looking for!
Hey,
user M
, thank you for fast reply. Yes, thats what i want to. But are u sure, that is good idea to fetch last order and sum +1 at frontend and after that send to backend? I wanted to do that, but one of my friend stop me, dont do that. Backend need to give you right order, dont create order on the frontend.This article i cant use for that
https://www.sanity.io/docs/groq-functions#03c444f2caf3 ?
Thank you for any ideas
Yeah, I suppose you could use that function to calculate your order number. It would still require you to fetch the previous order number and add to it. You'd just be doing it in a single step.
Thank you, I really preciated your answer
user M
, but if many customers use this flow, so in same moment wanna create order, it couldnt be like that. I tryied it - fetch last order, sum, create order at FE, send to BE a do it again, i get same number of order. API/DB probably need more time if u wanna use it same time to use this function. Are u really sure that it can be like that? I cant use any other function at BE to do sum with last value of order in last document?Thakn you

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?