Connect entities with relationships
Link records across entities with one-to-one, one-to-many and many-to-many fields, set what happens on delete, then read and filter related data.
Last updated
A relationship is a field that points at records of another entity: an order belongs to a customer, a post has many tags. You store the link once and Anythink joins the data for you, so your REST API can return an order with its customer inside it, and filter orders by the customer's name.
This page covers choosing a relationship type, creating the field, deciding what happens when a linked record is deleted, linking and unlinking records, and reading and filtering related data. It ends with a worked example. To create the entities themselves first, see Model your data: entities, fields, relations.
Choose a relationship type
Choose the type by asking how many records sit on each side of the link.
| Type | Meaning | Where the link is stored | Example |
|---|---|---|---|
Many-to-One (many-to-one) |
Many records here point at one record there. | A column on this entity holding the other record's id. |
Many orders belong to one customer. |
One-to-Many (one-to-many) |
One record here has many records there. | On the other entity, in a many-to-one field you name when you create it. | One customer has many orders. |
One-to-One (one-to-one) |
One record here points at one record there, and no two records share it. | A unique column on this entity holding the other record's id. |
One customer_profile per customer. |
Many-to-Many (many-to-many) |
Records on both sides can have several partners. | A hidden join entity that Anythink creates and manages for you. | posts and tags. |
When to use each:
- Many-to-one is the one you'll use most. Put it on the "many" side: the
customerfield goes onorders. A record can have only one value in it, and it can be empty. - One-to-many is the same link seen from the other side. It gives the "one" side a list of the records that point at it, so a
customershows itsorders. It needs a many-to-one field on the other entity to hang off. The dashboard can create that field for you in the same step. It holds no data of its own, so you don't write to it to link records. You link from the many side. - One-to-one is for splitting a record in two. Keep rarely used or sensitive fields on a second entity with its own permissions. The database refuses a second record pointing at the same target.
- Many-to-many is for flexible sets. Use it when neither side owns the other, such as tags, categories or members. If the link itself needs fields, such as a quantity or a date, make a third entity with two many-to-one fields instead.
Three other field types are links too:
| Type | What it links to |
|---|---|
User (user) |
A user in your project. It works like a many-to-one to the users list. See Manage app users and team members. |
File Upload (file) |
One or more uploaded files. See Upload and manage files. |
Dynamic Reference (dynamic-reference) |
A text reference to a record of any entity. It isn't a database relationship, so it has no deletion rule and none of the sections below apply to it. |
Create a relationship field
You add a relationship by adding a field of a relationship type to an entity. Create both entities first.
In the Anythink dashboard
- Go to Settings › Data Model and select the entity that holds the link, such as
orders. - Select Add Field. The Create New Field drawer opens.
- Pick Many-to-One, or one of the other relationship types.
- Under Related Entity, choose the entity to link to, such as
customers. - Choose On Deletion (see What happens on delete).
- Enter a Name, such as
customer. - For One-to-Many only, choose a Foreign Key. It lists the many-to-one fields on the related entity. Choose + Create New Target Field to have Anythink create that many-to-one field, then give it a New Target Field Name.
- Select Create Field.
Every control on the form, in order:
| Control | Applies to | What it does |
|---|---|---|
| Field type tabs (Relationships) | All | Choose One-to-One, One-to-Many, Many-to-One, Many-to-Many or Dynamic Reference. |
| Related Entity | The four relationship types | The entity to link to. Required. The entity you're editing isn't listed. |
| Foreign Key | One-to-Many | The many-to-one field on the related entity that points back. Choose + Create New Target Field to create it. |
| New Target Field Name | One-to-Many, when creating the target field | The name of the new many-to-one field. Required. |
| On Deletion | Relationship, File Upload and User fields | Cascade, Set Null, Set Default or Restrict. Set Null is hidden when Required field is ticked. |
| Name | All | The field's name. Required. |
| Label | All | A friendlier name shown in the Anythink dashboard. |
| Description | All | Free text about the field. |
| Default Value | All | The value used when none is given. |
| Required field | All | The value can't be null or empty. |
| Unique field | All | The value must be unique across records. |
| Immutable field | All | The value can't be updated once saved. |
| Name field | All | Identifies the record visually. |
| Indexed | All | Creates a database index. |
| Searchable | All | Searchable in API queries. |
| Publicly Searchable | All, once Searchable is ticked | Allows public user searches. |
| Display Configuration | All | The display type (Relationship for these fields) and the field's Width. |
The related entity can't be changed afterwards, and neither can the field's name or type. To link to a different entity, add a new field.
With the CLI
anythink fields add orders customer --type many-to-one --target customers --on-delete "SET NULL"
anythink fields add profiles customer --type one-to-one --target customers
anythink fields add posts tags --type many-to-many --target tags
Other fields add flags work on relationship fields too: --label, --description, --default, --required, --unique, --indexed, --searchable, --public (publicly searchable) and --name-field. Run anythink fields add --help for the full list.
--target is the related entity's name, and --on-delete takes CASCADE (the default), SET NULL, SET DEFAULT or RESTRICT. Add --required to demand a value. A required field can't use SET NULL.
The CLI's fields add creates many-to-one, one-to-one and many-to-many fields. For the one-to-many side, add the many-to-one field on the child entity as above, then create the one-to-many field in the Anythink dashboard, or send the field to the API yourself. You need the related entity's ID (anythink entities get orders shows it) and the name of the many-to-one field it points back through:
anythink fetch /entities/customers/fields --method POST --body '{
"name": "orders",
"database_type": "one-to-many",
"display_type": "relationship",
"relationship": {
"target_entity_id": 654,
"target_field_name": "customer",
"on_deletion": "CASCADE"
}
}'
Check the result with anythink fields get customers orders.
With an AI assistant (MCP)
Link orders to customers: each order has one customer. If the customer is deleted, keep the order and clear the link.
The assistant uses the cli tool to run fields add orders customer --type many-to-one --target customers --on-delete "SET NULL". Ask for the reverse side too ("and let a customer show its orders") and it adds the one-to-many field through the API.
What happens on delete
Every relationship has an On Deletion rule. It says what happens to a record when the record it points at is deleted: delete a customer, and the rule decides what becomes of their orders.
| Rule | CLI value | What happens |
|---|---|---|
| Cascade | CASCADE |
The linked records are deleted too. Deleting a customer deletes their orders. |
| Set Null | SET NULL |
The link is cleared and the records stay. The orders remain with an empty customer. |
| Set Default | SET DEFAULT |
The link is set back to the field's default value. |
| Restrict | RESTRICT |
The delete is refused while linked records exist. The request returns 400. Remove or re-link the others first. |
Things to know:
- The rule sits on the side that holds the link. For a customer and their orders, it protects or removes the orders when the customer goes. For a one-to-many field, the rule you choose is applied to the many-to-one column it points through.
- Many-to-many links are removed with the record. Deleting a record removes its links, and never deletes the records on the other side. Cascade is the usual choice for a many-to-many field.
- Required fields can't use Set Null. The dashboard hides the option and the API refuses it, because it would empty a required value.
- Cascade is permanent. Deleting a record removes everything it cascades to, with no undo. Prefer
RESTRICTorSET NULLfor data you can't afford to lose, such as orders.
The rule applies wherever the delete comes from: the dashboard, the CLI, the API or a workflow. See Model your data: entities, fields, relations for deleting entities.
Link records to each other
You set a link by writing the related record's id into the relationship field. How you write it depends on the type.
| Field type | Value to send |
|---|---|
| Many-to-one, one-to-one, user | The related record's id: "customer": 1. An object with an id, { "id": 1 }, also works. |
| Many-to-many, file | An array of related ids: "tags": [1, 4, 9]. |
| One-to-many | Nothing. You link from the many side by setting the many-to-one field on each child record. |
In the Anythink dashboard
- Open the entity from the sidebar. Select the Add button, named after the entity (such as Add Order), to create a record.
- For a many-to-one or one-to-one field, pick the related record from the list.
- For a one-to-many or many-to-many field, select Add Existing to pick records, or Create New to make a new related record and link it.
- Save the record.
The user needs orders:create to add records. See Roles and permissions.
With the CLI
Set the link when you create the record:
anythink data create orders --data '{"reference": "A-3", "total": 30, "customer": 1}'
anythink data create posts --data '{"title": "Hello", "tags": [1, 4]}'
A link to an id that doesn't exist is refused with 400, and a record that's locked can't be changed. The same body works over the REST API:
POST /org/{project_id}/entities/orders/items
Content-Type: application/json
{ "reference": "A-3", "total": 30, "customer": 1 }
See REST API reference for the record endpoints.
With an AI assistant (MCP)
Create an order A-3 for 30 pounds for customer 1, and tag post 7 with tags 1, 4 and 9.
The assistant uses the cli tool to run data create and data update with the ids. To find an id first, ask it to look the customer up by name.
Read related records
A record includes its related data under the relationship field's name. The shape depends on the type, and you control how much comes back with the fields parameter.
| Relationship | Shape under the field name |
|---|---|
| Many-to-one, one-to-one | The related record as an object with its fields, or null when it isn't set. |
| One-to-many, many-to-many | An object { "__count": n, "__data": [ … ] }. __count is how many related records there are, and __data holds them. |
| User | An object with the user's id, gid, first_name, last_name and email, or null. |
| File | An object { "__count": n, "__data": [ … ] } of file details. See Upload and manage files. |
Many-to-one and one-to-one fields come back fully expanded by default. One-to-many and many-to-many fields come back with each related record's id and __name only, plus the field that points back. __name is the value of the related entity's name field, or its id if it has none.
An order with its customer:
{
"locked": false,
"id": 3,
"reference": "A-3",
"customer": {
"id": 1,
"name": "Ada Lovelace",
"email": "ada@example.com",
"__name": "Ada Lovelace"
},
"__name": "A-3"
}
A customer with their orders:
{
"id": 1,
"name": "Ada Lovelace",
"orders": {
"__count": 2,
"__data": [
{ "id": 3, "__name": "A-3", "customer": 1 },
{ "id": 7, "__name": "A-7", "customer": 1 }
]
}
}
Choose what comes back
Use fields to ask for exactly what you need. Relationship fields use dots to reach into the related record:
fields value |
Effect |
|---|---|
reference,total |
Own fields only. Relationship fields are left out. |
reference,customer.name |
Expand customer with only its name. The related record's id and __name always come with it. |
name,orders |
Expand the one-to-many with each order's id and __name. |
name,orders.reference,orders.total |
Expand it with those fields of each order. |
reference,customer.country.name |
Follow a relationship through another relationship, as far as the depth limit allows. |
GET /org/{project_id}/entities/orders/items/3?fields=reference,customer.name
{
"locked": false,
"id": 3,
"reference": "A-3",
"customer": { "id": 1, "name": "Ada Lovelace", "__name": "Ada Lovelace" },
"__name": "A-3"
}
Expansion follows relationships up to four levels deep and never visits the same relationship twice on one path. The related records you get back are filtered by the caller's permissions and Groups and row-level security, just like top-level records. Two callers can see different __data for the same record.
In the Anythink dashboard
The record grid shows a related record by its name, and a count such as 3 items for one-to-many and many-to-many fields. Open a record to see the related records in full, and select one to open its details in a panel. The Select Fields picker above the grid lists the fields of related entities as paths such as customer.name, so you can add them as columns. See Records, views and CSV import.
With the CLI
anythink data get orders 3
anythink fetch "/entities/orders/items?fields=reference,total,customer.name&pageSize=5"
anythink fetch "/entities/customers/items/1?fields=name,orders.reference"
data get returns the record with its default expansion. Use fetch when you need fields.
With an AI assistant (MCP)
Show me order A-3 with its customer's name and email, and list the orders for customer 1.
The assistant uses the cli tool to call the records endpoint with fields and shows you the related records it gets back.
Filter on related data
Filter records by a field of a related record with a dotted path: the relationship field's name, a dot, then the related entity's field. It uses the same operators as any other filter. See Filter operators reference.
GET /org/{project_id}/entities/orders/items?customer.name=C:lovelace
That returns the orders whose customer's name contains lovelace. Going the other way, from the one side:
GET /org/{project_id}/entities/customers/items?orders.status=paid
returns the customers that have at least one paid order. You can chain relationships, such as customer.country.name=United Kingdom, and combine related filters with ordinary ones. They join with AND.
In the Anythink dashboard
Filtering across a relationship is a query on the records API, so use the CLI or REST API shown below. In the grid, add the related field as a column with Select Fields to see it beside each record.
With the CLI
anythink data list orders --filter 'customer.name=C:lovelace&status=paid'
anythink data list customers --filter '{"orders.status": "paid"}'
With an AI assistant (MCP)
Which orders belong to customers whose name contains Lovelace?
The assistant runs data list orders with a customer.name filter and shows the matching orders.
A path that names a field the entity doesn't have is ignored, so a misspelt path returns unfiltered records. Check the total if a result looks too big.
Worked example: orders and customers
This builds the example used above. A customer has many orders, and each order belongs to one customer. If a customer is deleted, their orders stay but lose the link.
In the Anythink dashboard
- In Settings › Data Model, create
customerswith a required, unique Small text fieldemailand a Small text fieldname, marked as the name field. - Create
orderswith a Small text fieldreference(name field) and a Decimal fieldtotal. - On
orders, add a Many-to-One field namedcustomer. Choosecustomersas the Related Entity and Set Null for On Deletion. - On
customers, add a One-to-Many field namedorders. Chooseordersas the Related Entity andcustomeras the Foreign Key. Set On Deletion to Set Null. - Open
customersin the sidebar, add Ada Lovelace, then openordersand add an order withcustomerset to her.
With the CLI
anythink entities create customers
anythink fields add customers name --type varchar --required --name-field
anythink fields add customers email --type varchar --required --unique
anythink entities create orders
anythink fields add orders reference --type varchar --name-field
anythink fields add orders total --type decimal
anythink fields add orders customer --type many-to-one --target customers --on-delete "SET NULL"
anythink data create customers --data '{"name": "Ada Lovelace", "email": "ada@example.com"}'
anythink data create orders --data '{"reference": "A-3", "total": 30, "customer": 1}'
Add the reverse orders field on customers with the one-to-many request shown earlier, setting target_entity_id to the ID of orders and target_field_name to customer. Then read both directions:
anythink fetch "/entities/orders/items?fields=reference,customer.name"
anythink fetch "/entities/customers/items/1?fields=name,orders.reference,orders.total"
anythink data list orders --filter 'customer.name=C:lovelace'
With an AI assistant (MCP)
Set up customers and orders. A customer has a name and a unique email. An order has a reference and a total. Each order belongs to one customer, and a customer shows their orders. If a customer is deleted, keep their orders and clear the link. Then add Ada Lovelace with one order for 30 pounds, and show me the order with its customer.
The assistant creates both entities and their fields with the cli tool, adds the links, creates the two records and reads the order back with its customer expanded.
Before your app's users can read any of this, grant their role orders:read and customers:read. A user who can read orders but not customers sees the order without the customer's details.
Limits
| Limit | Value |
|---|---|
| Expansion depth | Four levels. |
| Changing a relationship field | You can't change its related entity, name or type. Add a new field instead. |
| Deletion rules | CASCADE, SET NULL, SET DEFAULT or RESTRICT. SET NULL isn't allowed on a required field. |
| One-to-one | Each target record can be linked from only one record. |
| One-to-many | Needs a many-to-one field on the related entity to point through. |
| Page size on a list | 1 to 1000 records. Default 25. |
Next steps
- Model your data: entities, fields, relations: create the entities and fields your links connect.
- Call the instant REST API: read and write records through the API.
- REST API reference: every record endpoint, filter and error.
- Filter operators reference: all the filter operators.
- Groups and row-level security: control which related records each user sees.
- Upload and manage files: file fields link records to files.