Relations
Relation columns link CRM records across tables — built-in and user-defined relationships, edges, and cardinality.
A relation column links records in one CRM object table to records in another. Unlike other columns, the linked values do not live in the row: each link is an edge stored in the tenant database with provenance (api, cli, tool, workflow, system), timestamps, and archive state. The relation column is the anchor that renders those edges on the table grid and record page.
Relations are CRM object semantics. They exist on object-backed tables (companies, people, deals, and your custom objects) — a plain working table has no relation columns until you promote it to a CRM object.
Built-in relationships
oxygen crm setup creates the standard objects with their relationships already defined:
| Source | Slug | Target | Inverse | Cardinality |
|---|---|---|---|---|
companies | team | people | company | one company : many people |
deals | company | companies | deals | many deals : one company |
deals | primary_contact | people | deals | many deals : one person |
deals | stakeholders | people | deals_involved | many : many, with a per-edge role |
oxygen crm objects describe <object> --json lists every relationship an object participates in, both directions.
Defining your own relationship
Define a relationship from a custom object to any object (custom or standard). This creates the relation column on the source and registers the relationship definition; a custom target with --inverse-slug gets the inverse relation column too.
# Preview first (default is dry-run)
oxygen crm relationships define projects client \
--target-object companies \
--cardinality many_to_one \
--inverse-slug projects \
--json
# Apply
oxygen crm relationships define projects client \
--target-object companies \
--cardinality many_to_one \
--inverse-slug projects \
--live --jsonMCP: oxygen_crm_relationship_define with { object, slug, target_object, cardinality?, inverse_slug?, mode }.
Rules:
- The source must be a custom object — standard object schemas (companies/people/deals) are managed by
crm setupand cannot grow new relation columns. Point the relationship at the standard object instead and use the inverse slug to navigate back. - The relationship slug doubles as the relation column key on the source table (snake_case).
- Definitions are free internal schema writes — no credits.
- Self-relationships (
projects→projects) are allowed with distinct forward and inverse slugs.
Cardinality
--cardinality is read source:target, and controls how many active edges each record may hold. Writing an edge beyond a one side archives the previous edge (visible in edge history, not deleted).
| Value | Meaning |
|---|---|
one_to_one | Each source record links one target, each target one source |
one_to_many | Each source record links many targets; each target links back to one source |
many_to_one | Each source record links one target; a target can be linked by many sources |
many_to_many | No limit on either side (default) |
Linking records
Once a relationship is defined (built-in or custom), link records with an edge write:
oxygen crm relationships upsert projects <row_id> \
--relationship client \
--target-row-id <company_row_id> \
--live --jsonDefaults to dry-run; the preview shows which edges would be created and which would be archived by cardinality. MCP: oxygen_crm_relationship_upsert.
Inspect the result:
oxygen crm records get projects <row_id> --json # relationships.outbound / .inbound
oxygen crm objects describe projects --json # definitions on the objectOn the web, the record page renders each relation column with a search-picker; edges also appear on the linked record's side.
Plain tables
Working tables (imports, TAM builds, scratch lists) don't carry relations. To relate their rows, promote the table onto a CRM object first, then define the relationship on that object:
oxygen tables promote <table-id> --object <object> --dry-run --json