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

metadata.yml

root of each lesson repo

Yes — the only file to edit

the two generators below, and the lesson-metadata directive

CITATION.cff

root of each lesson repo

No — generated

generate_metadata_files.py, on push to main

GitHub’s “Cite this repository” button

bioschemas.yml

root of each lesson repo

No — generated

generate_metadata_files.py, on push to main

Bioschemas/schema.org crawlers and catalogs

Zenodo deposit

zenodo.org

No — built at release time

publish_to_zenodo.py, on GitHub release publish

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’s title, bioschemas’ name, Zenodo’s title).

  • abstract — becomes CITATION.cff’s abstract, bioschemas’ description/about, and the start of the Zenodo description (which also gets links to repository-code, url, and coderefinery.org appended automatically).

  • version — quoted date string "YYYY-MM-DD". Feeds CITATION.cff’s version and date-released (one field covers both now, since for a lesson they’re always the same date), bioschemas’ version, and Zenodo’s version.

  • doi — the Zenodo concept DOI; left as-is once set. Feeds bioschemas’ identifier (as https://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/url and is linked in the Zenodo description.

  • license — an SPDX identifier, e.g. CC-BY-4.0. Passed through as-is to CITATION.cff and 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 in CITATION.cff and linked in the Zenodo description.

  • keywords — a list. Not used in CITATION.cff at all; comma-joined into bioschemas’ keywords string; sent as-is (a list) to Zenodo’s keywords.

  • educationalLevel, inLanguage, teaches, isPartOf, audience, competencyRequired, accessibilitySummary, learningResourceType — bioschemas-only, no CITATION.cff or Zenodo equivalent. inLanguage uses a BCP47 tag (e.g. en-UK), deliberately not reused for Zenodo, which expects a different language code format. audience is schema.org’s structured Audience type, but is currently stored and emitted as a plain string — a known simplification.

  • authors — one entry per contributor: organizations via name:, people via family-names/given-names (and optionally orcid, given as the full https://orcid.org/... URL). Feeds CITATION.cff’s authors directly, bioschemas’ author list (mapped to Person/Organization, as a single name string, without orcid), and Zenodo’s creators (name joined as "Family, Given", orcid included, 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 as authors. 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). Feeds CITATION.cff’s contact and a Zenodo contributors entry (per maintainer, with type ContactPerson) — both are live already, conditional on this field being set. The bioschemas mapping (a maintainer property) exists in the generator too but is currently commented out, so it won’t appear in bioschemas.yml yet even once maintainers is 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:

  1. Reads metadata.yml and builds a Zenodo metadata payload from it (see field mapping below).

  2. Finds the latest existing Zenodo version under ZENODO_CONCEPT_ID and creates a new draft version under the same concept.

  3. Uploads the tagged GitHub release’s source archive and the lesson PDF built onto the gh-pages branch.

  4. 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 metadata.yml

Notes

title

title

the “ - CodeRefinery lesson” suffix is appended by the script

upload_type

hardcoded to "lesson" in the script

description

abstract

plus auto-appended links to repository-code, url, and a fixed link to coderefinery.org

creators

authors

organizational entries pass through as-is; person entries become a single "Family, Given" string; orcid, if present, is included with its https://orcid.org/ prefix stripped

keywords

keywords

sent as the list, as-is

version

version

sent as-is

license

license

sent as the raw SPDX identifier (e.g. CC-BY-4.0), unlike bioschemas.yml which converts it to a full URL

contributors

maintainers

one entry per maintainer, each with type: ContactPerson; omitted entirely if maintainers isn’t set

doi

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’s contact and Zenodo’s contributors (type: ContactPerson) are already wired up in the generator/publish scripts — filling in maintainers in metadata.yml and pushing would immediately start populating both.

  • bioschemas.yml’s maintainer property is not wired up yet: the mapping exists as commented-out code in generate_metadata_files.py, so it needs that line uncommented before it would take effect, even with maintainers filled 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 covers

  • authors — start with the CodeRefinery organizational entry, plus whoever is creating the lesson; more are added as people contribute

  • maintainers — 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 URL

  • licenseCC-BY-4.0 for all our lessons

  • keywords, educationalLevel, inLanguage, teaches, isPartOf, audience, competencyRequired, accessibilitySummary, learningResourceType — the Bioschemas-specific descriptive fields

  • version — today’s date, format YYYY-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 authors list in metadata.yml.

  • When the maintainer changes: the maintainers field in metadata.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: version in metadata.yml (this alone also covers date-released); pushing it to main regenerates CITATION.cff/bioschemas.yml automatically; tagging a release triggers the Zenodo publish.

  • Rarely/manually: doi (never, once set), license, url, repository-code.