Skip to content

Update Operators

Update operators allow you to perform granular updates on component model data beyond the default $set behavior. Instead of only replacing field values, you can increment counters, push to arrays, remove elements, and more — all within a single update request.


Overview

By default, all fields sent in the data payload of an update request replace the existing field value entirely.

With update operators, you can include $-prefixed keys directly in your data payload to perform different types of updates. Regular fields (without the $ prefix) continue to behave as before.


Supported Operators

Operator Description Example Value
$inc Increment a numeric field by the given amount { "counter": 1, "views": -1 }
$push Append a value to an array field { "tags": "new-tag" }
$pull Remove elements from an array matching a condition { "tags": "old-tag" }
$addToSet Add a value to an array only if it doesn't exist { "categories": "cat-1" }
$pop Remove the first (-1) or last (1) element of an array { "queue": 1 }
$min Update only if the new value is less than current { "low_score": 50 }
$max Update only if the new value is greater than current { "high_score": 100 }
$mul Multiply a numeric field by the given factor { "price": 1.1 }

Usage

Operators are passed inline within the data object. Any key starting with $ is treated as an operator — its value must be an object mapping field names to their operator-specific values.

Single Update

import { Component } from "@ptkl/sdk"

const orders = new Component("orders")
await orders.update(uuid, {
    // Regular fields → will use $set
    status: "confirmed",

    // Operator fields
    $inc: { order_count: 1 },
    $push: { status_history: "confirmed" },
    $addToSet: { tags: "priority" },
});
curl -X POST /v4/system/component/orders/model/{uuid} \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "status": "confirmed",
      "$inc": { "order_count": 1 },
      "$push": { "status_history": "confirmed" },
      "$addToSet": { "tags": "priority" }
    }
  }'

Concurrent Update

Works the same way — just include version for optimistic locking:

import { Component } from "@ptkl/sdk"

const products = new Component("products")
await products.concurrentUpdate(uuid, model.__version__, {
    name: "Updated Product",
    $inc: { view_count: 1 },
});
curl -X POST /v4/system/component/products/model/{uuid} \
  -H "Content-Type: application/json" \
  -d '{
    "version": 3,
    "data": {
      "name": "Updated Product",
      "$inc": { "view_count": 1 }
    }
  }'

Modify (Filter-Based Update)

Operators work with modify for bulk filter-based updates:

import { Component } from "@ptkl/sdk"

const articles = new Component("articles")
await articles.modify(
    { status: "published" },
    {
        $inc: { impression_count: 1 },
        $addToSet: { viewed_by: "user-123" },
    },
    { upsert: false }
);
curl -X PATCH /v4/system/component/articles/modify/model \
  -H "Content-Type: application/json" \
  -d '{
    "filters": { "status": "published" },
    "data": {
      "$inc": { "impression_count": 1 },
      "$addToSet": { "viewed_by": "user-123" }
    },
    "options": { "upsert": false }
  }'

Combining Operators with Regular Fields

You can freely mix regular fields and operators in the same request:

{
  "data": {
    "name": "John",
    "lastname": "Doe",
    "$inc": { "login_count": 1 },
    "$push": { "login_history": "2026-02-10T12:00:00Z" }
  }
}

This will:

  1. Set name to "John" and lastname to "Doe"
  2. Increment login_count by 1
  3. Push the timestamp to the login_history array

Operator Details

$inc — Increment

Adds the specified value to a numeric field. Use negative values to decrement.

{ "$inc": { "balance": -50, "transaction_count": 1 } }

Note

If the field does not exist, it will be created with the increment value.

$push — Append to Array

Appends a value to the end of an array field.

{ "$push": { "comments": { "text": "Great!", "by": "user-1" } } }

$pull — Remove from Array

Removes all elements from an array that match the specified condition.

{ "$pull": { "tags": "deprecated" } }

$addToSet — Add Unique to Array

Adds a value to an array only if it is not already present.

{ "$addToSet": { "subscribers": "user-456" } }

$pop — Remove First/Last from Array

Removes the first (-1) or last (1) element from an array.

{ "$pop": { "queue": -1 } }

$min / $max — Conditional Update

Updates the field only if the provided value is less than ($min) or greater than ($max) the current value.

{ "$min": { "lowest_price": 9.99 }, "$max": { "highest_price": 149.99 } }

$mul — Multiply

Multiplies the value of a field by the specified number.

{ "$mul": { "price": 1.25 } }


Error Handling

Error Cause
unsupported update operator: $foo An unrecognized operator was used
$unset is not allowed: fields defined in the schema cannot be removed $unset is banned — use a regular field update to clear a value instead
operator $inc cannot target protected field: __version__ Attempting to modify a protected field
operator $push value must be an object (map of field→value) The operator value is not a valid object

Aggregate Expression Updates ($aggregate)

v4 only

Aggregate expression updates are supported exclusively by the v4 component model API.

For advanced update scenarios — such as computing a field's new value based on its current value using conditional logic, array transformations, or mathematical aggregation — you can supply a $aggregate array. Each element is a plain object of { fieldName: expression } pairs.

{
  "data": {
    "$aggregate": [
      {
        "avg_rating": { "$avg": "$ratings" },
        "tier": {
          "$cond": {
            "if": { "$gte": ["$avg_rating", 4.5] },
            "then": "platinum",
            "else": {
              "$cond": {
                "if": { "$gte": ["$avg_rating", 3.0] },
                "then": "gold",
                "else": "standard"
              }
            }
          }
        }
      }
    ]
  }
}

Each element maps one or more field names to expressions. Multiple elements become sequential stages, so later elements can reference fields computed by earlier ones.

Usage

import { Component } from "@ptkl/sdk"

const products = new Component("products")
await products.update(uuid, {
    $aggregate: [
        {
            // Recompute average rating from the stored ratings array
            avg_rating: { $avg: "$ratings" },
        },
        {
            // Derive a tier label from the avg_rating computed above
            tier: {
                $cond: {
                    if: { $gte: ["$avg_rating", 4.5] },
                    then: "platinum",
                    else: {
                        $cond: {
                            if: { $gte: ["$avg_rating", 3.0] },
                            then: "gold",
                            else: "standard",
                        },
                    },
                },
            },
        },
    ],
});
curl -X POST /v4/system/component/products/model/{uuid} \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "$aggregate": [
        {
          "avg_rating": { "$avg": "$ratings" }
        },
        {
          "tier": {
            "$cond": {
              "if": { "$gte": ["$avg_rating", 4.5] },
              "then": "platinum",
              "else": {
                "$cond": {
                  "if": { "$gte": ["$avg_rating", 3.0] },
                  "then": "gold",
                  "else": "standard"
                }
              }
            }
          }
        }
      ]
    }
  }'

Behaviour

  • Provide field names and expressions directly — no wrapping required.
  • If the resulting document fails field validation, the update is rejected and no changes are persisted.
  • Multiple elements become sequential stages, so a later element can reference a field computed by an earlier one.
  • $aggregate and regular update operators ($inc, $push, etc.) cannot be combined in a single request.

Error Handling

Error Cause
$aggregate must be an array of field-expression objects The $aggregate value is not an array
$aggregate must contain at least one field-expression object An empty array was provided
$aggregate element 0 must be an object of field → expression pairs An array element is not a plain object
$aggregate cannot target protected field "uuid" A protected field was used as a key
$aggregate cannot target password field "…" A field of type password was used as a key — password fields cannot be set via pipeline expressions
cannot combine $aggregate pipeline with update operators in the same request Both $aggregate and operator keys (e.g. $inc) were present