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:
- State tracking — you want to ask "what was the state of X at time T?" (e.g., subscription lifecycle, contract status, MRR over time)
- Entity resolution — the concept exists in multiple sources and needs identity merging (e.g., a project exists in both Notion and GitHub)
- 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 pipelineregistration_model(non-ER only) — the dbt model that registers entities
ER Entities (Entity Resolution)
ER entities go through the full entity resolution pipeline:
- Source identifier extraction (
*_entity_identifiers) - Edge creation and deduplication
- Recursive connected-components resolution
- Trait resolution from EAV (
nexus_entity_traits) - 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_idfrom the source ID entity_typeandsourcecolumns- Trait columns passed through directly (no EAV intermediate)
_source_created_atand_source_updated_attimestamps (when provided) that flow into_created_atand_updated_atonnexus_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.