diff --git a/Makefile b/Makefile
index 23f0d235..621e77e5 100644
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,7 @@ SRC = src
DEST = project
PYMODEL = $(SRC)/$(SCHEMA_NAME)/datamodel
DOCDIR = docs
+TEMPLATE_DIR = $(SRC)/doc-templates
# basename of a YAML file in model/
.PHONY: all clean
@@ -84,7 +85,7 @@ $(DOCDIR):
gendoc: $(DOCDIR)
cp $(SRC)/docs/*md $(DOCDIR) ; \
- $(RUN) gen-doc -d $(DOCDIR) $(SOURCE_SCHEMA_PATH)
+ $(RUN) gen-doc -d $(DOCDIR) $(SOURCE_SCHEMA_PATH) --template-directory $(TEMPLATE_DIR)
testdoc: gendoc serve
diff --git a/src/doc-templates/class.md.jinja2 b/src/doc-templates/class.md.jinja2
new file mode 100644
index 00000000..390f5923
--- /dev/null
+++ b/src/doc-templates/class.md.jinja2
@@ -0,0 +1,123 @@
+# Class: {{ gen.name(element) }}
+
+{%- if header -%}
+{{header}}
+{%- endif -%}
+
+
+{% if element.description %}
+{% set element_description_lines = element.description.split('\n') %}
+{% for element_description_line in element_description_lines %}
+_{{ element_description_line }}_
+{% endfor %}
+{% endif %}
+
+{% if element.abstract %}
+* __NOTE__: this is an abstract class and should not be instantiated directly
+{% endif %}
+
+URI: {{ gen.uri_link(element) }}
+
+
+{% if diagram_type == "er_diagram" %}
+```{{ gen.mermaid_directive() }}
+{{ gen.mermaid_diagram([element.name]) }}
+```
+{% else %}
+{% include "class_diagram.md.jinja2" %}
+{% endif %}
+
+{% if schemaview.class_parents(element.name) or schemaview.class_children(element.name, mixins=False) %}
+
+## Inheritance
+{{ gen.inheritance_tree(element, mixins=True) }}
+{% else %}
+
+{% endif %}
+
+## Slots
+
+| Name | Cardinality and Range | Description | Inheritance |
+| --- | --- | --- | --- |
+{% if gen.get_direct_slots(element)|length > 0 %}
+{%- for slot in gen.get_direct_slots(element) -%}
+| {{ gen.link(slot) }} | {{ gen.cardinality(slot) }}
{{ gen.link(slot.range) }} | {{ slot.description|enshorten }} | direct |
+{% endfor -%}
+{% endif -%}
+{% if gen.get_indirect_slots(element)|length > 0 %}
+{%- for slot in gen.get_indirect_slots(element) -%}
+| {{ gen.link(slot) }} | {{ gen.cardinality(slot) }}
{{ gen.link(slot.range) }} | {{ slot.description|enshorten }} | {{ gen.links(gen.get_slot_inherited_from(element.name, slot.name))|join(', ') }} |
+{% endfor -%}
+{% endif %}
+
+{% if schemaview.is_mixin(element.name) %}
+## Mixin Usage
+
+| mixed into | description |
+| --- | --- |
+{% for c in schemaview.class_children(element.name, is_a=False) -%}
+| {{ gen.link(c) }} | {{ schemaview.get_class(c).description|enshorten }} |
+{% endfor %}
+{% endif %}
+
+{% if schemaview.usage_index().get(element.name) %}
+## Usages
+
+| used by | used in | type | used |
+| --- | --- | --- | --- |
+{% for usage in schemaview.usage_index().get(element.name) -%}
+| {{gen.link(usage.used_by)}} | {{gen.link(usage.slot)}} | {{usage.metaslot}} | {{ gen.link(usage.used) }} |
+{% endfor %}
+{% endif %}
+
+{% include "common_metadata.md.jinja2" %}
+
+
+{% if schemaview.get_mappings(element.name).items() -%}
+## Mappings
+
+| Mapping Type | Mapped Value |
+| --- | --- |
+{% for m, mt in schemaview.get_mappings(element.name).items() -%}
+{% if mt|length > 0 -%}
+| {{ m }} | {{ mt|join(', ') }} |
+{% endif -%}
+{% endfor %}
+
+{% endif -%}
+
+{% if gen.example_object_blobs(element.name) -%}
+## Examples
+{% for name, blob in gen.example_object_blobs(element.name) -%}
+### Example: {{name}}
+
+```yaml
+{{ blob }}
+```
+{% endfor %}
+{% endif %}
+
+
+## LinkML Source
+
+
+
+### Direct
+
+
+```yaml
+{{gen.yaml(element)}}
+```
+
+
+### Induced
+
+
+```yaml
+{{gen.yaml(element, inferred=True)}}
+```
+
+
+{%- if footer -%}
+{{footer}}
+{%- endif -%}
\ No newline at end of file
diff --git a/src/doc-templates/class_diagram.md.jinja2 b/src/doc-templates/class_diagram.md.jinja2
new file mode 100644
index 00000000..a636caa3
--- /dev/null
+++ b/src/doc-templates/class_diagram.md.jinja2
@@ -0,0 +1,59 @@
+{% if schemaview.class_parents(element.name) and schemaview.class_children(element.name) %}
+```{{ gen.mermaid_directive() }}
+ classDiagram
+ class {{ gen.name(element) }}
+ {% for s in schemaview.class_parents(element.name)|sort(attribute='name') -%}
+ {{ gen.name(schemaview.get_element(s)) }} <|-- {{ gen.name(element) }}
+ {% endfor %}
+
+ {% for s in schemaview.class_children(element.name)|sort(attribute='name') -%}
+ {{ gen.name(element) }} <|-- {{ gen.name(schemaview.get_element(s)) }}
+ {% endfor %}
+
+ {% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%}
+ {{ gen.name(element) }} : {{gen.name(s)}}
+ {% if s.range not in gen.all_type_object_names() %}
+ {{ gen.name(element) }} --|> {{ s.range }} : {{ gen.name(s) }}
+ {% endif %}
+ {% endfor %}
+```
+{% elif schemaview.class_parents(element.name) %}
+```{{ gen.mermaid_directive() }}
+ classDiagram
+ class {{ gen.name(element) }}
+ {% for s in schemaview.class_parents(element.name)|sort(attribute='name') -%}
+ {{ gen.name(schemaview.get_element(s)) }} <|-- {{ gen.name(element) }}
+ {% endfor %}
+ {% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%}
+ {{ gen.name(element) }} : {{gen.name(s)}}
+ {% if s.range not in gen.all_type_object_names() %}
+ {{ gen.name(element) }} --|> {{ s.range }} : {{ gen.name(s) }}
+ {% endif %}
+ {% endfor %}
+```
+{% elif schemaview.class_children(element.name) %}
+```{{ gen.mermaid_directive() }}
+ classDiagram
+ class {{ gen.name(element) }}
+ {% for s in schemaview.class_children(element.name)|sort(attribute='name') -%}
+ {{ gen.name(element) }} <|-- {{ gen.name(schemaview.get_element(s)) }}
+ {% endfor %}
+ {% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%}
+ {{ gen.name(element) }} : {{gen.name(s)}}
+ {% if s.range not in gen.all_type_object_names() %}
+ {{ gen.name(element) }} --|> {{ s.range }} : {{ gen.name(s) }}
+ {% endif %}
+ {% endfor %}
+```
+{% else %}
+```{{ gen.mermaid_directive() }}
+ classDiagram
+ class {{ gen.name(element) }}
+ {% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%}
+ {{ gen.name(element) }} : {{gen.name(s)}}
+ {% if s.range not in gen.all_type_object_names() %}
+ {{ gen.name(element) }} --|> {{ s.range }} : {{ gen.name(s) }}
+ {% endif %}
+ {% endfor %}
+```
+{% endif %}
\ No newline at end of file
diff --git a/src/doc-templates/common_metadata.md.jinja2 b/src/doc-templates/common_metadata.md.jinja2
new file mode 100644
index 00000000..de147a50
--- /dev/null
+++ b/src/doc-templates/common_metadata.md.jinja2
@@ -0,0 +1,78 @@
+{% if element.aliases %}
+## Aliases
+
+{% for alias in element.aliases %}
+* {{ alias }}
+{%- endfor %}
+{% endif %}
+
+
+{% if element.examples %}
+## Examples
+
+| Value |
+| --- |
+{% for x in element.examples -%}
+| {{ x.value }} |
+{% endfor %}
+{% endif -%}
+
+{% if element.comments -%}
+## Comments
+
+{% for x in element.comments -%}
+* {{x}}
+{% endfor %}
+{% endif -%}
+
+{% if element.todos -%}
+## TODOs
+
+{% for x in element.todos -%}
+* {{x}}
+{% endfor %}
+{% endif -%}
+
+{% if element.see_also -%}
+## See Also
+
+{% for x in element.see_also -%}
+* {{ gen.uri_link(x) }}
+{% endfor %}
+{% endif -%}
+
+## Identifier and Mapping Information
+
+{% if element.id_prefixes %}
+### Valid ID Prefixes
+
+Instances of this class *should* have identifiers with one of the following prefixes:
+{% for p in element.id_prefixes %}
+* {{p}}
+{% endfor %}
+
+{% endif %}
+
+
+{% if element.annotations %}
+### Annotations
+
+| property | value |
+| --- | --- |
+{% for a in element.annotations -%}
+{%- if a|string|first != '_' -%}
+| {{ a }} | {{ element.annotations[a].value }} |
+{%- endif -%}
+{% endfor %}
+{% endif %}
+
+{% if element.from_schema or element.imported_from %}
+### Schema Source
+
+{% if element.from_schema %}
+* from schema: {{ element.from_schema }}
+{% endif %}
+{% if element.imported_from %}
+* imported from: {{ element.imported_from }}
+{% endif %}
+{% endif %}
\ No newline at end of file
diff --git a/src/doc-templates/index.md.jinja2 b/src/doc-templates/index.md.jinja2
new file mode 100644
index 00000000..2a78e4c4
--- /dev/null
+++ b/src/doc-templates/index.md.jinja2
@@ -0,0 +1,62 @@
+# {% if schema.title %}{{ schema.title }}{% else %}{{ schema.name }}{% endif %}
+
+{% if schema.description %}{{ schema.description }}{% endif %}
+
+URI: {{ schema.id }}
+
+Name: {{ schema.name }}
+
+{% if include_top_level_diagram %}
+
+## Schema Diagram
+
+```{{ gen.mermaid_directive() }}
+{{ gen.mermaid_diagram() }}
+```
+{% endif %}
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+{% if gen.hierarchical_class_view -%}
+{% for u, v in gen.class_hierarchy_as_tuples() -%}
+| {{ " "|safe*u*8 }}{{ gen.link(schemaview.get_class(v)) }} | {{ schemaview.get_class(v).description }} |
+{% endfor %}
+{% else -%}
+{% for c in gen.all_class_objects()|sort(attribute=sort_by) -%}
+| {{gen.link(c)}} | {{c.description|enshorten}} |
+{% endfor %}
+{% endif %}
+
+## Slots
+
+| Slot | Description |
+| --- | --- |
+{% for s in gen.all_slot_objects()|sort(attribute=sort_by) -%}
+| {{gen.link(s)}} | {{s.description|enshorten}} |
+{% endfor %}
+
+## Enumerations
+
+| Enumeration | Description |
+| --- | --- |
+{% for e in gen.all_enum_objects()|sort(attribute=sort_by) -%}
+| {{gen.link(e)}} | {{e.description|enshorten}} |
+{% endfor %}
+
+## Types
+
+| Type | Description |
+| --- | --- |
+{% for t in gen.all_type_objects()|sort(attribute=sort_by) -%}
+| {{gen.link(t)}} | {{t.description|enshorten}} |
+{% endfor %}
+
+## Subsets
+
+| Subset | Description |
+| --- | --- |
+{% for ss in schemaview.all_subsets().values()|sort(attribute='name') -%}
+| {{gen.link(ss)}} | {{ss.description|enshorten}} |
+{% endfor %}
diff --git a/src/doc-templates/slot.md.jinja2 b/src/doc-templates/slot.md.jinja2
new file mode 100644
index 00000000..8bd9890d
--- /dev/null
+++ b/src/doc-templates/slot.md.jinja2
@@ -0,0 +1,96 @@
+# Slot: {{ gen.name(element) }}
+
+{%- if header -%}
+{{header}}
+{%- endif -%}
+
+{% if element.description %}
+{% set element_description_lines = element.description.split('\n') %}
+{% for element_description_line in element_description_lines %}
+_{{ element_description_line }}_
+{% endfor %}
+{% endif %}
+
+URI: {{ gen.uri_link(element) }}
+
+
+{% if schemaview.slot_parents(element.name) or schemaview.slot_children(element.name, mixins=False) %}
+
+## Inheritance
+
+{{ gen.inheritance_tree(element, mixins=True) }}
+{% else %}
+
+{% endif %}
+
+{% if schemaview.get_classes_by_slot(element, include_induced=True) %}
+
+## Applicable Classes
+
+| Name | Description | Modifies Slot |
+| --- | --- | --- |
+{% for c in schemaview.get_classes_by_slot(element, include_induced=True) -%}
+{{ gen.link(c) }} | {{ schemaview.get_class(c).description|enshorten }} | {% if c in schemaview.get_classes_modifying_slot(element) %} yes {% else %} no {% endif %} |
+{% endfor %}
+
+{% endif %}
+
+
+{% if schemaview.is_mixin(element.name) %}
+## Mixin Usage
+
+| mixed into | description | range | domain |
+| --- | --- | --- | --- |
+{% for s in schemaview.slot_children(element.name, is_a=False) -%}
+| {{ gen.link(s) }} | {{ schemaview.get_slot(s).description|enshorten }} | {{ schemaview.get_slot(s).range }} | {{ schemaview.get_classes_by_slot(schemaview.get_slot(s))|join(', ') }} |
+{% endfor %}
+{% endif %}
+
+## Properties
+
+* Range: {{gen.link(element.range)}}
+{% if element.multivalued %}
+* Multivalued: {{ element.multivalued }}
+{% endif -%}
+{% if element.required %}
+* Required: {{ element.required }}
+{% elif element.recommended %}
+* Recommended: {{ element.recommended }}
+{% endif -%}
+{% if element.minimum_value is not none %}
+* Minimum Value: {{ element.minimum_value|int }}
+{% endif -%}
+{% if element.maximum_value is not none %}
+* Maximum Value: {{ element.maximum_value|int }}
+{% endif -%}
+{% if element.pattern %}
+* Regex pattern: {{ '`' }}{{ element.pattern }}{{ '`' }}
+{% endif -%}
+{% if schemaview.is_mixin(element.name) %}
+* Mixin: {{ element.mixin }}
+{% endif -%}
+
+
+{% if schemaview.usage_index().get(element.name) %}
+## Usages
+
+| used by | used in | type | used |
+| --- | --- | --- | --- |
+{% for usage in schemaview.usage_index().get(element.name) -%}
+| {{gen.link(usage.used_by)}} | {{gen.link(usage.slot)}} | {{usage.metaslot}} | {{ gen.link(usage.used) }} |
+{% endfor %}
+{% endif %}
+
+{% include "common_metadata.md.jinja2" %}
+
+## LinkML Source
+
+
+```yaml
+{{ gen.yaml(element) }}
+```
+
+
+{%- if footer -%}
+{{footer}}
+{%- endif -%}
\ No newline at end of file