Skip to content

Reference Traversal

Reference traversal lets you filter thogits based on the properties of other thogits they link to. Instead of looking up a referenced thogit’s ID and filtering by it, you write a single filter expression that crosses the reference boundary using the -> operator.

A Reference field stores a ULID pointing to another thogit. When you define a schema like this:

{
"assignee": "Reference",
"project": "Reference"
}

The values are thogit IDs:

{
"assignee": "01JQXYZ000PERSON00000ALICE",
"project": "01JQXYZ000PROJECT0000API"
}

On its own, you can filter by exact ID: {"Task.assignee": "01JQXYZ000PERSON00000ALICE"}. But that requires knowing the ID upfront. Reference traversal lets you filter by properties of the target instead.

The arrow operator -> chains a Reference field to a filter on the target thogit:

SourceTag.ref_field->TargetTag.target_field

For example:

{"Task.project->Project.name": {"contains": "API"}}

This reads as: “Find tasks whose project reference points to a thogit tagged Project where the name field contains ‘API’.”

The server resolves this by:

  1. Looking at each Task thogit’s project field value (a ULID)
  2. Loading the referenced thogit
  3. Checking if it has the Project tag
  4. Checking if its name field contains “API”
  5. Including the original Task thogit in results if the check passes

You can chain multiple -> operators to traverse through several references. Each hop follows a Reference field to a new thogit and applies the next segment.

{"Task.project->Project.lead->Person.department": {"match": "Engineering"}}

This reads as: “Find tasks whose project’s lead is a person in Engineering.”

The chain resolves left to right:

  1. Task.project — follow the task’s project reference
  2. ->Project.lead — on that project, follow the lead reference
  3. ->Person.department — on that person, check the department field

The final segment after the last -> can be:

TargetExampleDescription
Tag field->Project.nameFilter on a field of the target thogit
Thogit name->nameFilter on the target thogit’s name
Thogit description->descriptionFilter on the target thogit’s description
Tag presence->has_tagCheck if the target has a specific tag

This section walks through setting up a full reference graph and querying across it.

  1. Terminal window
    curl -X POST https://app.thogits.com/api/tags \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "name": "Person",
    "fields": {
    "department": {
    "type": "Select",
    "variants": [
    "Engineering",
    "Design",
    "Product",
    "Marketing"
    ]
    },
    "title": "String"
    }
    }'

    Save the tag_id from the response — used below as PERSON_TAG_ID.

  2. Terminal window
    curl -X POST https://app.thogits.com/api/tags \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "name": "Project",
    "fields": {
    "name": "String",
    "lead": "Reference",
    "status": {
    "type": "Select",
    "variants": [
    "Planning",
    "Active",
    "Complete"
    ]
    }
    }
    }'

    Save as PROJECT_TAG_ID.

  3. Terminal window
    curl -X POST https://app.thogits.com/api/tags \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "name": "Task",
    "fields": {
    "status": {
    "type": "Select",
    "variants": [
    "Todo",
    "In Progress",
    "Done"
    ]
    },
    "priority": "Number",
    "project": "Reference",
    "assignee": "Reference"
    }
    }'

    Save as TASK_TAG_ID.

  4. Terminal window
    # Alice - Engineering
    curl -X POST https://app.thogits.com/api/thogits \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "name": "Alice Chen",
    "tags": [{
    "tag_ref": {"Existing": "PERSON_TAG_ID"},
    "field_values": {
    "department": {"variant": "Engineering"},
    "title": "Staff Engineer"
    }
    }]
    }'
    # Response: {"thogit_id": "ALICE_ID"}
    # Bob - Design
    curl -X POST https://app.thogits.com/api/thogits \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "name": "Bob Park",
    "tags": [{
    "tag_ref": {"Existing": "PERSON_TAG_ID"},
    "field_values": {
    "department": {"variant": "Design"},
    "title": "Senior Designer"
    }
    }]
    }'
    # Response: {"thogit_id": "BOB_ID"}
  5. Terminal window
    # API project led by Alice
    curl -X POST https://app.thogits.com/api/thogits \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "name": "API Redesign",
    "tags": [{
    "tag_ref": {"Existing": "PROJECT_TAG_ID"},
    "field_values": {
    "name": "API Redesign",
    "lead": "ALICE_ID",
    "status": {"variant": "Active"}
    }
    }]
    }'
    # Response: {"thogit_id": "API_PROJECT_ID"}
    # Brand project led by Bob
    curl -X POST https://app.thogits.com/api/thogits \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "name": "Brand Refresh",
    "tags": [{
    "tag_ref": {"Existing": "PROJECT_TAG_ID"},
    "field_values": {
    "name": "Brand Refresh",
    "lead": "BOB_ID",
    "status": {"variant": "Active"}
    }
    }]
    }'
    # Response: {"thogit_id": "BRAND_PROJECT_ID"}
  6. Terminal window
    # Task on API project, assigned to Alice
    curl -X POST https://app.thogits.com/api/thogits \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "name": "Design new endpoint schema",
    "tags": [{
    "tag_ref": {"Existing": "TASK_TAG_ID"},
    "field_values": {
    "status": {"variant": "In Progress"},
    "priority": 1,
    "project": "API_PROJECT_ID",
    "assignee": "ALICE_ID"
    }
    }]
    }'
    # Task on Brand project, assigned to Alice
    curl -X POST https://app.thogits.com/api/thogits \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "name": "Review brand guidelines",
    "tags": [{
    "tag_ref": {"Existing": "TASK_TAG_ID"},
    "field_values": {
    "status": {"variant": "Todo"},
    "priority": 2,
    "project": "BRAND_PROJECT_ID",
    "assignee": "ALICE_ID"
    }
    }]
    }'
    # Task on API project, unassigned
    curl -X POST https://app.thogits.com/api/thogits \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "name": "Write migration scripts",
    "tags": [{
    "tag_ref": {"Existing": "TASK_TAG_ID"},
    "field_values": {
    "status": {"variant": "Todo"},
    "priority": 3,
    "project": "API_PROJECT_ID"
    }
    }]
    }'
  7. Now use the -> operator to query across these relationships.

    Find tasks on projects led by someone in Engineering:

    Terminal window
    curl -X POST https://app.thogits.com/api/thogits/search \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "filter": {
    "Task.project->Project.lead->Person.department": {"match": "Engineering"}
    }
    }'

    This returns “Design new endpoint schema” and “Write migration scripts” — both tasks on the API Redesign project, which is led by Alice (Engineering). The “Review brand guidelines” task is excluded because its project is led by Bob (Design).

    Find tasks on projects whose name contains “API”:

    Terminal window
    curl -X POST https://app.thogits.com/api/thogits/search \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "filter": {
    "Task.project->Project.name": {"contains": "API"}
    }
    }'

    Combine traversal with direct filters:

    Terminal window
    curl -X POST https://app.thogits.com/api/thogits/search \
    -H "Content-Type: application/json" \
    -b cookies.txt -c cookies.txt \
    -d '{
    "filter": {
    "and": [
    {"Task.priority": {"lte": 2}},
    {"Task.project->Project.lead->Person.department": {"match": "Engineering"}},
    {"not": {"Task.status": {"match": "Done"}}}
    ]
    }
    }'

    This finds high-priority, not-done tasks on Engineering-led projects. Only “Design new endpoint schema” matches (priority 1, In Progress, on the Alice-led API project).

Dangling references — If a Reference field points to a thogit that has been deleted, the traversal treats the filter as not matching (the thogit is excluded from results). No error is raised.

Null references — If the Reference field is null (not set), the traversal does not match. Use is_null to explicitly find thogits with unset references:

{"Task.assignee": {"is_null": true}}

Non-Reference fields — Using -> on a field that is not a Reference type produces a validation error. The server checks the schema before executing the query.

Each hop in a traversal requires loading the referenced thogit and checking its fields. For large datasets:

  • Keep traversal chains short (1-2 hops is ideal)
  • Combine traversals with direct filters in and blocks to narrow the candidate set first
  • Use has_tag as the first condition to limit which thogits are evaluated