Skip to content

Field Types

When parse_schema() reads your annotated structs, it maps each field’s Rust type into a FieldType variant and each #[ontology(...)] annotation into a FieldRole. These two enums drive all downstream code generation — how columns are typed in SeaORM, how fields appear in DTOs, what TypeScript type is emitted, etc.

A simplified representation of the Rust type. Ontogen only needs to know the shape of a type for code generation — it doesn’t need to resolve the full type system.

FieldType variantRust typeSeaORM column typeSQL type (SQLite)TypeScript type
StringStringStringTEXT NOT NULLstring
OptionStringOption<String>Option<String>TEXTstring | null
I32i32i32INTEGER NOT NULLnumber
OptionI32Option<i32>Option<i32>INTEGERnumber | null
I64i64i64INTEGER NOT NULLnumber
OptionI64Option<i64>Option<i64>INTEGERnumber | null
F32f32f32REAL NOT NULLnumber
OptionF32Option<f32>Option<f32>REALnumber | null
F64f64f64REAL NOT NULLnumber
OptionF64Option<f64>Option<f64>REALnumber | null
BoolboolboolBOOLEAN NOT NULLboolean
OptionBoolOption<bool>Option<bool>BOOLEANboolean | null
OptionEnum(name)Option<EnumType>Option<String> (Option<i64> for an integer primitive such as Option<u32>)TEXT (INTEGER)string | null (number | null)
VecStringVec<String>String (JSON)TEXT NOT NULLstring[]
VecStruct(name)Vec<StructType>String (JSON)TEXT NOT NULLStructType[]
Other(name)anything elseString (i64 for an integer primitive such as u32)TEXT NOT NULL (INTEGER NOT NULL)string (number)

parse_schema() uses syn to inspect the Type of each field. The detection logic is straightforward pattern matching:

  • String — exact match.
  • Option<String> — Option wrapper with String inner type.
  • Option<SomeType> where the field has #[ontology(enum_field)] — becomes OptionEnum("SomeType").
  • Vec<String> — Vec wrapper with String inner type.
  • Vec<SomeType> where SomeType is not String — becomes VecStruct("SomeType").
  • i32, i64, f32, f64, bool and their Option wrappers — exact matches.
  • Everything else — Other("TypeName"), treated as a string-serialized value.

Other integer primitives (u8, u16, u32, u64, usize, u128, i8, i16, isize, i128) are Other or OptionEnum and keep their Rust type in DTOs, frontmatter and TypeScript (number). On SeaORM they are stored as i64 columns. Widening is lossless for u8, u16, u32, i8 and i16; for the rest, and when reading back, a value that does not fit fails with AppError::DbError naming the field rather than wrapping. The markdown store refuses the same out-of-range writes, so a u64 holds at most i64::MAX on either backend. See the Other fallback.

Vec<String> and Vec<StructType> fields are stored as JSON-serialized strings in the database column. The SeaORM entity uses a String column, and the conversion layer handles serde_json::to_string() / serde_json::from_str().

The exception is Vec<String> fields annotated with a relation (belongs_to, has_many, many_to_many). These aren’t stored in the entity’s table at all — they’re populated at read time from the related table or junction table.

When a field has #[ontology(enum_field)] and an Option<T> type, the FieldType is OptionEnum("T"). The string inside is the enum type name.

In the SeaORM entity, this becomes an Option<String> column. The conversion code uses .map(|v| v.to_string()) going into the database and .map(|s| s.parse().unwrap()) coming out.

Your enum type needs to implement Display + FromStr or handle serde string conversion. Ontogen doesn’t generate the enum — you provide it.

Any type that doesn’t match the known patterns becomes Other("TypeName"). It’s treated as string-serialized in the database. This works for custom types that implement Serialize/Deserialize, but you lose type-specific behavior in the generated code.


Determines how a field participates in code generation. Set by #[ontology(...)] annotations on the field.

FieldRole variantAnnotationDescription
Id#[ontology(id)]Primary key. One per entity.
Body#[ontology(body)]Markdown body content (below frontmatter).
EnumField#[ontology(enum_field)]Enum type stored as string.
Relation(RelationInfo)#[ontology(relation(...))]Cross-entity reference.
Plain(none)Regular data field. Default for unannotated fields.
Skip#[ontology(skip)]Excluded from all code generation.
GeneratorIdBodyEnumFieldRelationPlainSkip
SeaORM entityPrimary keyRegular columnString columnFK column or excludedRegular columnExcluded
SeaORM conversionIncludedIncludedString conversionFK: included; Vec: skipped (populated separately)IncludedExcluded
DTOs (Create)IncludedIncludedIncludedIncludedIncludedExcluded
DTOs (Update)ExcludedIncludedIncludedIncludedIncludedExcluded
Store CRUDLookup keyRegular fieldRegular fieldRelation population / junction syncRegular fieldExcluded
Markdown writerFrontmatterBody sectionFrontmatterWikilink formattingFrontmatterExcluded
Markdown parserFrontmatterBody sectionFrontmatterWikilink parsingFrontmatterExcluded

Attached to fields with FieldRole::Relation. Describes the relationship between entities.

FieldTypeDescription
kindRelationKindThe cardinality of the relation.
targetStringTarget entity name (e.g., "Tag", "Workout"). Must match a struct name in your schema.
junctionOption<String>Override for junction table name. Only used with ManyToMany. Derived automatically if not specified.
foreign_keyOption<String>FK column on the target entity. Only used with HasMany.
VariantSchema field typeJunction tableFK columnDescription
BelongsToString or Option<String>NoOn this entityThis entity holds a FK pointing to the target.
HasManyVec<String>NoOn the target entityThe target entity has a FK pointing back to this entity.
ManyToManyVec<String>YesIn the junction tableBoth sides linked through a junction table.
#[ontology(relation(belongs_to, target = "Workout"))]
pub workout_id: String,

The workout_id column exists on this entity’s table. SeaORM generates a BelongsTo relation variant.

#[ontology(relation(has_many, target = "Node", foreign_key = "parent_id"))]
pub contains: Vec<String>,

No column for contains on this entity’s table. The children are Node records whose parent_id column holds this record’s id; at read time, populate_*_relations() queries nodes where parent_id matches. The target must be the declaring entity (see has_many writes).

#[ontology(relation(many_to_many, target = "Tag"))]
pub tags: Vec<String>,

A junction table (workout_tags) is generated with workout_id and tag_id columns. At read time, populate_*_relations() loads tag IDs from the junction table. On create/update, sync_junction() replaces the junction rows.


The full field definition as parsed from your schema struct.

FieldTypeDescription
nameStringRust field name (e.g., "workout_id", "tags").
field_typeFieldTypeSimplified type representation.
roleFieldRoleRole in the ontology model.
serde_defaultboolWhether the field has #[serde(default)].
multiline_listboolWhether #[ontology(multiline_list)] is present. Affects Markdown rendering.
default_valueOption<String>Value from #[ontology(default_value = "...")]. When the field matches this value, it’s omitted from Markdown frontmatter.

The full entity definition. One per annotated struct.

FieldTypeDefaultDescription
nameString—Rust struct name (e.g., "Workout").
directoryStringsnake_case of nameSubdirectory for Markdown files.
tableStringsnake_case of nameSeaORM table name.
type_nameStringnameFrontmatter type: value.
prefixStringsnake_case of nameID prefix.
fieldsVec<FieldDef>—All fields on the struct.
MethodReturn typeDescription
id_field()Option<&FieldDef>The field with FieldRole::Id, if any.
body_field()Option<&FieldDef>The field with FieldRole::Body, if any.
relation_fields()impl Iterator<Item = &FieldDef>All fields with FieldRole::Relation(_).
junction_relations()impl Iterator<Item = (&FieldDef, &RelationInfo)>All ManyToMany relation fields.
has_many_relations()impl Iterator<Item = (&FieldDef, &RelationInfo)>All HasMany relation fields.
belongs_to_relations()impl Iterator<Item = (&FieldDef, &RelationInfo)>All BelongsTo relation fields.