> ## Documentation Index
> Fetch the complete documentation index at: https://docs.conversion.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Understanding Custom Objects

> A first-time guide to custom objects: how objects, records, fields, and relationships fit together, explained with diagrams.

This guide builds a mental model of custom objects for people who haven't used them before. For the full reference, setup steps, and UI details, see [Custom Objects](https://docs.conversion.ai/product-docs/records/custom-objects/overview.md).

***

## Think of each object as a table

The easiest way to picture Conversion's data model is as a set of spreadsheet tabs or database tables. Contacts, companies, and opportunities are tables Conversion gives you. A custom object is a table you add for data that doesn't fit them, such as products, subscriptions, or projects.

| Conversion term | Table term | Example |
| :- | :- | :- |
| **Object** | Table | *Products* |
| **Record** | Row | The "Analytics Pro" product |
| **Field** | Column | `plan`, `seats`, `renews_at` |
| **Object ID** | Primary key | `prod_123` |
| **Primary field** | The column shown as each row's name | `name` |
| **Relationship field** | Foreign key column | The contact who owns the product |

```mermaid theme={null}
erDiagram
  CONTACTS {
    string user_id "your ID for the contact"
    string email
    string first_name
  }
  PRODUCTS {
    string object_id "hidden ID, set by your source"
    string name "primary field"
    number seats
    date renews_at
    string contact "relationship field to Contacts"
  }
  CONTACTS ||--o{ PRODUCTS : "owns"
```

Two of these terms need a closer look:

* **Object ID:** every custom object record has a hidden ID that identifies it, such as the product ID from your warehouse or the record ID from Salesforce. Syncs and the API use it to find a record again and update it instead of creating a duplicate. Other records use it to link to this one.
* **Primary field:** the field Conversion shows as each record's name in lists, chips, and search. For *Products*, that's usually the product name. It's a display choice and doesn't need to be unique.

***

## Relationships work like foreign keys

A relationship field links each record to one record of another object. If you've worked with databases, it's a foreign key.

### The link lives on the "many" side

When one record can have many related records, such as one contact owning many products, the relationship field goes on the object that has many rows: *Products*. Each product holds the one contact it belongs to.

This feels backwards at first, because you might expect the contact to "hold" its products. But a contact can't hold an unknown number of products in one field. Each product can hold exactly one contact.

```mermaid theme={null}
flowchart RL
  subgraph Products["Products: the child"]
    P1["Analytics Pro<br/>contact: Ana"]
    P2["Reporting Add-on<br/>contact: Ana"]
    P3["Analytics Pro<br/>contact: Raj"]
  end
  subgraph Contacts["Contacts: the parent"]
    C1["Ana"]
    C2["Raj"]
  end
  P1 --> C1
  P2 --> C1
  P3 --> C2
```

Rule of thumb: **create the relationship field on the child object, the one where the foreign key would live in a database.**

### One relationship, two fields

When you create a relationship field, Conversion also adds a matching field to the related object. Using the example above:

| Field | Lives on | Holds | Who can set it |
| :- | :- | :- | :- |
| **Contact** (the field you create) | *Products* | One contact | The app, syncs, and the API |
| **Products** (the matching field) | Contacts | Every product that points at the contact | The app only. Syncs and the API can't write it |

```mermaid theme={null}
flowchart LR
  subgraph You["You create"]
    A["Products → Contact<br/>holds one contact<br/>this is the real link"]
  end
  subgraph Auto["Conversion adds"]
    B["Contacts → Products<br/>lists every product pointing here<br/>a view of the same link"]
  end
  A <-->|"same link, seen from both sides"| B
```

The matching field is a convenience. It lets you see a contact's products on the contact's record, filter contacts by their products, and loop over products in an email. It isn't a second copy of the data, and when you load data you only ever set the field you created. In most cases you can keep the default label Conversion suggests for it.

> [!TIP]
>
> Not sure which side holds the link? Edit the field on a record. The field you created opens **Edit related record** (pick one). The matching field opens **Edit related records** (check several).

### What a relationship value points at

The value you put in a relationship field has to identify the related record. Which value Conversion accepts depends on the object you're linking to:

| Linking to | Use this value |
| :- | :- |
| Contacts | The contact's `userId` (your ID for the contact) or email address |
| Companies | The company's Salesforce Account ID |
| A custom object | The related record's object ID |

Email works for contacts because it's usually unique. A relationship holds only one record, so if two contacts share an email, the link reaches only one of them. Use `userId` when you have it.

For custom objects, always link by the object ID, not by the name or any other field. If *Dashboards* records use `dash_42` as their object ID, a product links to that dashboard with the value `dash_42`.

### Objects can link to many objects

An object can have any number of relationship fields. Here, *Products* links to both the contact who owns it and the dashboard it appears on:

```mermaid theme={null}
erDiagram
  CONTACTS ||--o{ PRODUCTS : "owns"
  DASHBOARDS ||--o{ PRODUCTS : "shows"
  CONTACTS {
    string email
  }
  DASHBOARDS {
    string object_id
    string name "primary field"
  }
  PRODUCTS {
    string object_id
    string name "primary field"
    string contact "relationship to Contacts"
    string dashboard "relationship to Dashboards"
  }
```

*Products* holds both relationship fields because it's the child in both relationships: one contact owns many products, and one dashboard shows many products.

### Many-to-many and details about a link

A relationship is always one-to-many, and it has no fields of its own. When a contact can belong to many projects and a project can have many contacts, or when you need to store something about the link (such as the contact's role on the project), add a custom object for the link itself. That's the same "join table" you'd use in a database.

```mermaid theme={null}
erDiagram
  CONTACTS ||--o{ PROJECT_MEMBERS : "has"
  PROJECTS ||--o{ PROJECT_MEMBERS : "has"
  PROJECT_MEMBERS {
    string object_id
    string contact "relationship to Contacts"
    string project "relationship to Projects"
    string role
  }
```

See [Storing details about a link](https://docs.conversion.ai/product-docs/records/custom-objects/overview.md#storing-details-about-a-link).

***

## Where records come from

You define the object in Conversion first. Then records arrive from one or more sources. The sources differ in how much of the setup they do for you:

| Source | Fields | Relationship fields | Records matched by |
| :- | :- | :- | :- |
| **Salesforce** | Created for you from the Salesforce object | Not created for you. Create them, then link them to the Salesforce lookup fields | Salesforce ID |
| **Data warehouse** | You create each field you want to sync | You create them, then select a column for each in your sync query | The `object_id` column |
| **API** | You create each field | You create them | `objectId` |
| **Manually** in the app | You create each field | You create them | Not applicable |

```mermaid theme={null}
flowchart LR
  SF["Salesforce"] --> CO
  WH["Data warehouse"] --> CO
  API["API"] --> CO
  UI["Manual entry"] --> CO
  CO["Custom object records<br/>in Conversion"] --> F["Filters and audiences"]
  CO --> W["Workflow triggers"]
  CO --> L["Personalization with Liquid"]
  CO --> S["Lead scoring"]
```

To load a custom object from your data warehouse, see [Syncing Custom Objects from a Warehouse](https://docs.conversion.ai/product-docs/sync/data-warehouse/custom-objects.md).

***

## What you can do with custom objects

Once records are linked to contacts or companies, the rest of Conversion can reach them through the relationship:

* **Filter** contacts by their related records in audiences, workflow conditions, and list views, for example contacts whose company has a subscription on the *Enterprise* plan. See [Filtering by related records](https://docs.conversion.ai/product-docs/records/custom-objects/overview.md#filtering-by-related-records).
* **Trigger workflows** when a related record's field changes, for example when a subscription's renewal date moves. See [Triggering workflows](https://docs.conversion.ai/product-docs/records/custom-objects/overview.md#triggering-workflows).
* **Personalize** emails with related records' values. See [Personalizing with Liquid](https://docs.conversion.ai/product-docs/records/custom-objects/overview.md#personalizing-with-liquid).
* **Score** leads on related records with [lead scoring](https://docs.conversion.ai/product-docs/scoring/rules.md) fit rules.

Records that aren't linked to anything still appear in their own records list, but filters and workflows can't reach them from a contact.

***

## Recap

1. An object is a table, a record is a row, and a field is a column.
2. Every custom object record has an object ID. The primary field is just the name Conversion shows.
3. Create a relationship field on the child object, where the foreign key would live.
4. Creating a relationship field also creates a matching list field on the related object. You only ever load data into the field you created.
5. Link to contacts by `userId` or email, to companies by Salesforce Account ID, and to custom objects by object ID.

Ready to build one? Follow [Set up a custom object](https://docs.conversion.ai/product-docs/records/custom-objects/overview.md#set-up-a-custom-object).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.