Seed & demo data¶
A module often needs to ship records, not just schema: reference data your app can't work without (categories, tags, default settings) and demo data that makes a fresh install feel alive. In Victor you declare these records as seeds — <seed> elements in a data file — and Victor creates them when the module installs.
Seed data is the counterpart to your models and views: models define the shape of a record, views render it, and seeds populate it. A seed file is plain XML, versioned in your module's git repo, and reviewed like the rest of your code.
A seed data file¶
Here is a real data file from the built-in contact app. It seeds a few tags and two contacts:
<victor>
<seed model="tag" id="tag_vip"><display_name>VIP</display_name></seed>
<seed model="tag" id="tag_lead"><display_name>Lead</display_name></seed>
<seed model="tag" id="tag_partner"><display_name>Partner</display_name></seed>
<seed model="contact" id="rec_acme">
<display_name>Acme Corp</display_name>
<email>hello@acme.test</email>
<is_company>true</is_company>
</seed>
<seed model="contact" id="rec_jane">
<display_name>Jane Doe</display_name>
<email>jane@acme.test</email>
</seed>
</victor>
Every data file has a single <victor> root. Inside it, each <seed> is one record.
The <seed> element¶
A <seed> needs two attributes:
| Attribute | Meaning |
|---|---|
model |
The model name to create a record in (e.g. contact, tag, your own ticket). |
id |
A symbolic xmlid that names this record so you and other seeds can refer to it. |
The child elements are field names on that model, and their text is the field's value:
<seed model="contact" id="rec_acme">
<display_name>Acme Corp</display_name>
<email>hello@acme.test</email>
<is_company>true</is_company>
</seed>
This creates one contact with display_name = "Acme Corp", email = "hello@acme.test", and is_company = true. Only list the fields you want to set; anything you omit uses the model's default.
Use real field names
Each child tag must match a field defined on the model. A typo or a field that doesn't exist will fail the install. Check your model's fields on the Models & fields page.
The id is an xmlid, not a numeric key¶
The id="rec_acme" is a symbolic name, not the record's stored primary key. You choose it; Victor maps it to whatever underlying key the record ends up with. This matters for two reasons:
- You reference records by name, not by number. Other seeds point at
rec_acme— a stable, human-readable handle — instead of a number you can't predict. - The xmlid only needs to be unique within your module. You point at a seed by the bare name you gave it (
rec_acme); two different modules can each userec_acmewithout colliding.
Pick descriptive, prefixed xmlids (tag_vip, rec_acme) so they read clearly and don't collide.
Field values are coerced by type¶
The text you put in a child element is converted to match the field's type. Write values in these forms:
| Field type | Write it as | Example |
|---|---|---|
String |
Plain text | <subject>Cannot log in</subject> |
Text |
Plain text (escape markup: < &) |
<description>Line one<br>Line two</description> |
Boolean |
true, 1, or yes for true; anything else is false |
<done>false</done> |
Integer |
A whole number | <sequence>10</sequence> |
Float |
A decimal number | <hours>2.5</hours> |
Date |
ISO 8601 date (YYYY-MM-DD) |
<due_date>2026-07-10</due_date> |
Datetime |
ISO 8601 | <due_at>2026-07-10T09:00:00</due_at> |
Selection |
The option's stored value, as plain text | <priority>high</priority> |
Reference |
The xmlid of another seed (see below) | <customer_id>rec_acme</customer_id> |
Escaping
Data files are XML, so <, >, and & inside a value must be written as <, >, and &. This comes up most in Text fields that hold snippets of markup.
Referencing another record¶
A Reference field links one record to another. In a seed, set it to the xmlid of another seed declared earlier in the same file — Victor resolves that name to the record that seed created as it loads the file.
Consider a small helpdesk app whose ticket model has a customer_id reference to a contact. This data file seeds the customer first, then a ticket that points at it:
<victor>
<seed model="contact" id="rec_acme">
<display_name>Acme Corp</display_name>
<email>hello@acme.test</email>
<is_company>true</is_company>
</seed>
<seed model="ticket" id="ticket_login">
<subject>Cannot log in</subject>
<description>User reports a 500 on sign-in.</description>
<priority>high</priority>
<customer_id>rec_acme</customer_id>
<done>false</done>
</seed>
</victor>
Two rules for references:
- Declare the target first. The referenced seed must appear above the seed that points at it in the same file, so its xmlid is known by the time the reference is read.
- References resolve within a single file only. A seed can point at another seed only when that target is declared earlier in the same data file. An xmlid from a different file — or from another module — is not looked up, so keep a seed and any seed it references together in one file.
A Reference value must resolve to an xmlid declared earlier in the same file. If it doesn't match one, the reference has nothing to point at and the install fails — you can't target a record by a raw stored id or by an xmlid from another file.
Registering data files in module.yaml¶
A data file only runs if you list it under the data: key of your module.yaml. Files load in the order listed. Note that a seed's reference to another seed only resolves within the same file (see Referencing another record) — file order does not let a seed point at a seed in a different file — so keep a seed and any seed it references together in one file. Here is the contact app's manifest:
name: contact
label: Contacts
author: Victor Core
version: 0.1.0
requires: [base]
models: [models]
data:
- views/contact.xml
- data/contact.xml
The same data: list holds both your view files and your seed files. A common convention is to keep views under views/ and record seeds under data/, but the framework only cares about the paths you list, not the folder names.
Install and upgrade behavior¶
- On install, every
<seed>in your listed data files is created. A fresh instance gets your reference and demo data automatically. - On upgrade, seeds your module already created are left untouched, so edits users made to those records in the app survive a new version. Seeds you've added since the last version are still created.
- Removing a
<seed>deletes its record on upgrade. If you drop a seed from a data file, the record it created is deleted the next time the module upgrades — it is not left behind. Renaming a seed'sidcounts as removing the old seed and adding a new one, so the old record is deleted and a fresh one is created.
Deleting a seed is destructive
Because removing a <seed> deletes the record it created — along with any changes users made to it — don't drop a seed from a data file unless you intend that record to disappear on the next upgrade. Keep a seed's id stable across versions so the record it owns is preserved.
Design seeds as a starting point
Because existing seeds are preserved across upgrades, treat them as initial values users are free to change — not as configuration you'll silently overwrite later. To ship a genuinely new record, add a new <seed> with a new xmlid; to let users tune an existing one, seed a sensible default and let them edit it in the app.
Related¶
- Models & fields — define the models and field types your seeds populate.
- Views — the other kind of record that ships in a data file.