Entity Types

Understanding ER and non-ER entity types in dbt-nexus — when to promote a concept to an entity.

Nexus entities are any business object that needs state tracking, relationships to other entities, or entity resolution. Originally, entities were persons and groups — things that required entity resolution (ER) to merge identifiers from multiple sources into a single resolved identity. Nexus now supports a second class of entity: non-ER entities, which are objects like subscriptions, contracts, or projects that have their own lifecycle but don't require the full ER pipeline.

When to Promote Something to an Entity

Promote a concept (subscriptions, contracts, projects, tasks, etc.) to an entity when it needs at least one of:

  1. State tracking — you want to ask "what was the state of X at time T?" (e.g., subscription lifecycle, contract status, MRR over time)
  2. Entity resolution — the concept exists in multiple sources and needs identity merging (e.g., a project exists in both Notion and GitHub)
  3. Relationships — you want to connect it to other entities (e.g., company owns subscription, person is assigned to project)

Not everything should be an entity. Events that are purely point-in-time facts (like individual payments or page views) remain as events. Only promote when you need the capabilities above. There should be a handful of entity types per project, not dozens.

Configuration

Entity types are configured in dbt_project.yml under the nexus var:

vars:
  nexus:
    entity_types:
      person:
        entity_resolution: true
      group:
        entity_resolution: true
      subscription:
        entity_resolution: false
        registration_model: stripe_subscription_entities

Each entity type specifies:

  • entity_resolution: true/false — whether it goes through the full ER pipeline
  • registration_model (non-ER only) — the dbt model that registers entities

ER Entities (Entity Resolution)

ER entities go through the full entity resolution pipeline:

  1. Source identifier extraction (*_entity_identifiers)
  2. Edge creation and deduplication
  3. Recursive connected-components resolution
  4. Trait resolution from EAV (nexus_entity_traits)
  5. Trait pivoting onto nexus_entities

Use entity_resolution: true when:

  • The entity appears in multiple source systems with different identifiers
  • You need to merge identifiers (e.g., same person with multiple emails)
  • Traits come from multiple sources and need resolution

Examples: persons, groups/companies, organizations.

Non-ER Entities (Registered Entities)

Non-ER entities bypass entity resolution entirely. They register directly using the register_entities() macro:

-- models/sources/stripe/stripe_subscription_entities.sql
{{ config(materialized='table', tags=['stripe', 'entities']) }}

{{ nexus.register_entities(
    source_model='stripe_subscriptions',
    entity_type='subscription',
    source='stripe',
    source_id_column='id',
    trait_columns=['currency', 'billing_interval', 'collection_method', 'subscription_source'],
    created_at_column='created_at',
    updated_at_column='_updated_at'
) }}

The macro generates:

  • A deterministic entity_id from the source ID
  • entity_type and source columns
  • Trait columns passed through directly (no EAV intermediate)
  • _source_created_at and _source_updated_at timestamps (when provided) that flow into _created_at and _updated_at on nexus_entities

Use entity_resolution: false when:

  • The entity exists in only one source system
  • It has a single unique identifier (no merging needed)
  • You need it for state tracking, relationships, or event participation

Examples: subscriptions, contracts, projects, tasks.

How Non-ER Entities Integrate

Non-ER entities participate in all the same Nexus systems as ER entities:

Feature ER Entities Non-ER Entities
nexus_entities Via resolved identifiers Via registration model
nexus_entity_states Full support Full support
nexus_relationships Identifiers resolved from ER Source ID used as identifier
nexus_participants Via identifier resolution Via source_id lookup
Trait metadata From EAV pipeline From registration model introspection
nexus_entity_identifiers_to_entity_id All resolved identifiers Source ID mapped to entity_id

Event Participation for Non-ER Entities

To make non-ER entities participants on events, add their identifiers to the source's *_entity_identifiers model:

-- stripe_subscription_identifiers.sql
SELECT
    {{ nexus.create_nexus_id('entity_identifier', ['event_id', 'subscription_id', ...]) }} as entity_identifier_id,
    event_id,
    CAST(NULL AS STRING) as edge_id,  -- no ER needed
    'subscription' as entity_type,
    'subscription_id' as identifier_type,
    subscription_id as identifier_value,
    'subscription' as role,
    occurred_at,
    'stripe' as source
FROM subscription_events

The finalize_non_er_participants() macro resolves these identifiers against the registration model to produce participant records.