---
title: "Wrap domain values in their own types · Agent Wranglers"
url: https://agentwranglers.se/playbook/architecture/data-shapes/value-objects
updated: 2026-10-20
licence: "All rights reserved"
---

# Wrap domain values in their own types

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Recommended
- Chapter: [Data shapes](https://agentwranglers.se/playbook/architecture/data-shapes)
- Rule: Give each domain value with its own rules a small type, such as `EmailAddress`, `OrderId` or `Money`. Never pass it around as a bare string or number.
- How: Validate the value once, in the type's constructor or factory, and trust it everywhere after. Two IDs then cannot be swapped by mistake, because their types differ. Use branded types in TypeScript, `NewType` or a frozen dataclass in Python, and a readonly class in PHP. Store money as the [money rule](https://agentwranglers.se/playbook/api-design/requests-and-responses/money) says, never as a float.
- Check: A type test passes a swapped ID to a typed function and marks the line as an expected error. Examples are `// @ts-expect-error` in TypeScript and `# type: ignore[arg-type]` with mypy `--warn-unused-ignores`. The checker fails if the error ever disappears. A unit test gives each factory an invalid value, such as `"not-an-email"`, and expects an error.

Example:

```ts
type OrderId = string & { readonly brand: 'OrderId' };
type CustomerId = string & { readonly brand: 'CustomerId' };

function refund(order: OrderId, customer: CustomerId) { /* ... */ }
refund(customerId, orderId); // Fails to compile, because the arguments are swapped
```

```python
OrderId = NewType("OrderId", str)

@dataclass(frozen=True)
class EmailAddress:
    value: str

    def __post_init__(self):
        if "@" not in self.value:
            raise InvalidEmail(self.value)
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers architecture playbook: https://agentwranglers.se/playbook/architecture/data-shapes/value-objects`.

- Previous: [Use typed objects instead of loose arrays](https://agentwranglers.se/playbook/architecture/data-shapes/typed-data-objects)
- Next: [Never type anything as mixed or any](https://agentwranglers.se/playbook/architecture/data-shapes/no-mixed-or-any)
- [Data shapes](https://agentwranglers.se/playbook/architecture/data-shapes)
