Detailed reference for IOModel YAML model structure, syntax, and file-based authoring.
This reference explains how IOModel YAML models are structured in real projects, including the exact file layout used in apps/studio/content/example-saas/models and schema definitions from packages/model-schema/assets.
What this page covers
You will learn: 1) how model files are split, 2) which keys are supported at
model/object/link level, 3) how schema-driven types (c4, kubernetes)
affect authoring.
File structure (project-level)
IOModel projects use a root index file and per-model files:
# models.yaml
models:
example-saas: null# models/example-saas.yaml
title: Multi-Tenant SaaS Platform
type: model
description: Reference architecture for a B2B SaaS product.
objects:
actors: null
external_systems: null
product: null
workflows: {}And then each object can be split into its own file under:
models/example-saas/objects/<slug>.yaml
models/example-saas/objects/<slug>/<child>.yaml
...For example:
# models/example-saas/objects/product/application/api_service.yaml
title: API Service
type: microservice:service
description: Tenant-aware REST + WebSocket API.
objects:
rest_router: null
tenant_filter: null
ws_gateway: null
rate_limiter: nullCore YAML keys
Model root keys (models/<model>.yaml)
| Key | Type | Required | Example | Purpose |
|---|---|---|---|---|
title | string | no | title: Multi-Tenant SaaS Platform | Human-readable model name |
type | string | no | type: model | Model category label |
description | string | no | description: ... | Summary for UI/docs |
icon | string | no | icon: lucide:box | Optional model icon |
objects_schema | string | no | objects_schema: c4 | Object vocabulary: a built-in id, your own schema, or a schema URL |
objects | map | yes (practical) | objects: { product: null } | Root object tree |
workflows | map | no | workflows: {} | Workflow definitions (if used) |
Object keys (.../objects/*.yaml)
The parser handles these object keys directly:
| Key | Type | Example | Notes |
|---|---|---|---|
title | string | title: Tenant Service | Display title |
type | string | type: container:database | Object type; subtype is base:subtype |
description | string | description: Owns tenant records. | Text description |
icon | string | icon: lucide:server | Optional override icon |
group_type | string | group_type: product | Group/category marker |
ref | string | ref: product.application.api_service | Reference pointer |
attributes | map | attributes: { owner: [a@x.com] } | Free-form attributes |
tags | array | tags: [domain:billing, env:prod] | group:value tags |
links | array | links: [{ id: api_service }] | Outgoing links |
link_to | array | link_to: [{ id: product.application }] | Belongs-to links |
attachments | map/array | attachments: { handbook: { ... } } | Attached files (MDX, OpenAPI, ERD, SQL, …) |
attachments_view_mode | map | attachments_view_mode: { overview: [...] } | Attachment display order |
objects | map | objects: { router: null } | Child object slugs |
models | map | models: {} | Reserved/advanced |
article | string/map | article: ... | Optional content reference |
id | string | id: custom-id | Explicit id when needed |
Any extra top-level keys not listed above are still preserved as custom object
properties. This is useful for schema-specific fields such as team_owner,
status, tech, etc.
Retired key `artifacts:`
The parser no longer reads artifacts:. Rename it to attachments: (Studio
reports retired-yaml-key until you do). Cloud projects can be rewritten
with a one-off migrate; on disk, rename the key in YAML.
Link syntax and references
A link entry can be a short scalar or an object form.
Short form
links:
- api_serviceExtended form
links:
- id: product.application.api_service
description: Calls API for tenant operations
type: sync
required: true
tags:
- flow:request
attributes:
protocol: httpsCommon reference patterns:
- Relative sibling:
api_service - Relative parent scope:
product.application - Absolute path:
example-saas.product.application.api_service
If a reference cannot be resolved, parser diagnostics include an unknown-reference error.
Schema-driven modeling
The objects_schema on the model root selects the object vocabulary — which type values exist, which subtypes (encoded in type as base:subtype) are allowed, which property fields appear, and how objects look on the diagram. The default is c4.
objects_schema accepts three forms:
| Form | Example | Use when |
|---|---|---|
| Built-in id | objects_schema: c4 | Standard C4 or Kubernetes modeling |
| Custom schema | objects_schema: my-schema | You define your own object vocabulary |
| Schema URL | objects_schema: https://example.com/schemas/my-schema.yaml | Reuse a shared schema published at an open URL |
Bring your own schema
You are not limited to the built-in vocabularies. You can author a custom
schema for your own object types and properties, or point objects_schema at
a schema published on an open URL to reuse it across projects. See Custom
Schemas for the authoring format.
The built-in c4 schema defines:
- object types such as
actor,organization,product,domain,system,subsystem,container,component,microservice,external - subtypes such as
broker,broker:kafka,broker:rmq,database,storage,library,package,module - shared properties like
team,owner,status,tech - broker objects can own channels; links can name a channel so a message path is visible on Explore
Example C4-style object:
title: Billing Service
type: container:service
description: Handles subscriptions and invoices.
tech:
- typescript
- postgresql
status: activeBuild a model from files (like example-saas)
Create root index
Start with models.yaml and register your model slug:
models:
my-product: nullCreate model root file
Create models/my-product.yaml with root metadata and first-level objects:
title: My Product
type: model
description: Reference architecture for My Product.
objects_schema: c4
objects:
actors: null
product: null
workflows: {}Split objects into focused files
Create object files under models/my-product/objects/... and keep each file responsible for one object.
Add hierarchy with objects
In each object file, declare child slugs:
objects:
api_service: null
worker_service: nullAdd relationships with links
Connect objects explicitly:
links:
- id: api_service
label: callsEnrich incrementally
Add schema-aligned fields (status, tech, owners), tags, and attachments as needed.
Practical conventions
- Keep slugs stable (
snake_caseorkebab_case) because links depend on them. - Prefer one object per file after the first nesting level for cleaner diffs.
- Use relative link ids inside one subtree, absolute ids for cross-domain links.
- Set
objects_schemaearly (built-in, custom, or URL) so object types stay consistent. - Start minimal (
title,type,description,objects) and expand with attributes later.
Last updated on