UltipaDocs
Products
Solutions
Resources
Company
Start Free Trial
UltipaDocs
Start Free Trial
  • Introduction
  • GQL vs Other Languages
    • Overview
    • Node and Edge Patterns
    • Path Patterns
    • Quantified Paths
    • Questioned Paths
    • Shortest Paths
    • Cheapest Paths
    • K-Hop Traversal
    • Graph Patterns
    • Overview
    • Open Graphs
    • Closed Graphs
    • Graph Types
    • Constraints
    • Projections
    • Storage Maintenance
    • Node and Edge IDs
    • INSERT
    • INSERT OVERWRITE
    • UPSERT
    • MERGE
    • SET
    • REMOVE
    • DELETE
    • FOREACH
    • LOAD CSV
    • Query Composition
    • Result Table and Visualization
    • MATCH
    • OPTIONAL MATCH
    • FILTER
    • LET
    • FOR
    • ORDER BY
    • LIMIT
    • SKIP
    • CALL
    • RETURN
    • Composite Query
    • NEXT
    • All Functions
    • Element Functions
    • Path Functions
    • Aggregate Functions
    • Mathematical Functions
    • Trigonometric Functions
    • String Functions
    • List Functions
    • Datetime Functions
    • Spatial Functions
    • Null Functions
    • Utility Functions
    • Type Conversion Functions
    • Table Functions
  • Operators
  • Predicates
    • CASE
    • LET Value Expression
    • Value Query Expression
    • List Expressions
    • Current Values
    • Index
    • Full-text Index
    • Vector Index
  • Transactions
  • Triggers
  • Query Management
  • Execution Plan
    • Variables
    • Values and Types
    • Comments
    • Reserved Words
    • Naming Conventions
    • Syntactic Notation
  • GQL Conformance
  1. Docs
  2. /
  3. ISO GQL
  4. /
  5. Data Manipulation

Node and Edge IDs

Every node and edge in a graph carries two built-in identifiers:

  • _id: the user-facing identifier. A string you can assign yourself or let the system generate. Nodes always have it; edges' _id can be toggled off/on. Use _id for application data models and any identifier you want to control or reference from outside the database.
  • _uuid: the system-internal identifier. A UINT64 formatted as a decimal-string automatically assigned by the engine. Always populated for every node and edge. Use _uuid when you need an identifier that's guaranteed to resolve, especially for edges in edge _id disabled graphs.

Node _id

Each node has a unique identifier _id:

PropertyValue TypeDescription
_idSTRINGValue can be either system-generated (UUID v4) or manually assigned during insertion. Once assigned, a node's _id is immutable.

Edge _id

Each edge has a unique identifier _id and two endpoint references.

PropertyValue TypeDescription
_idSTRINGIf the graph's edge _id is enabled, value can be either system-generated (UUID v4) or manually assigned during insertion.

If the graph's edge _id is disabled, value is only system-generated (in the e:<N> form, where <N> is the internal numeric ID, such as e:1) during insertion, custom values cannot be assigned.

Once assigned, a edge's _id is immutable while the graph remains in its current edge _id status.

See below for details.
_fromSTRINGThe _id of the source node.
_toSTRINGThe _id of the destination node.

Edge _id on Graph Creation

Edge _id is enabled by default for new graphs. To disable edge _id at graph creation:

GQL
CREATE GRAPH myGraph WITH EDGE_ID DISABLED

Toggling Edge _id

You can disable edge _id on an existing graph at any time:

GQL
ALTER GRAPH myGraph SET EDGE_ID DISABLED

After disabling edge _id on a graph, the hidden edge _id index is dropped and the in-memory cache is cleared.

Disabling edge _id does not immediately erase the UUID v4 _id values already stored on existing edges. Each edge retains its originally assigned _id until it is rewritten by SET on one of its properties, at which point the _id reverts to the system-assigned e:<N> form.

Enable edge _id on an existing graph:

GQL
ALTER GRAPH myGraph SET EDGE_ID ENABLED

After enabling edge _id on a graph, a background converter assigns UUIDs to legacy edges and the system auto-creates a hidden _id index for edges; progress can be inspected with SHOW EDGE_ID STATUS.

Checking Edge _id Status

Inspect edge _id status of the current graph:

GQL
SHOW EDGE_ID STATUS

The result includes the following fields:

FieldDescription
graphThe graph name.
statusThe current edge _id state, one of DISABLED, ENABLING, ENABLED, or DISABLING.
progressNumber of edges processed by the background converter.
totalTotal number of edges to process.
percentConversion completion percentage.
skippedNumber of edges the converter passed over without rewriting: decode errors, deleted edges (tombstones), or edges whose _id was already a valid UUID and present in the cache.
started_atWhen the current conversion started.

The ENABLING and DISABLING states appear only while a background converter is running. On a steady-state graph, status is either ENABLED or DISABLED, and the progress columns reflect the most recent transition (or zeros if no transition has ever run).

Inserting with Edge _id

When edge _id is enabled, an _id can be supplied explicitly or omitted:

GQL
-- Explicit _id (requires edge _id enabled)
MATCH (a {_id: 'a'}), (b {_id: 'b'})
INSERT (a)-[:Knows {_id: 'tx-12345'}]->(b)

-- Auto-generated UUID v4 _id (when _id is omitted)
MATCH (a {_id: 'a'}), (b {_id: 'b'})
INSERT (a)-[:Knows]->(b)

When edge _id is disabled, providing _id in an INSERT on an edge is rejected:

GQL
ALTER GRAPH myGraph SET EDGE_ID DISABLED

-- Auto-generated _id in the e:<N> form (when _id is omitted)
MATCH (a {_id: 'a'}), (b {_id: 'b'})
INSERT (a)-[:Knows]->(b)

Matching by Edge _id

Matching an edge by _id, whether via WHERE e._id = 'X' or the inline form [e {_id: 'X'}], requires edge _id to be enabled on the graph. When edge _id is disabled, GQLDB blocks _id-based edge lookups.

GQL
-- Edge _id lookup (requires edge _id enabled)
MATCH ()-[e WHERE e._id = 'tx-12345']->() RETURN e

Reading _id (e.g., RETURN e._id) always works regardless of the edge _id status.

Why Edge _id is a Toggle but Node _id is Not

A node and an edge with edge _id enabled both support fast O(1) _id lookup, but for different reasons.

Nodes are stored directly by their _id, so looking up MATCH (n {_id: 'U1'}) goes straight to the right node, no extra structure is needed.

Edges are stored by where they connect (source, label, destination), not by _id. So to look an edge up by _id, the database has to maintain a separate hidden index that maps each _id back to its edge. That hidden index — plus an in-memory cache that makes it fast — is exactly what the edge _id feature provides.

NodesEdges (edge _id on)
How they're storedIndexed by _idIndexed by their endpoints
_id lookupDirect, always availableGoes through the edge _id hidden index + cache
Extra disk / memory costNoneThe hidden index on disk, plus a per-edge cache entry in memory

That's why nodes don't have an edge-_id-style switch: their _id lookup costs nothing extra. Edges only have the option because the hidden index and cache are real overhead — turning edge _id off gives that overhead back, in exchange for losing _id-based edge lookup.

When to Disable Edge _id

Although enabled by default, you may want to disable edge _id to skip its overhead in workloads that never need to address an edge by _id:

  • Memory. Each enabled edge keeps a ~50-byte entry in the in-memory edge _id cache. At scale this is significant: 100M edges ≈ 5 GB, 1B edges ≈ 50 GB.
  • Storage. A hidden _id property index is maintained on disk while edge _id is enabled. Disabling drops the index entirely.
  • Write throughput. Every edge insert / overwrite must generate (or accept) a UUID, perform a uniqueness check in the cache, and update the hidden index. Bulk-load and ETL pipelines are measurably faster with edge _id off.
  • Workload doesn't need it. Pure topology workloads — graph algorithms (PageRank, centrality, community detection), k-hop traversal, recommendation engines — walk the graph through endpoints and labels and never address individual edges by _id.
  • Backward compatibility. Pre-existing schemas, drivers, or queries written before edge _id may rely on the no-_id edge model. Disabling keeps that behavior.

Internal ID (_uuid)

Every node and edge also carries a system-internal numeric identifier exposed as the _uuid property and the internal_id() function. Unlike _id, the internal ID is always available, including for edges in an edge _id disabled graph.

PropertyValue TypeDescription
_uuidSTRINGThe system-assigned uint64 identifier, formatted as a decimal string (returned as a string because the value can exceed the signed-64-bit range). Always populated and immutable.

Both forms return the same value:

GQL
-- Property-form access
MATCH (n)-[e]->()
RETURN n._uuid, e._uuid

-- Function-form access (works on any expression, including computed elements)
MATCH (n)-[e]->()
RETURN internal_id(n), internal_id(e)

Matching by _uuid

You can filter by _uuid, but only via the WHERE clause, and the lookup is a full scan (there is no _uuid index).

GQL
MATCH (n) WHERE n._uuid = '12345' RETURN n
MATCH ()-[e WHERE e._uuid = '67890']->() RETURN e

So _uuid is best thought of as a stable identifier you can always read and emit, not a fast lookup key. If your workflow is "return a _uuid from one query, then MATCH an entity by that _uuid later," it works through the WHERE clause but pays a scan cost. Prefer _id when you need fast lookup-by-handle round trips.

When to use which

NeedUse
Stable user-facing identifier you control (custom strings, UUIDs you assigned)_id
Identifier that always resolves, even when edge _id is disabled_uuid
Identifier for debugging, logging, or cross-system correlation_uuid — guaranteed unique within the graph and never null
Node/Edge fast lookup_id (edges require edge _id enabled)

The _uuid is not a substitute for _id in user-facing data models. It exists primarily so the database always has a guaranteed-unique handle for every element, regardless of feature toggles. Treat it as an operational / diagnostic identifier rather than a stable application key - bulk restore, edge _id toggle, and similar operations may renumber _uuids.