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

# Cindel Embedded Objects: Nested Value Types Explained

> Mark Dart classes with @embedded to store nested value objects inside a parent document, with support for lists of embedded objects and deep filter paths.

An embedded object is a value type stored directly inside its parent collection document. Unlike a root collection, an embedded object has no ID of its own and no independent lifecycle — it is always read and written together with the parent. Use embedded objects for structured data that belongs to a single parent record, such as a postal address inside an order or a recipient list inside an email.

## Declaring an Embedded Object

Annotate the nested class with `@embedded` or `@Embedded()` and use it as a field type in your root collection.

```dart theme={null}
import 'package:cindel/cindel.dart';

part 'order.g.dart';

@Collection()
class Order {
  Id dbId = autoIncrement;
  late String reference;
  late Address shippingAddress;
}

@Embedded()
class Address {
  late String street;
  late String city;
  late String country;
}
```

`Order` is the root collection. `Address` is stored inside each order document — there is no separate `addresses` collection or join step.

<Note>
  Embedded classes are not passed to `Cindel.open` as schema entries. Only root collection schemas go in the schema list. The generator registers embedded types through their parent collection automatically.
</Note>

***

## Key Constraints

Embedded objects are intentionally simpler than root collections. Keep these limits in mind when modeling:

<CardGroup cols={2}>
  <Card title="No own ID" icon="id-badge">
    Embedded objects do not have a `dbId` field. They are identified by their position inside the parent document.
  </Card>

  <Card title="No independent CRUD" icon="ban">
    You cannot call `get`, `put`, `delete`, or `filter` directly on an embedded class. All persistence goes through the parent collection.
  </Card>

  <Card title="No indexes on embedded fields" icon="magnifying-glass-minus">
    Cindel does not support `@Index` on fields of an embedded class. Place indexes on root collection fields when you need indexed lookup.
  </Card>

  <Card title="No nested lists" icon="list-ul">
    A `List` field inside an embedded object cannot itself contain a `List`. For deeper nesting, use additional embedded classes as list element types.
  </Card>
</CardGroup>

***

## Nullable and List Variants

Embedded fields can be nullable or collected into a list.

### Nullable Embedded Field

```dart theme={null}
@Collection()
class Customer {
  Id dbId = autoIncrement;
  late String name;
  Address? billingAddress; // optional
}
```

### List of Embedded Objects

```dart theme={null}
@Collection()
class Customer {
  Id dbId = autoIncrement;
  late String name;
  List<Address> previousAddresses = const [];
}
```

Each element in the list is stored as a full embedded document inside the parent record.

***

## Nested Embedding

An embedded object can itself contain another embedded object. This lets you model structured hierarchies up to any depth, as long as no level contains a nested list.

```dart theme={null}
@embedded
class Recipient {
  String? name;
  String? address;
  RecipientMetadata? metadata;
}

@embedded
class RecipientMetadata {
  String? label;
}

@Collection(name: 'emails')
class Email {
  Id dbId = autoIncrement;
  String? subject;
  Recipient? sender;
  List<Recipient>? recipients;
}
```

`Email` is the root. `Recipient` is stored inside each email. `RecipientMetadata` is stored inside each recipient.

***

## Filtering on Embedded Fields

The generated query API provides typed helpers that navigate embedded field paths. You do not need to write raw path strings for common queries.

### Filter a Single Embedded Object

```dart theme={null}
final messages = await db.emails
    .filter()
    .sender((recipient) {
      return recipient.addressEqualTo('ada@example.com');
    })
    .findAll();
```

### Filter Nested Embedded Objects

```dart theme={null}
final leads = await db.emails
    .filter()
    .sender((recipient) {
      return recipient.metadata((metadata) {
        return metadata.labelEqualTo('lead');
      });
    })
    .findAll();
```

### Filter a List of Embedded Objects by Element

```dart theme={null}
final sentToMary = await db.emails
    .filter()
    .recipientsElement((recipient) {
      return recipient.addressEqualTo('mary@example.com');
    })
    .findAll();
```

The generated helpers match the Dart field names of each embedded class, so renaming a field (while keeping its stored `@Name`) keeps your Dart code consistent.

***

## Embedded vs. Linked Root Collection

Choosing between an embedded object and a linked root collection comes down to lifecycle and sharing:

| Concern                        | Embedded object         | Linked root collection |
| ------------------------------ | ----------------------- | ---------------------- |
| Has its own identity/ID        | No                      | Yes                    |
| Fetched independently          | No — always with parent | Yes                    |
| Shared across multiple parents | No                      | Yes                    |
| Supports indexes               | No                      | Yes                    |
| Deleted independently          | No                      | Yes                    |

<Tip>
  A good rule of thumb: if you would describe the nested value as **belonging to** the parent (a shipping address belongs to an order), embed it. If you would describe the relationship as **connecting** two independent things (a song connects to an artist), use a root collection with a `CindelLink`.
</Tip>
