> For the complete documentation index, see [llms.txt](https://docs.opendatadiscovery.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.opendatadiscovery.org/features/data-modelling/relationships.md).

# Relationships

ODD Platform tracks entity-to-entity relationships as first-class catalog objects. They show up two ways: as **ERD diagrams** between table-class entities (foreign-key-style edges) and as **graph relationships** between graph-store entities (Neo4j-style edges). Both are surfaced under [Data Modelling](/features/data-modelling.md) → Relationships in the UI and through the `/api/relationships` API.

## The two relationship classes

The platform's internal model defines a `DATA_RELATIONSHIP(9)` data-entity class that contains two types:

| Internal type             | API filter value | What it represents                                                                                                                                                                                           |
| ------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ENTITY_RELATIONSHIP(25)` | `ERD`            | Foreign-key-style edges between two table-class entities. Cross-schema references are supported. The collector that catalogs the source emits one `ENTITY_RELATIONSHIP` per detected foreign-key constraint. |
| `GRAPH_RELATIONSHIP(26)`  | `GRAPH`          | Free-form graph edges between graph-store entities (e.g., relationships between nodes in a Neo4j database). Distinct from ERD because the cardinality model is graph-native, not relational.                 |

A relationship is a regular catalog entity — it has an ODDRN, an owner, a namespace inherited from its source / target, and shows up in search alongside datasets. The dedicated UI surface and `/api/relationships` API are for **list and detail navigation** of just the relationship-class entities.

## ERD cardinality model (ENTITY\_RELATIONSHIP)

ERD relationships carry the source-and-target table pair plus a cardinality classifier. Four classifiers are defined upstream:

* `ONE_TO_EXACTLY_ONE` — one source row maps to exactly one target row.
* `ONE_TO_ZERO_OR_ONE` — one source row maps to zero or one target row.
* `ONE_TO_ONE_OR_MORE` — one source row maps to one or more target rows.
* `ONE_TO_ZERO_ONE_OR_MORE` — one source row maps to zero, one, or many target rows.

These mirror the cardinality vocabulary defined in the `odd-collectors` monorepo's [Relationships section](https://github.com/opendatadiscovery/odd-collectors#relationships).

## UI walkthrough

Open **Data Modelling → Relationships** from the top-level navigation (`/data-modelling/relationships`). The list page shows every visible relationship across all data sources — as of 0.28.0 it applies the catalog's default visibility rules, so soft-deleted, hollow, and excluded-from-search relationship entities are filtered out (earlier releases listed them here even after every other catalog surface hid them). The page shows:

* **A table** with columns Name, Type (ERD or GRAPH), Namespace + Datasource, Source entity, Target entity.
* **A type tab strip** filtering by `ALL` / `ERD` / `GRAPH` (matches the API enum).
* **A search input** filtering by name across the visible set.
* **Infinite-scroll pagination** — page size is 30 by default.

Click a row to open the relationship's detail page; the platform routes to the `/api/relationships/erd/{id}` or `/api/relationships/graph/{id}` payload depending on the relationship type.

![Relationships list page — eight ENTITY\_RELATIONSHIP rows from a Snowflake sample-data source, each with its name, type (ENTITY RELATIONSHIP), namespace + datasource, source entity, and target entity. The All / ERD / Graph type-filter strip sits above the table; the right-rail switches between Query Examples and Relationships, the two Data Modelling sub-surfaces.](/files/VySae3Sx4hMRi2x3A5qY)

The same data is also surfaced **per-entity** — every dataset's detail page has a **Relationships** tab (in the same tab strip as Overview / Structure / Lineage / Test reports / Alerts / Query examples / Activity / Discussions) that lists only the relationships in which the current entity participates as Parent or Child:

![Per-entity Relationships tab on the ORDERS table (DS / TABLE) — two relationships: (1) ORDERS\_references\_CUSTOMER with CUSTOMER as Parent and ORDERS as Child, cardinality ONE TO ZERO ONE OR MORE, Is Identifying False; (2) LINEITEM\_references\_ORDERS with ORDERS as Parent and LINEITEM as Child, cardinality ONE TO ZERO ONE OR MORE, Is Identifying True. Each row carries an inline ERD-style cardinality glyph (crow's-foot notation) between the Parent and Child columns.](/files/4TIesWKioG3ElTGhXcAE)

The per-entity view is the operator-relevant perspective: when reading a table's documentation, you see only its incoming and outgoing relationships, not the whole catalog's. The Parent / Child columns and the cardinality glyph match the underlying `ENTITY_RELATIONSHIP` model and the same `/api/relationships/erd/{id}` payload surfaces both in this tab and on the global Data Modelling → Relationships list above. As of 0.28.0 this tab hides soft-deleted and hollow relationship entities, but — unlike the global list — it deliberately keeps `exclude_from_search`-flagged ones visible: that flag scopes discovery surfaces, and the dataset's own detail tab would be silently incomplete without its real relationships.

## API surface

The three endpoints exposed by `RelationshipController` are documented at [API Reference → Relationships](/developer-guides/api-reference/relationships.md) — `GET /api/relationships` (paginated list with optional `type=ERD|GRAPH|ALL` filter and free-text query) plus the two per-type detail endpoints. The same payloads also surface on the source and target dataset's detail pages under the dataset's relationships cluster.

## How relationships get into the catalog

Relationships are populated **by the ingesting collector** during the source-side metadata pull. The platform does not infer relationships from naming conventions or column-name similarity — only what the collector emits is surfaced.

Adapter coverage as of the time of writing (per the upstream [`odd-collectors` Relationships matrix](https://github.com/opendatadiscovery/odd-collectors#relationships)):

| Collector       | Adapter      | Relationship type           | How it's derived                                                                |
| --------------- | ------------ | --------------------------- | ------------------------------------------------------------------------------- |
| `odd-collector` | `postgresql` | `ENTITY_RELATIONSHIP` (ERD) | Foreign-key constraints in the database catalog. Cross-schema FKs are detected. |
| `odd-collector` | `snowflake`  | `ENTITY_RELATIONSHIP` (ERD) | Foreign-key constraints in the database catalog. Cross-schema FKs are detected. |

{% hint style="info" %}
**No other adapter currently emits relationships.** The `GRAPH_RELATIONSHIP` type is reserved in the platform model for graph-store sources (e.g., Neo4j edges), but no adapter in the published `odd-collectors` Relationships matrix currently surfaces graph relationships at the time of writing — operators using a Neo4j adapter today get nodes catalogued without their inter-node edges. This is an upstream collector-side gap; if your deployment relies on graph relationships, follow the upstream repo for adapter changes.
{% endhint %}

## Known operator caveats

Four behaviours of the Relationships surface are non-obvious from the UI alone — they are model contracts to know about, not defects. Two further code-side defects from earlier releases (the Target-column rendering and the unvalidated `?type=` parameter) were **fixed in 0.28.0**; the notes below describe them for operators on 0.27.x and earlier.

{% hint style="info" %}
**Fixed in 0.28.0 — the Target column previously showed the source entity.** On 0.27.x and earlier, the list-row renderer passed the source entity's data to both the Source and the Target cells, so every row displayed the same dataset twice and a discovery workflow of the form "find every relationship where `ORDERS` is the target" was impossible from this page. As of 0.28.0 the Target column renders the actual target entity. On affected releases, use the per-entity Relationships tab (which was never affected) for target-side questions.
{% endhint %}

{% hint style="warning" %}
**There is no RBAC gate on the Relationships endpoints — any authenticated caller can list every visible relationship in the catalog.** Neither `GET /api/relationships` nor the two per-type detail endpoints (`/api/relationships/erd/{id}` and `/api/relationships/graph/{id}`) are enumerated in the platform's authorization rules; the catch-all "any authenticated user" rule applies, and the query does not filter by owner or namespace. The catalog UI route mounts bare — contrast with `/data-modelling/query-examples`, which is wrapped in a permission-context provider. (Until 0.28.0 the listing also ignored the catalog's visibility flags — soft-deleted and excluded-from-search relationship entities stayed listed here after every other surface hid them; as of 0.28.0 the default visibility rules apply, so this caveat is now purely about access, not visibility.)

**Operator-visible consequence.** Every authenticated user reads every visible relationship in the catalog, across team boundaries. Under `auth.type=DISABLED`, the same listing is anonymous. Multi-team deployments expecting per-team isolation on relationships cannot enforce it through the platform's RBAC today.

**Mitigation today.** Treat the Relationships listing as catalog-read-collaborative — the same posture as the Owner / Namespace / Datasource directories. If your deployment requires per-team isolation on relationships specifically, enforce it at the network perimeter (reverse-proxy rules on `/api/relationships/*` plus the corresponding UI route).
{% endhint %}

{% hint style="warning" %}
**Clicking a row on the list opens the source entity's detail page — not a relationship-type-specific URL.** The row's Name link routes to `/dataentities/{id}` (the entity-detail-page route) on every click, regardless of relationship type. The two type-specific endpoints (`/api/relationships/erd/{id}` and `/api/relationships/graph/{id}`) are reached **from inside** the entity-detail page's relationship-card rendering, not from the row click on the list page. If you are scripting deep-links into "the relationship's detail view," use the per-entity Relationships tab on the source / target entity's page; there is no list-page row URL that lands on a type-specific relationship view today.
{% endhint %}

{% hint style="warning" %}
**The search input filters by relationship name only — not by source or target entity name.** Typing `orders` into the list-page search box matches relationships whose own `name` field contains the string `orders` (case-insensitive). It does **not** match relationships where the source or target entity is named `ORDERS`. The behaviour is consistent with how the platform models relationships as first-class entities, but the framing on the page above ("search input filtering by name across the visible set") does not call out the scope.

**To find every relationship that involves a specific dataset**, open that dataset's detail page → Relationships tab. That surface lists only the relationships in which the entity participates as Parent or Child, which is the question operators usually mean when they search.
{% endhint %}

{% hint style="info" %}
**Fixed in 0.28.0 — a mistyped `?type=` URL parameter previously rendered a dead, blank view.** On 0.27.x and earlier, the page sent the raw value to the backend, which rejected it with an opaque HTTP 400 from enum-binding; the tab strip showed no active tab and the list read "0 relationships overall" with no error message — indistinguishable from an empty catalog. As of 0.28.0 an unknown value (`?type=erd` lower-case, `?type=Erd` mixed-case, anything outside `ALL` / `ERD` / `GRAPH`) falls back to the `ALL` view with the All tab active. The accepted tokens are still exactly `ALL` / `ERD` / `GRAPH` (case-sensitive) — deep links using other spellings load the unfiltered list, not the intended tab.
{% endhint %}

{% hint style="info" %}
**The `relationship_id` path parameter on the API is the relationship's data-entity id, not the `relationships` table primary key.** The platform models a relationship as a regular catalog entity (entity-class id `9`, `DATA_RELATIONSHIP`). The API parameter named `relationship_id` is the corresponding `data_entity.id`; the underlying `relationships.id` row is internal and is not the value the API consumes.

The same trap exists **inside the API payload itself**: the details response exposes `erd_relationship.erd_relationship_id` and `graph_relationship.graph_relationship_id` — internal detail-record ids from two further tables. Feeding either back into `/api/relationships/{type}/{id}` returns a 404 (or, on a numeric coincidence with another relationship's data-entity id, the payload of an unrelated relationship). The only value that round-trips is the relationship's own `id` field from the list or details payload; the UI always uses it and is unaffected. As of 0.28.0 the OpenAPI spec states this on the `relationship_id` parameter and on both `*_relationship_id` fields.
{% endhint %}

## Where to next

* [Data Modelling overview](/features/data-modelling.md) — parent section; pairs Relationships with Query Examples.
* [Query Examples](/features/data-modelling/query-examples.md) — the other Data Modelling sub-surface.
* [Integrations → odd-collector](/integrations/integrations/odd-collector.md) — the generic collector that hosts the PostgreSQL and Snowflake adapters surfacing ERD relationships today; the per-adapter feature matrix on that page calls out ERD support explicitly.
* [Main Concepts](/introduction/main-concepts.md) — for the broader catalog vocabulary that names these entities.
