Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Upsert Nodes

⚠️ Warning

HelixQL is deprecated in HelixDB v2. Queries are now written with the Rust DSL and dispatched as JSON — see the Querying guide. This section is kept as a reference for legacy HelixQL projects.

For the complete documentation index optimized for AI agents, see llms.txt.

Upsert Nodes using UpsertN  

Create new nodes or update existing ones with insert-or-update semantics.

::UpsertN({properties})

ℹ️ Info

UpsertN is a traversal step that operates on an existing traversal context. The type comes from the preceding traversal (N<Type>), not from the upsert call itself. If the traversal returns existing nodes, they are updated; if no nodes are found, a new node is created.

⚠️ Warning

When using the SDKs or curling the endpoint, the query name must match what is defined in the queries.hx file exactly.

Example 1: Basic node upsert with properties

QUERY UpsertPerson(name: String, age: U32) =>
    existing <- N<Person>::WHERE(_::{name}::EQ(name))
    person <- existing::UpsertN({name: name, age: age})
    RETURN person
N::Person {
    INDEX name: String,
    age: U32,
}

Here’s how to run the query using the SDKs or curl

from helix.client import Client

client = Client(local=True, port=6969)

# First call creates a new person
person = client.query("UpsertPerson", {
    "name": "Alice",
    "age": 25,
})

print("Created person:", person)

# Subsequent calls with same data update the existing person
updated = client.query("UpsertPerson", {
    "name": "Alice",
    "age": 26,
})

print("Updated person:", updated)
use helix_rs::{HelixDB, HelixDBClient};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = HelixDB::new(Some("http://localhost"), Some(6969), None);

    // First call creates a new person
    let person: serde_json::Value = client.query("UpsertPerson", &json!({
        "name": "Alice",
        "age": 25,
    })).await?;

    println!("Created person: {person:#?}");

    // Subsequent calls update the existing person
    let updated: serde_json::Value = client.query("UpsertPerson", &json!({
        "name": "Alice",
        "age": 26,
    })).await?;

    println!("Updated person: {updated:#?}");

    Ok(())
}
package main

import (
    "fmt"
    "log"

    "github.com/HelixDB/helix-go"
)

func main() {
    client := helix.NewClient("http://localhost:6969")

    // First call creates a new person
    payload := map[string]any{
        "name": "Alice",
        "age":  uint32(25),
    }

    var person map[string]any
    if err := client.Query("UpsertPerson", helix.WithData(payload)).Scan(&person); err != nil {
        log.Fatalf("UpsertPerson failed: %s", err)
    }

    fmt.Printf("Created person: %#v\n", person)

    // Subsequent calls update the existing person
    payload["age"] = uint32(26)

    var updated map[string]any
    if err := client.Query("UpsertPerson", helix.WithData(payload)).Scan(&updated); err != nil {
        log.Fatalf("UpsertPerson failed: %s", err)
    }

    fmt.Printf("Updated person: %#v\n", updated)
}
import HelixDB from "helix-ts";

async function main() {
    const client = new HelixDB("http://localhost:6969");

    // First call creates a new person
    const person = await client.query("UpsertPerson", {
        name: "Alice",
        age: 25,
    });

    console.log("Created person:", person);

    // Subsequent calls update the existing person
    const updated = await client.query("UpsertPerson", {
        name: "Alice",
        age: 26,
    });

    console.log("Updated person:", updated);
}

main().catch((err) => {
    console.error("UpsertPerson query failed:", err);
});
# First call creates a new person
curl -X POST \
  http://localhost:6969/UpsertPerson \
  -H 'Content-Type: application/json' \
  -d '{"name":"Alice","age":25}'

# Subsequent calls update the existing person
curl -X POST \
  http://localhost:6969/UpsertPerson \
  -H 'Content-Type: application/json' \
  -d '{"name":"Alice","age":26}'

Example 2: Upsert from a pre-fetched node by ID

When you already have a node ID, you can fetch it directly and apply the upsert.

QUERY UpsertPersonById(id: ID, new_age: U32) =>
    existing <- N<Person>(id)
    person <- existing::UpsertN({age: new_age})
    RETURN person
N::Person {
    INDEX name: String,
    age: U32,
}

Here’s how to run the query using the SDKs or curl

from helix.client import Client

client = Client(local=True, port=6969)
result = client.query("UpsertPersonById", {
    "id": "<person_id>",
    "new_age": 30,
})
print("Upserted person:", result)
use helix_rs::{HelixDB, HelixDBClient};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = HelixDB::new(Some("http://localhost"), Some(6969), None);

    let result: serde_json::Value = client.query("UpsertPersonById", &json!({
        "id": "<person_id>",
        "new_age": 30,
    })).await?;
    println!("Upserted person: {result:#?}");

    Ok(())
}
package main

import (
    "fmt"
    "log"

    "github.com/HelixDB/helix-go"
)

func main() {
    client := helix.NewClient("http://localhost:6969")

    payload := map[string]any{
        "id":      "<person_id>",
        "new_age": uint32(30),
    }

    var result map[string]any
    if err := client.Query("UpsertPersonById", helix.WithData(payload)).Scan(&result); err != nil {
        log.Fatalf("UpsertPersonById failed: %s", err)
    }

    fmt.Printf("Upserted person: %#v\n", result)
}
import HelixDB from "helix-ts";

async function main() {
    const client = new HelixDB("http://localhost:6969");

    const result = await client.query("UpsertPersonById", {
        id: "<person_id>",
        new_age: 30,
    });
    console.log("Upserted person:", result);
}

main().catch((err) => {
    console.error("UpsertPersonById query failed:", err);
});
curl -X POST \
  http://localhost:6969/UpsertPersonById \
  -H 'Content-Type: application/json' \
  -d '{"id":"<person_id>","new_age":30}'

Example 3: Upsert with WHERE filter

Find existing nodes with a WHERE clause and update or create if not found.

QUERY UpdateOrCreatePerson(name: String, new_age: U32) =>
    existing <- N<Person>::WHERE(_::{name}::EQ(name))
    person <- existing::UpsertN({name: name, age: new_age})
    RETURN person
N::Person {
    INDEX name: String,
    age: U32,
}

Here’s how to run the query using the SDKs or curl

from helix.client import Client

client = Client(local=True, port=6969)

# If "Alice" exists, updates her age; otherwise creates a new person
result = client.query("UpdateOrCreatePerson", {
    "name": "Alice",
    "new_age": 30,
})

print("Upserted person:", result)
use helix_rs::{HelixDB, HelixDBClient};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = HelixDB::new(Some("http://localhost"), Some(6969), None);

    // If "Alice" exists, updates her age; otherwise creates a new person
    let result: serde_json::Value = client.query("UpdateOrCreatePerson", &json!({
        "name": "Alice",
        "new_age": 30,
    })).await?;

    println!("Upserted person: {result:#?}");

    Ok(())
}
package main

import (
    "fmt"
    "log"

    "github.com/HelixDB/helix-go"
)

func main() {
    client := helix.NewClient("http://localhost:6969")

    // If "Alice" exists, updates her age; otherwise creates a new person
    payload := map[string]any{
        "name":    "Alice",
        "new_age": uint32(30),
    }

    var result map[string]any
    if err := client.Query("UpdateOrCreatePerson", helix.WithData(payload)).Scan(&result); err != nil {
        log.Fatalf("UpdateOrCreatePerson failed: %s", err)
    }

    fmt.Printf("Upserted person: %#v\n", result)
}
import HelixDB from "helix-ts";

async function main() {
    const client = new HelixDB("http://localhost:6969");

    // If "Alice" exists, updates her age; otherwise creates a new person
    const result = await client.query("UpdateOrCreatePerson", {
        name: "Alice",
        new_age: 30,
    });

    console.log("Upserted person:", result);
}

main().catch((err) => {
    console.error("UpdateOrCreatePerson query failed:", err);
});
curl -X POST \
  http://localhost:6969/UpdateOrCreatePerson \
  -H 'Content-Type: application/json' \
  -d '{"name":"Alice","new_age":30}'

How Upsert differs from Add and Update

OperationBehavior
AddNAlways creates a new node
UPDATEOnly modifies existing nodes (fails if node doesn’t exist)
UpsertNOperates on traversal context: updates if nodes found, creates if empty

💡 Tip

When updating, UpsertN merges properties: it updates specified properties while preserving any existing properties that aren’t included in the upsert.