Lesson metadata
To make our lessons citable, findable, and machine-readable (see
making lessons FAIR), each lesson repository carries
a single master metadata file, metadata.yml. Everything else —
CITATION.cff, bioschemas.yml, and the Zenodo deposit created at
release time — is generated from it automatically. metadata.yml is
the only file maintainers ever hand-edit; the rest should never be
edited by hand, since those edits would just be overwritten.
This page is the reference for what metadata.yml contains, how the
other three are generated from it, and when things get updated —
including a field-by-field guide for setting up metadata on a new
lesson. For the human side of
“who gets listed”, see lesson credits; for the
step-by-step release process, see releasing a new
version.
Overview
File / target |
Location |
Hand-edited? |
Generated by |
Read by |
|---|---|---|---|---|
|
root of each lesson repo |
Yes — the only file to edit |
— |
the two generators below, and the |
|
root of each lesson repo |
No — generated |
|
GitHub’s “Cite this repository” button |
|
root of each lesson repo |
No — generated |
|
Bioschemas/schema.org crawlers and catalogs |
Zenodo deposit |
zenodo.org |
No — built at release time |
|
Zenodo record page, OpenAIRE |
metadata.yml
Example, based on our documentation lesson:
title: "How to document your research software"
abstract: "The lesson 'How to document your research software' gives an overview of the different ways how a code project can be documented: from small projects to larger projects. Markdown and Sphinx are central tools in this lesson."
version: "2026-08-03"
doi: "10.5281/zenodo.8280234"
url: "https://coderefinery.github.io/documentation/"
license: "CC-BY-4.0"
repository-code: "https://github.com/coderefinery/documentation"
keywords:
- "Documentation"
- "Sphinx"
educationalLevel: "Beginner"
inLanguage: "en-UK"
teaches: "Understand the importance of writing code documentation together with the source code, Know what makes a good documentation, Learn what tools can be used for writing documentation, Be able to motivate a balanced decision: sometimes READMEs are absolutely enough"
isPartOf: "https://coderefinery.org"
audience: ""
competencyRequired: ""
accessibilitySummary: ""
learningResourceType: "lesson"
authors:
- name: "CodeRefinery"
- family-names: "Doe"
given-names: "Jane"
- family-names: "Foe"
given-names: "John"
# Optional, currently unset for this lesson. Same shape as `authors`.
# maintainers:
# - family-names: "Doe"
# given-names: "Jane"
All values are deliberately quoted, so they always load as plain
strings rather than YAML dates/numbers — this matters especially for
version, which is a date.
Field reference, with where each one ends up:
title— the base title, without the “ - CodeRefinery lesson” suffix: that’s appended automatically wherever it’s needed (CITATION.cff’stitle, bioschemas’name, Zenodo’stitle).abstract— becomesCITATION.cff’sabstract, bioschemas’description/about, and the start of the Zenodo description (which also gets links torepository-code,url, and coderefinery.org appended automatically).version— quoted date string"YYYY-MM-DD". FeedsCITATION.cff’sversionanddate-released(one field covers both now, since for a lesson they’re always the same date), bioschemas’version, and Zenodo’sversion.doi— the Zenodo concept DOI; left as-is once set. Feeds bioschemas’identifier(ashttps://doi.org/<doi>); not sent to Zenodo directly, since Zenodo assigns/records the version-specific DOI itself.url— the lesson’s rendered site. Feeds bioschemas’@id/urland is linked in the Zenodo description.license— an SPDX identifier, e.g.CC-BY-4.0. Passed through as-is toCITATION.cffand Zenodo; converted to a full license URL for bioschemas via a lookup table (SPDX_LICENSE_URLS) in the generator script.repository-code— the source repo URL. Included inCITATION.cffand linked in the Zenodo description.keywords— a list. Not used inCITATION.cffat all; comma-joined into bioschemas’keywordsstring; sent as-is (a list) to Zenodo’skeywords.educationalLevel,inLanguage,teaches,isPartOf,audience,competencyRequired,accessibilitySummary,learningResourceType— bioschemas-only, noCITATION.cffor Zenodo equivalent.inLanguageuses a BCP47 tag (e.g.en-UK), deliberately not reused for Zenodo, which expects a different language code format.audienceis schema.org’s structuredAudiencetype, but is currently stored and emitted as a plain string — a known simplification.authors— one entry per contributor: organizations vianame:, people viafamily-names/given-names(and optionallyorcid, given as the fullhttps://orcid.org/...URL). FeedsCITATION.cff’sauthorsdirectly, bioschemas’authorlist (mapped toPerson/Organization, as a single name string, withoutorcid), and Zenodo’screators(name joined as"Family, Given",orcidincluded, stripped to its bare form, if present). See lesson credits for how someone gets added. We don’t distinguish smaller “contributor”-level credit — anyone listed here is an author.maintainers— optional, same list shape asauthors. Meant to record lesson maintainer(s) — work in progress, since we don’t currently have lesson maintainers (that page is only an initial suggestion for the role, not an adopted practice). FeedsCITATION.cff’scontactand a Zenodocontributorsentry (per maintainer, with typeContactPerson) — both are live already, conditional on this field being set. The bioschemas mapping (amaintainerproperty) exists in the generator too but is currently commented out, so it won’t appear inbioschemas.ymlyet even oncemaintainersis filled in. No lesson has this field populated yet — see lesson maintainer metadata below.
cff-version, message, and type (the CFF schema/citation-message
boilerplate) aren’t in metadata.yml at all — they’re hardcoded
constants in the generator script, since they never change between
lessons.
Generating CITATION.cff and bioschemas.yml
generate_metadata_files.py
reads metadata.yml and (re)writes both CITATION.cff and
bioschemas.yml from it. The
generate-metadata.yml
GitHub Actions workflow runs this automatically whenever metadata.yml
changes on main, and commits the regenerated files back with a bot
commit.
In practice: edit metadata.yml, open a pull request, merge it — the
two generated files update themselves. There’s nothing to run locally
and nothing else to edit.
Publishing to Zenodo
Every GitHub release triggers a new version in our CodeRefinery
Zenodo community, under
the same concept DOI as previous releases (the concept DOI is the one
kept as doi in metadata.yml).
Publishing is driven by
publish_to_zenodo.py,
run by each lesson’s zenodo.yml GitHub Actions workflow whenever a
GitHub release is published. It reads metadata.yml directly. Like the two scripts above, it isn’t part of the
shared lesson template yet — it’s currently copied into each lesson
repository individually, so a fix or improvement has to be applied per
repo. It:
Reads
metadata.ymland builds a Zenodo metadata payload from it (see field mapping below).Finds the latest existing Zenodo version under
ZENODO_CONCEPT_IDand creates a new draft version under the same concept.Uploads the tagged GitHub release’s source archive and the lesson PDF built onto the
gh-pagesbranch.Publishes the draft, producing a new version DOI under the same concept DOI — see the verification step in releasing a new version.
Field mapping from metadata.yml to Zenodo:
Zenodo field |
From |
Notes |
|---|---|---|
|
|
the “ - CodeRefinery lesson” suffix is appended by the script |
|
— |
hardcoded to |
|
|
plus auto-appended links to |
|
|
organizational entries pass through as-is; person entries become a single |
|
|
sent as the list, as-is |
|
|
sent as-is |
|
|
sent as the raw SPDX identifier (e.g. |
|
|
one entry per maintainer, each with |
— |
|
not part of the payload — Zenodo assigns/records the version DOI itself |
All authors — including the CodeRefinery organizational entry —
stay in creators, never in contributors: Zenodo only includes
creators in the actual citation text, so moving individual people to
contributors would drop them from the permanent citation.
contributors is reserved for people who aren’t already counted as an
author, which is exactly the maintainers field’s role above.
Displaying metadata in the lesson itself
A Sphinx directive, {lesson-metadata}, is available (from
content/lesson_metadata.py,
registered as a Sphinx extension in conf.py). Dropped onto any
content page, it renders the repository’s metadata.yml as an HTML
table — useful for an about/credits page, for example. It’s available
tooling, not required on any particular page.
Lesson maintainer metadata
Work in progress
We don’t currently have lesson maintainers — lesson maintainer is only an initial suggestion for the role, not an adopted practice. Everything below describes plumbing that exists in case/once it is adopted.
Recording a lesson maintainer in metadata.yml’s maintainers field
is possible but not yet in active use — no lesson has it filled in
today. The plumbing that exists so far:
CITATION.cff’scontactand Zenodo’scontributors(type: ContactPerson) are already wired up in the generator/publish scripts — filling inmaintainersinmetadata.ymland pushing would immediately start populating both.bioschemas.yml’smaintainerproperty is not wired up yet: the mapping exists as commented-out code ingenerate_metadata_files.py, so it needs that line uncommented before it would take effect, even withmaintainersfilled in.
Setting up metadata for a new lesson
This metadata.yml setup isn’t part of the Sphinx lesson
template yet
— that centralization is planned but not done. Until then, new lessons
should copy metadata.yml and the two generator scripts/workflows
above from an existing lesson that already uses this pattern (e.g. our
documentation lesson), then fill in metadata.yml field by field:
Fill in now:
title— the lesson’s title, without the “ - CodeRefinery lesson” suffix (added automatically)abstract— a short (1-3 sentence) description of what the lesson coversauthors— start with theCodeRefineryorganizational entry, plus whoever is creating the lesson; more are added as people contributemaintainers— optional; the initial lesson maintainer(s), if one has already been assigned (see the caveats in lesson maintainer metadata about what this currently does and doesn’t reach)url— the lesson’s future GitHub Pages URL, i.e.https://coderefinery.github.io/<repo-name>/repository-code— the new GitHub repository’s URLlicense—CC-BY-4.0for all our lessonskeywords,educationalLevel,inLanguage,teaches,isPartOf,audience,competencyRequired,accessibilitySummary,learningResourceType— the Bioschemas-specific descriptive fieldsversion— today’s date, formatYYYY-MM-DD
Leave blank until the first release:
doi— there’s no concept DOI yet. Follow the prerequisites in releasing a new version to set up the Zenodo entry, then fill this in with the resulting concept DOI. Once set, it never changes again.
Once metadata.yml is filled in and merged to main, CITATION.cff
and bioschemas.yml generate themselves — there’s nothing to set up
in those two files directly.
When things get updated, at a glance
As people contribute: the
authorslist inmetadata.yml.When the maintainer changes: the
maintainersfield inmetadata.yml, once it’s in active use (see lesson maintainer metadata).When the lesson’s scope changes materially:
title,abstract, and the Bioschemas-specific descriptive fields.At release time:
versioninmetadata.yml(this alone also coversdate-released); pushing it tomainregeneratesCITATION.cff/bioschemas.ymlautomatically; tagging a release triggers the Zenodo publish.Rarely/manually:
doi(never, once set),license,url,repository-code.