Skip to main content

Read an Entity's Edit History

Every change in Geo is published as an edit, recorded as one or more EditVersion rows. Values and relations track which edit they came from. That means you can answer questions like:

  • Who last changed this entity?
  • When was this value first published?
  • What did this entity look like a month ago?

This recipe walks the read patterns for all three.

Concept — versioned data

In GRC-20 (Req #10), the 9 op types are: CreateEntity, UpdateEntity, DeleteEntity, RestoreEntity, CreateRelation, UpdateRelation, DeleteRelation, RestoreRelation, CreateValueRef. Each one writes a row to:

  • EditVersion — one row per published edit (the "commit")
  • ValueVersion — historical state of every value, with validFromKey / validToKey defining when it was current
  • RelationVersion — same idea for relations

A row is current when its validToKey is null. When superseded by a later edit, validToKey gets set and a new row is inserted with a fresh validFromKey.

1. List recent edits across the graph

query RecentEdits {
editVersions(first: 10, orderBy: BLOCK_NUMBER_DESC) {
editId
name
blockNumber
sequence
createdAt
createdById
}
}
{
"data": {
"editVersions": [
{
"editId": "5e736eea...",
"name": "Hospitals (10 entities)",
"blockNumber": "128935",
"sequence": "1",
"createdAt": "2026-05-05T16:57:09",
"createdById": "..."
},
{
"editId": "aa6c9086...",
"name": "Bounty links for: Hospitals (10 entities)",
"blockNumber": "128934",
"sequence": "0",
"createdAt": "2026-05-05T16:56:54"
}
]
}
}
Loading interactive query runner…

2. History of a specific entity's values

valueVersions filtered by entityId returns every version of every value ever attached to that entity:

query EntityValueHistory($id: UUID!) {
valueVersions(
first: 50
filter: { entityId: { is: $id } }
) {
id
propertyId
text
integer
date
datetime
validFromKey
validToKey
}
}
{ "id": "9e49e5a95d2a41a6ba90cd84944080b5" }
{
"data": {
"valueVersions": [
{
"id": "805736af...",
"propertyId": "41aa3d98...",
"date": "2022-08-22Z",
"validFromKey": "457237923364864",
"validToKey": null
},
{
"id": "9860640572...",
"propertyId": "eed38e74...",
"text": "https://stability.ai/stable-image",
"validFromKey": "457237923364864",
"validToKey": null
}
]
}
}
Loading interactive query runner…

Reading the response:

  • validToKey: null → this version is current.
  • validToKey: "..." → this version was superseded by a later edit. The newer version is somewhere else in the result set with a matching validFromKey.

To find the "current value of property P on entity E":

const current = data.valueVersions.find(
v => v.propertyId === P && v.validToKey === null
);

To trace history of one property over time:

const history = data.valueVersions
.filter(v => v.propertyId === P)
.sort((a, b) => a.validFromKey.localeCompare(b.validFromKey));
// First entry was the original; later entries are subsequent edits

3. History of a specific entity's relations

Same shape, different table:

query EntityRelationHistory($id: UUID!) {
relationVersions(
first: 50
filter: { fromEntityId: { is: $id } }
) {
id
typeId
fromEntityId
toEntityId
validFromKey
validToKey
}
}

Useful to answer: "When did this entity stop being tagged as a Project?" (Look for a validToKey set on the Types relation.)

4. What did this edit do? Find all rows from one edit

Every value/relation row carries an implicit edit reference via validFromKey. To list everything written by edit X, filter on its validFromKey:

query EditChangelog($validFromKey: String!) {
valueVersions(first: 100 filter: { validFromKey: { is: $validFromKey } }) {
entityId
propertyId
text
date
}
relationVersions(first: 100 filter: { validFromKey: { is: $validFromKey } }) {
fromEntityId
typeId
toEntityId
}
}

This is how the editVersions field links to its actual content — by shared validFromKey.

Notes

  • validFromKey / validToKey are opaque string keys, not human timestamps. Use them for ordering and equality, not parsing. To get a human timestamp, join through editVersions on the matching block number.
  • createdById is the personal space ID of the publisher (the wallet's identity), not their wallet address. To resolve to a human name, look up the entity by ID.
  • Deletion is a versioning event: DeleteEntity ops set validToKey on existing values/relations of the entity. The rows aren't physically removed — you can still read them with validToKey: { isNull: false }.
  • Restore = a new version: RestoreEntity creates fresh validFromKey rows pointing back to the previous data. Trace via valueVersions ordering.
  • Performance: valueVersions and relationVersions are large tables. Always filter by entityId (or another narrow predicate) — never paginate the entire table.