---
title: "Split a model across files"
description: "Load other source files with @\"path\" so a base model and its overrides unify into one document."
source: "https://aontu.dev/how-to/split-a-model-across-files/"
---

# Split a model across files

Load other source files with @"path" so a base model and its overrides unify into one document.

Rendered from [`docs/how-to/split-a-model-across-files.md`](https://github.com/aontu-lang/aontu/blob/main/docs/how-to/split-a-model-across-files.md) in the engine repository, where a correction belongs, and where the test suite executes every example on this page.

One file holds a model well until two teams edit it, or until the environment-specific lines outnumber the shared ones. `@"path"` loads another source file and unifies it in place, so a split costs nothing: the parts mean exactly what the whole meant. Write the shared shape as `base.aon`:

```aontu
server: { host:string port: *8080|integer debug: *false|boolean }
```

the production pins as `override.aon`:

```aontu
server: { host:"app.corp.example" port:8443 }
```

and an entry file `main.aon` that loads both:

```aontu
@"./base.aon"
@"./override.aon"
```

```sh
$ aontu main.aon
{
  "server": {
    "debug": false,
    "host": "app.corp.example",
    "port": 8443
  }
}
```

The override beats the `*8080` default, `debug` falls back to its own, and `host: string` is satisfied rather than replaced. Unification is order-independent, so swapping the two include lines changes nothing: there is no cascade to reason about, only one merged document.

An include is an ordinary value, so it can land under a key. Write `car.aon`:

```aontu
color: silver
doors: 4
```

and mount it in `lot.aon`, constraints attached:

```aontu
car: @"./car.aon"
car: { doors:number wheels:4 }
```

```sh
$ aontu lot.aon
{
  "car": {
    "color": "silver",
    "doors": 4,
    "wheels": 4
  }
}
```

The loaded file never learns it was mounted: its `doors: 4` [meets](https://aontu.dev/docs/unification) the local `doors: number` the way any two conjuncts meet, and a conflict between a loaded value and a local one is a normal unification error (read one with [read a conflict error](https://aontu.dev/how-to/read-a-conflict-error)).

Watch the paths. A relative include resolves against the including file’s own directory (the CLI starts the chain at the entry file’s directory), so a tree of split files moves as a unit. The embedding APIs accept a base directory and an in-memory resolver for tests; see [`AontuOptions`](https://aontu.dev/docs/reference-api#aontuoptions). And one shape of path bypasses the filesystem entirely: a first segment carrying a dot and no `./` prefix (`@"corp.example/schemas/service"`) is a module import, resolved from the project’s vendor tree under the lockfile’s pins. The path carries no version: compatibility is computed at publish, so the major left the name. That story starts in [vendor a dependency closure](https://aontu.dev/how-to/vendor-a-dependency-closure).

The full loading contract, extension defaulting included, is [source loading](https://aontu.dev/docs/reference-language#source-loading-) in the language reference. [Change a value with an overlay](https://aontu.dev/how-to/change-a-value-with-an-overlay) uses this same split so machine edits land in a file of their own, and [use-cases/11-shared-modules](https://aontu.dev/use-cases/11-shared-modules) is the live version of a model split all the way across repositories.
