> For the complete documentation index, see [llms.txt](https://docs.acpt.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.acpt.io/custom-content-types/relational-fields.md).

# Relational fields

Link records across tables with a complete bidirectional Movies ↔ Directors example.

A **Relational** column links a record in one table to records in another table (or to WordPress posts, taxonomy terms, users, or option pages). This is the CCT equivalent of a foreign-key relationship surfaced in the admin UI as a searchable select field.

## Relational column settings

When you add a column of type **Relational** in the [Columns](/custom-content-types/columns.md) step, configure:

| Setting                  | Description                                                                           |
| ------------------------ | ------------------------------------------------------------------------------------- |
| **Relates to**           | The target: another CCT table, a CPT post type, a taxonomy, users, or an option page. |
| **Related table/entity** | The specific table or entity (e.g. `directors`).                                      |
| **Cardinality**          | Single (select one) or Multiple (select many).                                        |

ACPT automatically queries the related table to populate a dropdown or a searchable multi-select in the record form.

***

## Example — Movies ↔ Directors

This example builds a bidirectional one-to-many relationship:

* **One Director → Many Movies**
* From a movie you can navigate to its director
* From a director you can list all their movies

### Step 1 — Create the `directors` table

1. Navigate to **ACPT → Custom Content Types → Register Table**.
2. **General step:**
   * Name: `directors`
   * Label: `Directors`
   * ID strategy: `AUTO_INCREMENT`
3. **Columns step** — add:

| Column name | Type     | Required |
| ----------- | -------- | -------- |
| `name`      | Text     | Yes      |
| `bio`       | Textarea | No       |

4. Skip Indexes and Foreign Keys. **Save**.

### Step 2 — Create the `movies` table

1. Click **Register Table** again.
2. **General step:**
   * Name: `movies`
   * Label: `Movies`
   * ID strategy: `AUTO_INCREMENT`
3. **Columns step** — add:

| Column name   | Type       | Required | Notes                                              |
| ------------- | ---------- | -------- | -------------------------------------------------- |
| `title`       | Text       | Yes      |                                                    |
| `year`        | Number     | No       | 4-digit year                                       |
| `poster`      | Image      | No       | WordPress media library                            |
| `director_id` | Relational | No       | Relates to: `directors` table, cardinality: single |

4. **Indexes step** — add an index on `director_id` for query performance:

| Index name        | Key part      | Type  |
| ----------------- | ------------- | ----- |
| `idx_director_id` | `director_id` | INDEX |

5. **Foreign Keys step** — add a database-level constraint:

| Field            | Value                   |
| ---------------- | ----------------------- |
| Constraint name  | `fk_movies_director_id` |
| Local column     | `director_id`           |
| Reference table  | `directors`             |
| Reference column | `id`                    |
| On delete        | `SET NULL`              |
| On update        | `CASCADE`               |

6. **Save**.

### Step 3 — Create Table Groups

Create a `Directors Group` with location rules for the post type (or context) where directors should be managed, and a `Movies Group` for movies. Assign the respective tables to each group.

### Step 4 — Add example records

**Directors:**

| id | name              | bio                                                             |
| -- | ----------------- | --------------------------------------------------------------- |
| 1  | Denis Villeneuve  | Canadian filmmaker known for Arrival and Dune.                  |
| 2  | Christopher Nolan | British-American director known for Inception and Interstellar. |
| 3  | Ridley Scott      | British director known for Blade Runner and Alien.              |

**Movies:**

| id | title             | year | director\_id |
| -- | ----------------- | ---- | ------------ |
| 1  | Blade Runner 2049 | 2017 | 1            |
| 2  | Arrival           | 2016 | 1            |
| 3  | Dune              | 2021 | 1            |
| 4  | Inception         | 2010 | 2            |
| 5  | Interstellar      | 2014 | 2            |
| 6  | The Martian       | 2015 | 3            |

### Step 5 — Using the relationship in the record form

When creating or editing a movie record, the `director_id` column renders as a searchable dropdown listing all director names. Selecting "Denis Villeneuve" stores `1` in the `director_id` column.

<figure><img src="https://3235524095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fiyf0tSKPgXiyoQ3zV3I6%2Fuploads%2Fgit-blob-d5843e95512ab34fd5df9b3aae52b0462488a802%2Fcct-relational-field.png?alt=media" alt="Relational field in record form"><figcaption><p>The director_id column rendered as a searchable select field.</p></figcaption></figure>

### Step 6 — Navigating bidirectionally

Records are read through the [REST API](/developers/rest-api.md) — the record endpoint is driven by the table name (`/wp-json/acpt/v1/{tableName}`), and `filters` is a URL-encoded JSON array.

**From a movie to its director:**

```bash
# 1. fetch the movie, read director_id from the row
curl -G "https://example.com/wp-json/acpt/v1/movies/42" \
  -H "acpt-api-key: your-key-your-secret"

# 2. fetch that director by ID
curl -G "https://example.com/wp-json/acpt/v1/directors/1" \
  -H "acpt-api-key: your-key-your-secret"
```

**From a director, list all their movies** — filter `movies` on `director_id`, ordered by year:

```bash
curl -G "https://example.com/wp-json/acpt/v1/movies" \
  -H "acpt-api-key: your-key-your-secret" \
  --data-urlencode 'order_by[year]=ASC' \
  --data-urlencode 'filters=[{"column":"director_id","operator":"equals","value":"1"}]'
```

The response is the standard list envelope (`{ "currentPage", "prev", "next", "total", "records": [ … ] }`).

{% hint style="info" %}
There is no dedicated PHP helper for reading table records — the REST API is the supported read path. In PHP you can build the query yourself with `get_acpt_table_object('movies')->selectQuery([...])` and run it via `$wpdb`, but that is an internal API with no stability guarantee.
{% endhint %}

## Relating to WordPress content

A Relational column can also point to non-CCT entities:

| Target            | Description                                                               |
| ----------------- | ------------------------------------------------------------------------- |
| **CPT post type** | Links to a WordPress post of the specified post type. Stores the post ID. |
| **Taxonomy**      | Links to a taxonomy term. Stores the term ID.                             |
| **Users**         | Links to a WordPress user. Stores the user ID.                            |
| **Option page**   | Links to an ACPT option page.                                             |

The record form renders an autocomplete search field for all these cases.

## See also

* [Foreign Keys](/custom-content-types/foreign-keys.md) — database-level referential integrity
* [Columns](/custom-content-types/columns.md) — all column types
* [Records](/custom-content-types/records.md) — creating and managing records
