diff --git a/docs/docs/extend/extensions/extension-development.md b/docs/docs/extend/extensions/extension-development.md
index 27adbd8..8d8356a 100644
--- a/docs/docs/extend/extensions/extension-development.md
+++ b/docs/docs/extend/extensions/extension-development.md
@@ -171,7 +171,7 @@ MyAwesomeExtension.info = {
}
```
-The `version` field describes the version of the extension used and then durin the experiment will be part of the generated data. This is used generate metadata and help maintain the Psych-DS standard. It should imported from the package.json file by including an import statement in the top of the index.ts file. This allows the `version` field be automatically updated with each changeset. If you are not using a build environment and instead writing a plain JS file, you can manually enter the `version` as a string as done in the comment.
+The `version` field describes the version of the extension used, and during the experiment it becomes part of the generated data. This is used to generate metadata and help maintain the [Psych-DS standard](../../learn/guides/metadata-what-is-psych-ds.md). It should be imported from the package.json file by including an import statement at the top of the index.ts file. This allows the `version` field be automatically updated with each changeset. If you are not using a build environment and instead writing a plain JS file, you can manually enter the `version` as a string as done in the comment.
The `data` field is an object containing all of the `data` generated for the plugin. Each 'data' object has a `type` and `default` property. Additionally, this should be only used for data you choose to generate. Any jsdoc (comments included in the /** */ tags) you include will be scraped as metadata if you are choosing to generate metadata. This scraped metadata will also be used to create the JsPsych documentation.
diff --git a/docs/docs/extend/plugins/plugin-components.md b/docs/docs/extend/plugins/plugin-components.md
index 7c3f196..e84a2bf 100644
--- a/docs/docs/extend/plugins/plugin-components.md
+++ b/docs/docs/extend/plugins/plugin-components.md
@@ -116,7 +116,7 @@ If the `default` value is `undefined` then a user must specify a value for this
jsPsych allows most [plugin parameters to be dynamic](../../learn/concepts/dynamic-parameters.md), which means that the parameter value can be a function that will be evaluated right before the trial starts. However, if you want your plugin to have a parameter that is a function that _shouldn't_ be evaluated before the trial starts, then you should make sure that the parameter type is `'FUNCTION'`. This tells jsPsych not to evaluate the function as it normally does for dynamic parameters. See the `canvas-*` plugins for examples.
-We strongly encourage using [JSDoc comments](https://jsdoc.app/about-getting-started) to document the parameters and data generated by the plugin, as shown below. We use these comments to automatically generate documentation for the plugins and to generate default descriptions of variables for experiment metadata.
+We strongly encourage using [JSDoc comments](https://jsdoc.app/about-getting-started) to document the parameters and data generated by the plugin, as shown below. We use these comments to automatically generate documentation for the plugins and to generate default descriptions of variables for [experiment metadata](../../learn/guides/metadata-what-is-psych-ds.md).
```js
const info = {
diff --git a/docs/docs/learn/deploy/running-experiments.md b/docs/docs/learn/deploy/running-experiments.md
index ae0e79c..e36f201 100644
--- a/docs/docs/learn/deploy/running-experiments.md
+++ b/docs/docs/learn/deploy/running-experiments.md
@@ -90,6 +90,7 @@ To be maximally flexible, jsPsych doesn't provide a single built-in solution for
Some options for running your jsPsych experiment online include:
* [Cognition.run](https://www.cognition.run/) - A free service designed specifically for hosting jsPsych experiments, with an easy-to-use interface.
+* [DataPipe](../guides/datapipe.md) - A free, jsPsych-maintained service that saves data to the [Open Science Framework](https://osf.io/) without you running a server or database. It handles the data-saving side rather than hosting the experiment files, so it pairs with a static host such as GitHub Pages.
* [JATOS](https://www.jatos.org/Whats-JATOS.html) - A free program that runs on your own server and provides a GUI for setting up experiments and accessing the data. Offers lots of features for creating more complex experiments and managing multiple researchers.
* [Pavlovia](https://pavlovia.org/) - A paid hosting service for web-based experiments, run by the PsychoPy team. Experiment files are managed on a GitLab repository. Participants will access the experiment through a link to Pavlovia.
* [PsiTurk](https://psiturk.org/) - Python-based program to help you host your experiment on your own computer and collect data from MTurk (see Recruiting Participants below). Relatively easy for a DIY option.
diff --git a/docs/docs/learn/guides/datapipe.md b/docs/docs/learn/guides/datapipe.md
new file mode 100644
index 0000000..663c65e
--- /dev/null
+++ b/docs/docs/learn/guides/datapipe.md
@@ -0,0 +1,130 @@
+---
+title: DataPipe
+description: Born-open data collection — send jsPsych data straight to an OSF project, with automatic Psych-DS metadata.
+tags:
+- Data
+- Method
+---
+
+# DataPipe
+
+[DataPipe](https://pipe.jspsych.org) is a **free, open-source service** that sends data from any online experiment directly to the [Open Science Framework](https://osf.io) (OSF). No server setup, no download step. DataPipe is built by the [jsPsych](https://www.jspsych.org) team.
+
+:::tip Full documentation
+
+This page covers the essentials of using DataPipe with jsPsych. To use DataPipe itself, or to read the complete, always-current documentation — including the full API and FAQ — go to **[pipe.jspsych.org](https://pipe.jspsych.org)**.
+
+:::
+
+## How it works
+
+Three steps to start collecting data:
+
+1. **Create an OSF project** and link your OSF account to DataPipe.
+2. **Set up an experiment on DataPipe** and add a few lines of code to your study to send data through the API.
+3. **Activate data collection.** Participant data goes straight to your OSF project as files — no downloads, no manual transfers.
+
+Using jsPsych? One trial saves all your data:
+
+```javascript title="experiment.html"
+// Save data with the jsPsych pipe plugin
+const save_data = {
+ type: jsPsychPipe,
+ action: "save",
+ experiment_id: "your_id",
+ filename: filename,
+ data_string: () => jsPsych.data.get().csv()
+};
+```
+
+Not using jsPsych? A single fetch call is all you need:
+
+```javascript title="experiment.js"
+// Send data with a fetch request
+fetch(url, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ experimentID: "your_id",
+ filename: "subject01.csv",
+ data: dataAsString
+ })
+});
+```
+
+## Getting started
+
+Set up DataPipe to send experiment data directly to the OSF using free tools.
+
+1. **Create an OSF project.** Make a project at [osf.io](https://osf.io) to store your data (you'll need a free OSF account). Your OSF account can also sign you in to DataPipe directly.
+2. **Link your OSF account to DataPipe.** DataPipe needs authorization to create files in your OSF projects. If you sign in with your OSF account this is automatic; otherwise, use **Link OSF Account** in your [Account Settings](https://pipe.jspsych.org/admin/account). (A legacy personal-access-token option is also available — see [pipe.jspsych.org](https://pipe.jspsych.org).)
+3. **Create a DataPipe experiment.** Click **New Experiment** and provide a title, your **OSF Project ID** (the short code from the project URL, e.g. `abcde` in *osf.io/abcde*), and a **Data Component Name** for the component DataPipe creates to hold your data files.
+4. **Configure the experiment.** The dashboard has optional features: condition assignment, data validation (with required-field checks), a session limit, and Psych-DS metadata. See [Metadata with DataPipe](#metadata-with-datapipe) below.
+5. **Add code to your experiment.** With jsPsych, the easiest option is the [jsPsychPipe plugin](https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-pipe); otherwise call the [API](#sending-data) directly. Ready-to-use snippets for both are on your experiment dashboard.
+6. **Publish your experiment online.** Host it on any web server — university hosting, GitHub Pages, Netlify, etc.
+7. **Activate and test.** Turn on the features you need (data collection, base64 collection, condition assignment, Psych-DS metadata), then run through your experiment once to confirm files appear in your OSF component.
+
+:::warning
+
+Only activate the features you need, and only during active data collection. DataPipe creates an open path to your OSF project — validation and session limits reduce the risk of unwanted submissions.
+
+:::
+
+
+Publishing with GitHub Pages (a free option)
+
+1. Create a GitHub account and a [new repository](https://www.github.com/new). The repo name becomes part of your experiment URL, so avoid names that reveal information to participants. Add a README so the repo isn't empty.
+2. Go to **Settings → Pages**, set the source to **Deploy from a branch**, select **main**, and click **Save**.
+3. Use **Add Files → Upload Files** to upload your experiment, then **Commit Changes**.
+
+Your experiment will be at `https://[username].github.io/[repo-name]`. If your HTML file isn't named `index.html`, append its filename. It can take a few minutes to go live.
+
+
+
+## Sending data
+
+All endpoints accept JSON request bodies with `Content-Type: application/json`, and need the **experiment ID** from your dashboard. Your dashboard also shows ready-made code samples.
+
+- **Save text data** — `POST https://pipe.jspsych.org/api/data/`. Send `experimentID`, a unique `filename` (e.g. `subject01.csv`), and `data` (the file contents as a string). Validation rules, if configured, run before the file reaches OSF.
+- **Save base64 data** — `POST https://pipe.jspsych.org/api/base64/`. Same fields, with `data` as a base64-encoded string; DataPipe decodes it and stores the binary file (audio, video, images).
+- **Get condition assignment** — `POST https://pipe.jspsych.org/api/condition/`. Send `experimentID`; returns the next condition number, cycling `0 … n−1` for balanced assignment.
+
+A successful request returns `{ "message": "Success" }` (the condition endpoint also returns a `condition`). On failure the response has an `error` code and a `message` — common ones include `MISSING_PARAMETER`, `EXPERIMENT_NOT_FOUND`, `DATA_COLLECTION_NOT_ACTIVE`, `SESSION_LIMIT_REACHED`, `INVALID_DATA`, and `OSF_FILE_EXISTS`. See the [full API reference](https://pipe.jspsych.org/api-docs) for every field and error code.
+
+## Metadata with DataPipe
+
+DataPipe can automatically generate machine-readable metadata describing your dataset according to the [Psych-DS specification](https://psychds-docs.readthedocs.io/en/latest/) — a community standard that makes datasets easier to understand, share, and reuse. For background on the standard and the standalone tools, see [What is Psych-DS?](./metadata-what-is-psych-ds.md)
+
+When enabled, DataPipe writes a `dataset_description.json` to your OSF project and updates it as new sessions arrive. For each variable it records the data type and, when available, a human-readable description drawn from the relevant jsPsych plugin documentation, plus observed numeric ranges and categorical values across sessions.
+
+:::warning Generated, not validated
+
+DataPipe **generates** these files from your incoming data using the [@jspsych/metadata](https://github.com/jspsych/metadata) library — the same code the [metadata CLI](./metadata-cli-guide.md) uses. It does **not** run a Psych-DS validator on the result. To confirm a dataset passes validation, run the [Psych-DS validator](https://psych-ds.github.io/validator/) on the files in your OSF project separately.
+
+:::
+
+With metadata enabled, each submission produces a Psych-DS-compliant layout in your OSF data component:
+
+- `dataset_description.json` — the dataset-level metadata file, updated after every session.
+- `data/raw/` — your original submission, stored byte-for-byte. This is the critical upload; every other file is derived from it.
+- `data/_data.csv` — the main data table as CSV (CSV submissions keep their exact bytes; JSON is converted to a CSV table).
+- `data/_measure-_data.csv` — one sidecar CSV per column that holds nested arrays-of-objects or objects, so nested measures stay analyzable as flat tables.
+- `.psychds-ignore` — placed at the component root so validators skip the `data/raw/` folder.
+
+Metadata production is **strongly recommended** when you plan to share or publish your data. To enrich the generated metadata — authors, a study description, custom variable descriptions — the [metadata options format](./metadata-options.md) and [CLI](./metadata-cli-guide.md) work on the same `dataset_description.json` files.
+
+## Good to know
+
+- **DataPipe is free.** The costly parts of online studies — hosting and storage — are handled by free services (GitHub Pages, the OSF); DataPipe is a lightweight bridge between them.
+- **DataPipe doesn't store your data.** It routes data to your OSF project and keeps no copy. The one exception: if an OSF upload fails, the data is cached (encrypted, up to one week) and retried automatically until it succeeds.
+- **DataPipe won't host your experiment.** You need separate hosting (GitHub Pages, Netlify, university servers). DataPipe only sends data to OSF.
+- **There's a 32 MB request limit,** enforced by the server. Most jsPsych datasets are far smaller (≈50 KB–5 MB); the pipe plugin gzip-compresses text data, effectively raising the limit for most experiments.
+- **Who can see your data** depends on your OSF settings — a private component is visible only to you and your collaborators.
+
+For the complete FAQ (security, one-click auth, condition designs, and more), see [pipe.jspsych.org](https://pipe.jspsych.org).
+
+### How to cite DataPipe
+
+If you use DataPipe to collect data, please cite:
+
+> de Leeuw, J. R. (2024). DataPipe: Born-open data collection for online experiments. *Behavior Research Methods*, 56(3), 2499–2506. [https://doi.org/10.3758/s13428-023-02161-x](https://doi.org/10.3758/s13428-023-02161-x)
diff --git a/docs/docs/learn/guides/metadata-cli-guide.md b/docs/docs/learn/guides/metadata-cli-guide.md
new file mode 100644
index 0000000..506d209
--- /dev/null
+++ b/docs/docs/learn/guides/metadata-cli-guide.md
@@ -0,0 +1,228 @@
+---
+title: Using the metadata CLI
+description: Turn raw jsPsych experiment data into a Psych-DS compliant dataset with the jsPsych Metadata CLI.
+tags:
+- Data
+- Metadata
+- Tool
+---
+
+# Using the metadata CLI
+
+This guide walks you through using the jsPsych Metadata CLI to turn your raw experiment data into a [Psych-DS](https://psychds-docs.readthedocs.io/en/latest/) compliant dataset. By the end, you'll have a structured project folder with a `dataset_description.json` file that describes your experiment and its variables.
+
+If you're not sure whether the CLI or the browser wizard is right for you, start with [Getting started](./metadata-getting-started.md). For a complete list of flags, exit codes, and filename rules, see the [CLI reference](./metadata-cli-reference.md).
+
+## Before you start
+
+You'll need:
+
+- **Node.js** installed on your computer (version 18 or later). You can check by running `node --version` in your terminal. If it's not installed, download it from [nodejs.org](https://nodejs.org).
+- **Your jsPsych data files** — CSV, JSON, or JSON-Lines files produced by your experiment. These can be in one folder or organized into subfolders one level deep. See [Accepted data formats](#accepted-data-formats) below.
+
+## Running the tool
+
+Open a terminal and run:
+
+```
+npx @jspsych/metadata-cli
+```
+
+The first time you run this, `npx` will download the tool automatically. After that it launches immediately.
+
+## Accepted data formats
+
+The CLI reads jsPsych data as **CSV**, a **JSON array** (`[ {…}, {…} ]`), the **`{ "trials": [...] }` wrapper** some platforms (e.g. OSF) use, or **JSON-Lines (`.jsonl`)**. JSON and JSON-Lines are converted to CSV in the output `data/` folder so the validator can read them, and your originals are preserved untouched under `data/raw/`. Files of any other type are ignored.
+
+See the [CLI reference](./metadata-cli-reference.md#accepted-formats) for the exact per-format behaviour.
+
+## Interactive walkthrough
+
+The following steps use two example data files named `participant_01.csv` and `participant_02.csv`.
+
+### Step 1: Create a new project or update an existing one
+
+```
+? What would you like to do?
+❯ Create a new project
+ Update an existing project
+```
+
+Select **Create a new project** if this is the first time you're generating metadata for this dataset. Use **Update an existing project** if you've run the tool before and want to regenerate the metadata (for example, after collecting more data).
+
+### Step 2: Choose where to save the project
+
+```
+? Path to the folder where the new project will be created:
+```
+
+Enter the path to an existing folder — the tool will create a new subfolder inside it for your project. **The folder you enter here must already exist.**
+
+- **Windows:** `C:\Users\yourname\Documents\experiments`
+- **Mac / Linux:** `/Users/yourname/Documents/experiments` or `~/experiments`
+
+On Windows, you can copy the path directly from File Explorer's address bar. On Mac, you can drag a folder into the terminal window to paste its path. The `~` character is a shortcut for your home folder on Mac and Linux.
+
+### Step 3: Name your project
+
+```
+? Enter the project name (used as the folder name and in the metadata): my-experiment
+```
+
+The name becomes the subfolder created inside the folder from Step 2, and is recorded in the metadata. Use something descriptive without spaces (hyphens are fine). `my-experiment` here is just an example — use whatever name fits your study; the rest of this guide uses `my-experiment` to illustrate the output.
+
+### Step 4: Point to your data
+
+```
+? Path to your raw data folder (files will be copied, not moved):
+```
+
+Enter the path to the folder containing your jsPsych data files. Use the same path format as Step 2 — the full path to an existing folder on your computer. Your original files are never modified; the tool copies them into the new project.
+
+### Step 5: File naming (if your files need renaming)
+
+Psych-DS requires data files to follow a specific naming pattern: `keyword-value_data.csv`. For example, `subject-01_data.csv` or `task-flanker_data.csv`.
+
+If your files don't already follow this pattern, the tool lists them and offers a menu of naming **strategies**:
+
+```
+2 data file(s) do not follow the Psych-DS naming pattern ([keyword-value_]+data.csv):
+ participant_01.csv
+ participant_02.csv
+
+Scanning 2 data file(s) for identifier columns…
+
+? How should these files be renamed?
+❯ Use the "subject_id" value found inside each file (recommended)
+ Most reliable: the ID is read from the data itself, so it works even when the old filenames are meaningless.
+ participant_01.csv → subject-p01_data.csv
+ participant_02.csv → subject-p02_data.csv
+ Keep only the part that differs between the filenames
+ Give the files fresh numbered names (subject-001, subject-002, …)
+ Keep the whole old filename as the value
+```
+
+Each strategy shows a live preview of what it would produce, so you can judge the choice on your real filenames. The recommended option, when available, is **using the value found inside each file** — it reads an ID column from the data itself, so it works even when the old filenames are meaningless. For what each strategy does and when it's offered, see [Data file naming](./metadata-cli-reference.md#data-file-naming) in the CLI reference.
+
+After picking a strategy you'll see the full set of proposed renames and choose what to do next:
+
+```
+Proposed renames:
+ participant_01.csv → subject-p01_data.csv
+ participant_02.csv → subject-p02_data.csv
+
+? Apply these names?
+❯ Apply
+ Edit one filename
+ Choose a different strategy
+```
+
+Choose **Apply** to write the names, **Edit one filename** to hand-tune a single name, or **Choose a different strategy** to go back. Name collisions are flagged and auto-adjusted in the preview before anything is written.
+
+:::tip If you prefer, rename your files before running the tool
+A compliant name looks like `subject-01_data.csv` — a keyword, a hyphen, a value, then `_data.csv`. Files that already follow this pattern are used as-is, with no prompt. (Files that use an *unofficial* keyword are technically valid but draw a validator warning; the tool offers to rename those too.)
+:::
+
+### Step 6: Join keys for nested data (only if needed)
+
+Some jsPsych data contains **nested arrays** inside a trial (for example, per-keypress logs). Psych-DS stores these as separate CSV files, which need a column that uniquely identifies each row. If `trial_index` alone isn't unique, the tool asks you to add more columns:
+
+```
+⚠ [trial_index] not unique in "participant_01.csv": 12 duplicate rows found.
+ Nested arrays need a unique row ID to be saved as separate CSV files.
+ Suggested addition: [subject_id]
+
+? Select additional join-key columns for extracted array CSVs:
+ ── Sufficient alone ──
+❯◉ subject_id
+ ── Reduces duplicates ──
+ ◯ block
+ ──────────────
+ ◯ Proceed anyway (extracted CSVs may have duplicate rows)
+```
+
+Columns under **Sufficient alone** make every row unique by themselves; columns under **Reduces duplicates** help but may need to be combined. Pick the column(s) that identify a row, or choose **Proceed anyway** to skip. For JSON-Lines data with no per-trial id, the tool synthesizes a `source_record_id` (the line number) so nested data can still be split out — this marks the source record, not a real participant. Most flat datasets never see this prompt.
+
+### Step 7: Customize metadata (optional)
+
+```
+? Would you like to customize the metadata?
+❯ Use defaults
+ Use a custom metadata file
+```
+
+The tool generates metadata automatically from your data files. If you want to add author names, a study description, or override variable descriptions, choose **Use a custom metadata file** and provide a path to a `.json` file. See [Metadata options](./metadata-options.md) for the format.
+
+Select **Use defaults** for now — you can always edit `dataset_description.json` directly later, or re-run the CLI and provide an options file at that point.
+
+### Step 8: Fill in unknown variable descriptions (optional)
+
+After reading your data files, the tool tries to look up what each variable means by checking the jsPsych plugin that produced it. For variables it couldn't identify automatically, it will ask:
+
+```
+3 variable(s) have unknown descriptions. Would you like to fill them in?
+❯ Fill in descriptions - You will be prompted for each variable. Press Enter to skip individual ones.
+ Skip - Leave descriptions as unknown in the dataset_description.json.
+```
+
+If you choose to fill them in, you'll see one prompt per variable. Press Enter to skip any you'd rather leave for later.
+
+### Step 9: Validation
+
+The tool automatically checks your new project against the Psych-DS specification:
+
+```
+✔ Psych-DS validation passed (2 warnings).
+ (Rerun with --verbose to see warnings.)
+```
+
+A checkmark means your dataset is valid. Warnings are minor issues (like recommended fields that aren't filled in yet) — your dataset is still usable. If there are errors, the tool will describe them and may prompt you to fix required fields before finishing.
+
+## What you get
+
+Your new project folder will be inside the location you chose in Step 2, named after the project name from Step 3. For example, if you chose `C:\Users\yourname\Documents\experiments` as the output location and `my-experiment` as the project name, the result is at `C:\Users\yourname\Documents\experiments\my-experiment\`:
+
+```
+my-experiment/
+├── data/
+│ ├── subject-p01_data.csv Psych-DS compliant copies of your data
+│ └── subject-p02_data.csv
+├── dataset_description.json generated metadata
+├── README.md placeholder for a human-readable description
+└── CHANGES.md placeholder for a changelog
+```
+
+The `data/` folder holds the Psych-DS-compliant copies of your data. The example above starts from CSV files, which are already tabular and so are written straight to `data/`.
+
+If your inputs are **JSON or JSON-Lines**, the tool converts them to CSV in `data/` and additionally preserves your originals untouched under a `data/raw/` folder, alongside a top-level `.psychds-ignore` file that tells the validator to skip the raw copies. (CSV inputs aren't duplicated under `raw/`, so a CSV-only dataset has no `data/raw/` folder.) Data files that contain **nested arrays** produce one extra CSV per nested column.
+
+Open `dataset_description.json` to see what was generated. It will look something like:
+
+```json
+{
+ "@context": "https://schema.org/",
+ "@type": "Dataset",
+ "name": "my-experiment",
+ "variableMeasured": [
+ {
+ "@type": "PropertyValue",
+ "name": "trial_type",
+ "description": "The name of the jsPsych plugin used to run the trial."
+ },
+ {
+ "@type": "PropertyValue",
+ "name": "rt",
+ "description": "The response time in milliseconds for the participant to make a response."
+ }
+ ]
+}
+```
+
+## Next steps
+
+- **Edit `dataset_description.json`** to add your name as an author, a study description, or other dataset-level fields.
+- **Edit `README.md`** inside your project to add a human-readable description of the experiment.
+- **Re-run the CLI** any time you add new data: use **Update an existing project** and point to your project folder — it will reload your existing metadata and incorporate the new files.
+- **Share or archive your project folder** — it's a self-contained, Psych-DS compliant dataset.
+
+For automating the tool in scripts (non-interactive mode, flags, exit codes), see the [CLI reference](./metadata-cli-reference.md). Prefer a point-and-click interface? See [Using the web wizard](./metadata-web-wizard.md).
diff --git a/docs/docs/learn/guides/metadata-cli-reference.md b/docs/docs/learn/guides/metadata-cli-reference.md
new file mode 100644
index 0000000..0dba69a
--- /dev/null
+++ b/docs/docs/learn/guides/metadata-cli-reference.md
@@ -0,0 +1,174 @@
+---
+title: Metadata CLI reference
+description: All flags, exit codes, filename rules, and output behaviour for the jsPsych Metadata CLI.
+tags:
+- Data
+- Metadata
+- Reference
+---
+
+# Metadata CLI reference
+
+This page documents all flags, exit codes, filename rules, and output behaviour for the jsPsych Metadata CLI. For a step-by-step walkthrough of a typical first run, see [Using the CLI](./metadata-cli-guide.md).
+
+## Running the tool
+
+```
+npx @jspsych/metadata-cli [flags]
+```
+
+With no flags, the tool runs interactively and prompts you for everything it needs. All flags are optional — any flag you omit will be filled in by a prompt at runtime.
+
+## Flags
+
+| Flag | Alias | Type | Description |
+|------|-------|------|-------------|
+| `--psych-ds-dir` | `-em` | path | Path to an **existing** Psych-DS project folder. Must contain a `dataset_description.json`. Use this when updating a project you have already generated. |
+| `--data-dir` | `-d` | path | Path to the folder containing your raw jsPsych data files (`.csv`, `.json`, or `.jsonl`). |
+| `--metadata-options` | `-m` | path | Path to a metadata options `.json` file. See [Metadata options](./metadata-options.md). |
+| `--verbose` | `-v` | boolean | Print detailed output at each processing step. Shows plugin fetching, variable resolution, and full validation warnings. |
+
+### Notes on flag behaviour
+
+- Paths can use `~` for your home directory (e.g. `--data-dir=~/experiments/raw`).
+- If a flag is provided but the path is invalid, the tool falls back to prompting for that step interactively.
+- `--psych-ds-dir` implies **update mode** — the tool loads the existing `dataset_description.json` before processing new data. Without this flag, the tool asks whether to create or update.
+
+## Non-interactive mode
+
+When all three path flags are provided and valid, every interactive prompt is skipped and the tool runs to completion without user input:
+
+```
+npx @jspsych/metadata-cli \
+ --psych-ds-dir=/path/to/project \
+ --data-dir=/path/to/data \
+ --metadata-options=/path/to/options.json
+```
+
+Non-interactive mode enforces stricter rules than interactive mode:
+
+- **Non-compliant filenames are a hard error.** In interactive mode, the tool offers a menu of renaming strategies to bring non-compliant filenames into the Psych-DS naming pattern. In non-interactive mode, a non-compliant filename causes the tool to exit immediately with an error message and exit code 1. Rename your files before running (see [Data file naming](#data-file-naming) below).
+- **Unknown variable descriptions are not prompted.** Variables the tool cannot automatically describe are left as `"unknown"` in the output.
+- **Join keys are resolved automatically.** When nested-array rows aren't uniquely identified by `trial_index`, the tool picks the keys deterministically instead of prompting, and reports the choice (see [Nested arrays and join keys](#nested-arrays-and-join-keys)).
+
+The tool also runs without prompting whenever it isn't attached to an interactive terminal (for example, piped output or a CI job), even if you omit some flags. In that case it keeps the generated metadata defaults unless `--metadata-options` is supplied. Non-interactive mode is useful for running the tool on a schedule, in a script, or on a remote machine.
+
+## Exit codes
+
+| Code | Meaning |
+|------|---------|
+| `0` | Completed successfully. Psych-DS validation passed (warnings are allowed). |
+| `1` | Psych-DS validation failed with one or more errors, or a non-compliant filename was found in non-interactive mode. |
+
+You can use the exit code in a shell script to handle failures:
+
+```bash
+npx @jspsych/metadata-cli --psych-ds-dir=./project --data-dir=./data --metadata-options=./options.json
+if [ $? -ne 0 ]; then
+ echo "Metadata generation failed — check the output above for errors."
+fi
+```
+
+## Data file requirements
+
+### Accepted formats
+
+The tool accepts the following jsPsych data shapes:
+
+| Format | Notes |
+|--------|-------|
+| **CSV** | One row per trial. Copied to `data/` under its normalized name. Unnamed row-index columns (the blank leading column some exporters and R add) are dropped. |
+| **JSON array** | The standard jsPsych export: `[ {…}, {…} ]`. Converted to CSV. |
+| **`{ "trials": [...] }` wrapper** | An object whose single key is `trials` holding the trial array (e.g. OSF exports). Automatically unwrapped, then treated as a JSON array. |
+| **JSON-Lines (`.jsonl`)** | One JSON value per line (JATOS and several labs export this way — often one participant's trial array per line). All lines are flattened into a single observation stream. |
+
+JSON and JSON-Lines files are automatically converted to CSV in the output (`data/` folder); the originals are preserved byte-for-byte under `data/raw/`, and a top-level `.psychds-ignore` is written so the validator skips that raw copy. CSV files are written to `data/` under their normalized name; because they are already tabular, CSV inputs are **not** duplicated under `data/raw/` (so a CSV-only dataset has no `data/raw/` folder).
+
+Files of any other type are ignored during metadata generation.
+
+### Nested arrays and join keys
+
+When a data file contains nested arrays inside a trial, the tool extracts each array into its own Psych-DS CSV. Those rows need a column that uniquely identifies them. If `trial_index` alone isn't unique, the tool prompts (interactively) for additional **join-key** columns, grouping candidates into "Sufficient alone" and "Reduces duplicates", with a "Proceed anyway" escape. In a non-interactive run there is nothing to prompt, so the keys are resolved automatically and the choice is reported in the output. For JSON-Lines input with no per-trial identifier, a `source_record_id` (the source line) is synthesized so the join key can be formed; it marks the source record, not a real participant.
+
+### Folder depth
+
+The tool reads all files in the data folder and one level of subdirectories. Files nested deeper are not processed.
+
+### Data file naming
+
+Psych-DS requires all data files to follow a `keyword-value_data.csv` naming pattern. Each filename consists of one or more `keyword-value` pairs joined by underscores, ending with `_data.csv`.
+
+**Valid examples:**
+
+```
+subject-01_data.csv
+task-flanker_data.csv
+subject-01_session-2_data.csv
+study-zebraQuestionnaire_data.csv
+```
+
+**Invalid examples:**
+
+```
+results.csv ← missing keyword-value structure and _data suffix
+participant_01.csv ← underscore instead of hyphen between keyword and value
+data_2024-01-15.csv ← date is not in keyword-value format
+flanker_results_data.csv ← "flanker_results" is not a keyword-value pair
+```
+
+In **interactive mode**, the tool detects non-compliant names and offers a menu of naming **strategies**, each with a live preview of what it would produce on your actual filenames:
+
+| Strategy | What it does | When offered |
+|----------|--------------|--------------|
+| **Use the value found inside each file** | Reads an ID column from the data (one unique value per file) and uses it as the value. The most reliable option, and recommended when available. | Only when such a column exists in *every* file. |
+| **Keep only the part that differs** | Strips the shared prefix/suffix across filenames; the varying middle becomes the value (you pick the keyword). | Only when the filenames share a common pattern. |
+| **Give the files fresh numbered names** | Replaces names with a clean sequence (`subject-001`, `subject-002`, …); you type the first name. | Always. |
+| **Keep the whole old filename as the value** | The fallback: the entire old name becomes one value under a keyword you pick. Nothing is lost, but names get verbose. | Always. |
+
+After you pick a strategy, the tool shows the full set of proposed renames (including any sidecar CSVs from nested arrays, and auto-adjusting name collisions) and lets you **Apply**, **Edit one filename**, or **Choose a different strategy**. Psych-DS values may not contain hyphens or underscores, so those are stripped when a value is derived from a filename. For example:
+
+```
+participant_01.csv → subject-participant01_data.csv
+flanker_results.json → task-flankerResults_data.csv
+```
+
+Files with technically valid names that use an **unofficial keyword** (one not in the table below) are legal but draw a validator warning; the tool offers to rename those too.
+
+Official Psych-DS keywords offered by the tool:
+
+| Keyword | Intended use |
+|---------|-------------|
+| `subject` | The participant or subject the data belongs to |
+| `session` | A session of data collection |
+| `task` | The task in which the data was collected |
+| `condition` | The experimental condition |
+| `trial` | The trial the data belongs to |
+| `stimulus` | The stimulus item |
+| `study` | The study the data belongs to |
+| `site` | The site where data was collected |
+| `description` | A free-form label |
+
+Custom keywords are allowed but will produce a validator warning. In **non-interactive mode**, non-compliant filenames are a hard error — rename files before running.
+
+## Validation output
+
+After generating `dataset_description.json`, the tool automatically validates the project against the Psych-DS specification.
+
+**Passed:**
+```
+✔ Psych-DS validation passed (2 warnings).
+ (Rerun with --verbose to see warnings.)
+```
+
+**Failed:**
+```
+✘ Psych-DS validation failed: 1 error, 0 warnings.
+
+ Error 1: JSON_KEY_REQUIRED: dataset_description.json is missing required field(s): [description]
+```
+
+Warnings are advisory — they indicate recommended fields or practices that aren't strictly required. A dataset with warnings is still valid. Errors must be resolved for the dataset to be Psych-DS compliant.
+
+Run with `--verbose` to see the full list of warnings alongside errors.
+
+If validation fails due to missing required fields and you are running interactively, the tool will prompt you to fill them in before finishing.
diff --git a/docs/docs/learn/guides/metadata-getting-started.md b/docs/docs/learn/guides/metadata-getting-started.md
new file mode 100644
index 0000000..0ba53f3
--- /dev/null
+++ b/docs/docs/learn/guides/metadata-getting-started.md
@@ -0,0 +1,57 @@
+---
+title: Getting started with Psych-DS metadata
+description: Generate Psych-DS compliant metadata for your jsPsych experiment data with the metadata tools.
+tags:
+- Data
+- Metadata
+- Method
+---
+
+# Getting started with Psych-DS metadata
+
+The [jsPsych metadata project](https://github.com/jspsych/metadata) generates [Psych-DS](https://psychds-docs.readthedocs.io/en/latest/) compliant metadata for [jsPsych](https://www.jspsych.org/) experiments. It reads your raw experiment data and produces a `dataset_description.json` that describes your experiment and its variables, so your data is easier to share, archive, and reuse.
+
+There are two ways to use it — both produce the same Psych-DS output. Pick whichever fits how you work.
+
+:::tip
+If you collect data with [DataPipe](./datapipe.md), you may not need to run these tools at all — DataPipe can generate this same Psych-DS metadata automatically in your OSF project as sessions are uploaded.
+:::
+
+## What these tools do
+
+[Psych-DS](https://psychds-docs.readthedocs.io/en/latest/) is a community standard built around a machine-readable `dataset_description.json` that travels with your data. The hardest part of meeting it is writing that file by hand — these tools automate it, reading your jsPsych data, looking up what each variable means from the plugin that produced it, and generating the description for you.
+
+See [What is Psych-DS?](./metadata-what-is-psych-ds.md) for the full background on the standard.
+
+## Two ways to use it
+
+### Web wizard — point and click
+
+A wizard that runs entirely in your browser. Upload your data, fill in a few fields, and download a ready-to-share Psych-DS project — including in-browser validation. Nothing leaves your computer. The wizard is not hosted online yet; you run it locally from the [jspsych/metadata](https://github.com/jspsych/metadata) repository with a couple of terminal commands.
+
+→ **[Using the web wizard](./metadata-web-wizard.md)**
+
+### Command-line tool — for local folders and automation
+
+A terminal tool that reads a folder of data files and writes a Psych-DS project next to them. Best when your data already lives on your machine, or when you want to script metadata generation as part of a pipeline.
+
+→ **[Using the CLI](./metadata-cli-guide.md)** for a step-by-step walkthrough, and the **[CLI reference](./metadata-cli-reference.md)** for flags, exit codes, and non-interactive use.
+
+### Which should I pick?
+
+Use the **web wizard** if you prefer a point-and-click interface. Use the **CLI** if your data is already in a local folder, or you need to run metadata generation unattended (in a script, on a schedule, or on a remote machine).
+
+## What you'll get
+
+Either path produces a self-contained Psych-DS project folder:
+
+```
+your-project/
+├── data/ your data files, in Psych-DS compliant CSV form
+│ └── raw/ your original files, untouched
+├── dataset_description.json the generated metadata
+├── README.md placeholder for a human-readable description
+└── CHANGES.md placeholder for a changelog
+```
+
+Both tools accept jsPsych data as CSV, JSON, or JSON-Lines (`.jsonl`), and convert JSON/JSONL to CSV so the Psych-DS validator can read it. Your originals are always preserved under `data/raw/`.
diff --git a/docs/docs/learn/guides/metadata-options.md b/docs/docs/learn/guides/metadata-options.md
new file mode 100644
index 0000000..1531ab5
--- /dev/null
+++ b/docs/docs/learn/guides/metadata-options.md
@@ -0,0 +1,159 @@
+---
+title: Metadata options file
+description: Set or override fields in the generated dataset_description.json with a metadata options file.
+tags:
+- Data
+- Metadata
+- Reference
+---
+
+# Metadata options file
+
+A metadata options file lets you set or override fields in the generated `dataset_description.json` before it is saved. You can use it to add author information, a dataset description, or custom variable descriptions that the tool couldn't determine automatically.
+
+Pass the file path to the CLI with the [`--metadata-options` flag](./metadata-cli-reference.md#flags), or choose **Use a custom metadata file** when prompted interactively.
+
+## File format
+
+The options file is a plain JSON file. It can have any name and be stored anywhere — only the content matters.
+
+At the top level, each key maps to a field in `dataset_description.json`. Two keys have special handling (`author` and `variables`); everything else is written through directly.
+
+**Minimal example:**
+
+```json
+{
+ "name": "Flanker Study",
+ "description": "A jsPsych flanker task measuring response inhibition."
+}
+```
+
+**Complete example:**
+
+```json
+{
+ "name": "Flanker Study",
+ "description": "A jsPsych flanker task measuring response inhibition in undergraduate participants.",
+ "author": {
+ "Alex Johnson": {
+ "givenName": "Alex",
+ "familyName": "Johnson",
+ "identifier": "https://orcid.org/0000-0000-0000-0000"
+ }
+ },
+ "variables": {
+ "rt": {
+ "description": { "user": "Response time in milliseconds from stimulus onset to key press." }
+ },
+ "correct": {
+ "description": { "user": "Whether the participant's response matched the correct answer (true/false)." }
+ }
+ }
+}
+```
+
+## Top-level fields
+
+Any key not named `author` or `variables` is written directly to `dataset_description.json`. Commonly used fields:
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `name` | string | The name of the dataset. |
+| `description` | string | A plain-language description of the dataset. |
+| `license` | string | The license under which the data is shared (e.g. `"CC-BY-4.0"`). |
+| `citation` | string | A citation for the dataset or associated publication. |
+| `url` | string | A URL where the dataset can be found (e.g. OSF link). |
+| `funder` | string | Name of the funding body. |
+| `keywords` | array | List of keywords describing the study (e.g. `["attention", "inhibition"]`). |
+
+These correspond to [Schema.org Dataset](https://schema.org/Dataset) properties. Any valid Schema.org field can be included.
+
+## Author fields
+
+The `author` key takes an object where each entry represents one author. The key you use for each author is their display name, which is also used as the `name` field if you don't specify one explicitly.
+
+```json
+{
+ "author": {
+ "Alex Johnson": {
+ "givenName": "Alex",
+ "familyName": "Johnson",
+ "identifier": "https://orcid.org/0000-0000-0000-0000"
+ },
+ "Sam Lee": {
+ "givenName": "Sam",
+ "familyName": "Lee"
+ }
+ }
+}
+```
+
+Accepted author fields:
+
+| Field | Required | Description |
+|-------|----------|-------------|
+| `name` | inferred | Full name. Defaults to the author's key if omitted. |
+| `givenName` | no | First name. |
+| `familyName` | no | Last name. |
+| `identifier` | no | A persistent identifier URL, such as an [ORCID](https://orcid.org/) (`https://orcid.org/...`). |
+
+## Variable fields
+
+The `variables` key lets you enrich or override variable metadata that was auto-generated from your data files. Each entry is keyed by the exact column name as it appears in your CSV files.
+
+:::note
+Variables listed here must already exist in your data. The tool generates a variable entry for every column it finds — the options file can only update those entries, not create new ones.
+:::
+
+```json
+{
+ "variables": {
+ "rt": {
+ "description": { "user": "Response time in milliseconds from stimulus onset to key press." },
+ "minValue": 0,
+ "maxValue": 5000
+ },
+ "correct": {
+ "description": { "user": "Whether the response was correct." },
+ "levels": ["true", "false"],
+ "levelsOrdered": false
+ },
+ "stimulus": {
+ "description": { "user": "The flanker arrow string shown on screen." }
+ }
+ }
+}
+```
+
+### The `description` field
+
+Variable descriptions use an object where each key identifies the source of the description and each value is the description text:
+
+```json
+"description": { "user": "My description here." }
+```
+
+Use `"user"` as the key for descriptions you write yourself. The tool uses plugin names (e.g. `"jsPsych-html-keyboard-response"`) as keys for descriptions it fetches automatically. Your `"user"` entry appears alongside auto-generated entries rather than replacing them, so the final description in `dataset_description.json` will show both.
+
+To replace a description entirely, open `dataset_description.json` after generation and edit the `description` field for that variable directly.
+
+### Accepted variable fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `description` | object | Source-keyed description (see above). |
+| `minValue` | number | Minimum expected value for numeric variables. |
+| `maxValue` | number | Maximum expected value for numeric variables. |
+| `levels` | array | List of all possible values for categorical variables. |
+| `levelsOrdered` | boolean | Whether `levels` has a meaningful order (e.g. Likert scale). |
+| `na` | boolean | Whether missing values are present in this variable. |
+| `naValue` | string | The string used to represent missing values (e.g. `"NA"`, `"999"`). |
+| `alternateName` | string | An alternative name or abbreviation for the variable. |
+| `identifier` | string | A URL pointing to a formal definition of this variable. |
+| `privacy` | string | Notes on the sensitivity of this variable. |
+
+## Tips
+
+- **You don't need to include every field.** The options file only needs to contain what you want to add or override — the rest is generated automatically from your data.
+- **Run the tool first without an options file**, then open `dataset_description.json` to see what was generated. Use the options file to fill in gaps (missing descriptions, author names, etc.) and re-run.
+- **Variable descriptions you write persist across re-runs.** When you update a project with `--psych-ds-dir`, the existing `dataset_description.json` is loaded first, so your custom descriptions are carried forward.
diff --git a/docs/docs/learn/guides/metadata-web-wizard.md b/docs/docs/learn/guides/metadata-web-wizard.md
new file mode 100644
index 0000000..38d4702
--- /dev/null
+++ b/docs/docs/learn/guides/metadata-web-wizard.md
@@ -0,0 +1,107 @@
+---
+title: Using the metadata web wizard
+description: A browser-based wizard for generating Psych-DS compliant metadata from jsPsych experiment data.
+tags:
+- Data
+- Metadata
+- Tool
+---
+
+# Using the metadata web wizard
+
+A browser-based wizard for generating [Psych-DS](https://psychds-docs.readthedocs.io/en/latest/) compliant `dataset_description.json` files from jsPsych experiment data. It mirrors the functionality of the [CLI](./metadata-cli-guide.md) in a point-and-click interface, and your data never leaves your computer — all processing happens in the browser.
+
+If you're not sure whether the wizard or the CLI is right for you, start with [Getting started](./metadata-getting-started.md).
+
+## Running the wizard
+
+The wizard is the `packages/frontend` app in the [jspsych/metadata](https://github.com/jspsych/metadata) repository. It is not deployed to a public URL yet, so you run it locally:
+
+```bash
+git clone https://github.com/jspsych/metadata.git
+cd metadata
+npm ci
+cd packages/frontend && npm run dev
+```
+
+Then open the local URL that Vite prints (typically `http://localhost:5173`) in your browser.
+
+## Welcome screen
+
+Choose one of two starting points:
+
+- **Create new project** — start from scratch. The wizard walks you through each step and produces a downloadable Psych-DS project.
+- **Open existing project** — upload an existing `dataset_description.json` to continue editing, or to update the metadata after adding new data files.
+
+A **Dark / Light** toggle in the top-right corner switches the colour theme. Your choice is remembered across sessions.
+
+## Step 1 — Project Info
+
+Fill in basic information about your dataset:
+
+- **Project name** *(required)* — used as the dataset identifier and as the downloaded folder name.
+- **Description** — a plain-English summary of the experiment. Defaults to "No description provided." if left blank.
+- **License, funding source, keywords, citation** *(optional)* — additional Psych-DS recommended fields.
+
+You can also upload an existing `dataset_description.json` here to pre-fill all fields, with a before/after comparison when the uploaded values differ from anything you've already entered.
+
+## Step 2 — Data
+
+Upload your jsPsych data files:
+
+- Click **Choose folder** to browse for a directory (uses the browser's folder picker) **or** click **Upload zip** to upload a `.zip` containing your data files.
+- Each file is shown with a status indicator once processed.
+- Accepted formats: **CSV**, **JSON arrays**, the **`{ "trials": [...] }` wrapper** (e.g. OSF exports), and **JSON-Lines (`.jsonl`)**. JSON and JSONL are converted to Psych-DS-named CSV (e.g. `data/subject-sub01_data.csv`) so the validator and the downloaded zip both see compliant tables; your originals are preserved under `data/raw/`. CSV uploads are kept as-is.
+- If your data files contain **nested arrays**, the wizard checks whether `trial_index` uniquely identifies each row. If not, a **join-key chooser** appears before processing continues.
+
+ **What is a join key?** jsPsych experiments sometimes produce nested data — for example, a survey trial might contain multiple responses stored as an array inside a single row. To save this as a flat table (CSV), each nested item needs to be matched back to its parent row. A join key is a column (or combination of columns) whose values are unique for every row. `trial_index` works fine for single-participant files, but if you merged data from multiple participants, each participant resets `trial_index` to 0 — making it non-unique.
+
+ In the chooser, `trial_index` is pre-selected. Additional candidate columns are listed below it; columns tagged **sufficient alone** make every row unique on their own (typically `source_record_id` or `participant_id`). Select one or more until the combination uniquely identifies each row, then click **Apply and process files**. If no combination works, check **Proceed anyway** — extracted CSVs may contain duplicate rows, but you can still continue.
+
+ Once files are processed, a **Re-configure join keys** button remains available on the Data step if you need to change your selection.
+
+:::note For existing projects
+The Data step is pre-marked complete since your variables are already loaded from the uploaded `dataset_description.json`. You can still upload new data files to regenerate the variable list.
+:::
+
+## Step 3 — Variables
+
+Review and annotate each variable detected in your data:
+
+- Variables with **unknown descriptions** (those the plugin lookup couldn't fill in automatically) appear in an expanded section at the top.
+- Known variables start collapsed. Use **Expand all / Collapse all** to open or close all rows at once.
+- Each row has a **Description** text area and a **Type** dropdown. Editing either updates the metadata immediately.
+- Long level lists are truncated at five entries with a **Show all N** toggle.
+
+## Step 4 — Authors
+
+Add contributors to the dataset:
+
+- Click **Add author** to create a new row. Type a name and press Tab or click away to commit it.
+- Expand a row to fill in optional fields: given name, family name, author type, ORCID.
+- **Bulk import** lets you paste a list of names (one per line, optionally followed by an ORCID) to add multiple authors at once. Invalid ORCIDs are flagged with a warning banner.
+
+## Step 5 — Review
+
+Inspect the generated `dataset_description.json` and download your project:
+
+- The JSON viewer shows a syntax-highlighted, collapsible preview of the output.
+- Click **Download `.zip`** to download a complete Psych-DS project archive:
+ ```
+ .zip
+ ├── dataset_description.json
+ ├── README.md
+ ├── CHANGES.md
+ └── data/
+ └──
+ ```
+- If no data files were uploaded, a **Save `dataset_description.json`** button is shown instead.
+- **Validate dataset** runs the Psych-DS validator entirely in your browser (an internet connection is required) and lists any errors and warnings inline. Missing `README` / `CHANGES` warnings are expected here — the downloaded zip includes both files, so they clear when you validate the unzipped folder using the [Psych-DS web validator](https://psych-ds.github.io/validator/).
+
+The **`{}` Preview** pill button (visible on all steps except Review) opens a live JSON snapshot in a slide-in drawer, so you can check the output at any point without leaving the current step.
+
+## Next steps
+
+- **Unzip and share** your downloaded project — it's a self-contained, Psych-DS compliant dataset.
+- Need to script metadata generation, or already have your data in a local folder? See [Using the CLI](./metadata-cli-guide.md).
+- Want to customize variable descriptions or authors from a file? See [Metadata options](./metadata-options.md).
diff --git a/docs/docs/learn/guides/metadata-what-is-psych-ds.md b/docs/docs/learn/guides/metadata-what-is-psych-ds.md
new file mode 100644
index 0000000..05d8231
--- /dev/null
+++ b/docs/docs/learn/guides/metadata-what-is-psych-ds.md
@@ -0,0 +1,82 @@
+---
+title: What is Psych-DS?
+description: An introduction to the Psych-DS data standard and why it matters for jsPsych experiments.
+tags:
+- Data
+- Metadata
+---
+
+# What is Psych-DS?
+
+[Psych-DS](https://psychds-docs.readthedocs.io/en/latest/) is a community data standard for psychology and behavioral science research. It defines a set of conventions for how datasets should be organized and documented — consistent folder structure, file naming rules, and a machine-readable description file that travels with your data.
+
+This page explains what the standard involves and why it matters. For a hands-on guide to generating a Psych-DS compliant dataset from your jsPsych experiment, see [Getting started](./metadata-getting-started.md).
+
+## The problem it solves
+
+Most behavioral science labs develop their own conventions for organizing data files: a folder structure that made sense at the time, filenames that seemed clear to whoever made them, a README that may or may not still be accurate. This works fine within a lab, but creates friction everywhere else:
+
+- A collaborator receives your data and spends days figuring out what each file contains.
+- You return to a dataset six months later and can't reconstruct what the columns mean.
+- A reviewer asks for raw data and your file structure doesn't match what the paper describes.
+- A meta-analyst wants to include your study but can't parse your variable names automatically.
+
+Psych-DS addresses this by giving datasets a predictable, documented structure that anyone — including software tools — can read without asking you questions first.
+
+## The two core requirements
+
+### 1. File organization
+
+A Psych-DS compliant dataset has a specific folder layout:
+
+```
+my-experiment/
+├── dataset_description.json ← machine-readable metadata about the dataset
+├── data/
+│ └── task-flanker_data.csv ← your data files, named to a standard pattern
+└── data/raw/ ← optional: original files in their earliest form
+```
+
+The `data/` folder holds your data files in CSV format. Each filename follows a `keyword-value_data.csv` pattern (e.g. `subject-01_data.csv`, `task-flanker_data.csv`) so that the role of each file is unambiguous. The `data/raw/` folder is for preserving originals — JSON exports, Excel workbooks, anything that isn't a clean CSV — and is ignored by the validator.
+
+### 2. A metadata file
+
+Every Psych-DS dataset includes a `dataset_description.json` file placed next to the `data/` folder. This is a JSON file that describes the dataset in a machine-readable way, using the [Schema.org](https://schema.org/) vocabulary so that the information is interpretable by any software that understands that standard.
+
+At minimum it must include:
+
+```json
+{
+ "@context": "https://schema.org/",
+ "@type": "Dataset",
+ "name": "Flanker Study",
+ "description": "A description of the dataset.",
+ "variableMeasured": ["trial_type", "rt", "correct", "stimulus"]
+}
+```
+
+The `variableMeasured` field lists every column name that appears across all CSV files in the dataset. This is what makes the metadata machine-readable — a script or meta-analyst can open `dataset_description.json` and immediately know what variables the dataset contains, without opening a single data file.
+
+Richer metadata — author information, variable descriptions, measurement units, links to publications — can also be included and makes the dataset significantly more useful to others.
+
+## Why this matters for jsPsych experiments
+
+jsPsych experiments produce structured tabular data, but the output files typically have ad-hoc names and no attached documentation. Psych-DS gives that data a standard home, and the `dataset_description.json` file is where the documentation lives.
+
+Because jsPsych plugins have consistent, documented parameter names, it's possible to automatically generate much of the `variableMeasured` content by reading the data and looking up what each column means in the plugin that produced it. That's what the jsPsych Metadata CLI does — it reads your experiment output and generates a `dataset_description.json` populated with variable descriptions drawn from the jsPsych plugin documentation.
+
+### How it ties into the rest of jsPsych
+
+Metadata generation isn't a separate tool you have to bolt on afterward — it draws on the same jsPsych modules your experiment is already built from, and can run wherever your workflow lives:
+
+- **In the browser, alongside your experiment.** The `@jspsych/metadata` library runs client-side, so an experiment can generate or update its own `dataset_description.json` at the moment data is collected, using the plugins it already loaded.
+- **In Node or a build step.** The same library runs server-side to process data you've already collected, in batch.
+- **From the command line.** The Metadata CLI wraps that library for researchers who just want to point at a folder of files. See [Using the CLI](./metadata-cli-guide.md).
+- **Via the web wizard.** A point-and-click interface that runs the same process in your browser. See [Using the web wizard](./metadata-web-wizard.md).
+- **Automatically, via DataPipe.** If you collect data with [DataPipe](./datapipe.md), it can generate this metadata for you on OSF as each session is uploaded, using the same `@jspsych/metadata` library.
+
+In every case the variable descriptions come from the **same jsPsych plugin modules your experiment uses**. Because the plugin is the source of truth, the descriptions stay consistent with the plugin versions you actually ran — there's no separate data dictionary to maintain by hand.
+
+## FAIR data principles
+
+Psych-DS is designed to help datasets meet the [FAIR principles](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4792175/) — a widely adopted framework for scientific data that stands for **Findable, Accessible, Interoperable, and Reusable**. A dataset that follows Psych-DS conventions is easier to find (it has a structured description), easier to use by others (the format is predictable), and easier to integrate into meta-analyses and automated pipelines.
diff --git a/docs/docs/learn/guides/storing-data.md b/docs/docs/learn/guides/storing-data.md
index 58b65ca..bd68c9b 100644
--- a/docs/docs/learn/guides/storing-data.md
+++ b/docs/docs/learn/guides/storing-data.md
@@ -8,7 +8,7 @@ tags:
# Storing and exporting data
-While an experiment runs, jsPsych collects data [in memory](../concepts/data.md). That data disappears as soon as the browser window closes, so saving it permanently is a crucial part of any experiment. Because there are dozens of ways to store data permanently, jsPsych does not lock you into one solution. This page describes two common approaches: writing each participant's data to a file, and writing it to a database.
+While an experiment runs, jsPsych collects data [in memory](../concepts/data.md). That data disappears as soon as the browser window closes, so saving it permanently is a crucial part of any experiment. Because there are dozens of ways to store data permanently, jsPsych does not lock you into one solution. This page describes two common approaches: writing each participant's data to a file, and writing it to a database. If you'd rather not run a server at all, a hosted service like [DataPipe](./datapipe.md) can receive your data and save it to the [Open Science Framework](https://osf.io/).
For more on how the in-memory data is structured and how to filter or aggregate it before saving, see [The data object](../concepts/data.md).
@@ -169,3 +169,7 @@ var trial = {
}
}
```
+
+## Sending data to a hosted service
+
+If you don't want to run and maintain a server of your own, you can send data to a hosted service instead. [DataPipe](./datapipe.md) is a free, jsPsych-maintained service that receives data from the participant's browser and saves it to your project on the [Open Science Framework](https://osf.io/). You add a single trial at the end of your experiment — no PHP, database, or server administration required. DataPipe can also generate [Psych-DS metadata](./metadata-what-is-psych-ds.md) for your dataset automatically. See the [DataPipe guide](./datapipe.md) to get started.
diff --git a/docs/docs/learn/tutorials/rt-task.md b/docs/docs/learn/tutorials/rt-task.md
index 23fafea..9b94683 100644
--- a/docs/docs/learn/tutorials/rt-task.md
+++ b/docs/docs/learn/tutorials/rt-task.md
@@ -1443,3 +1443,7 @@ This code is available in the `/examples` folder in the jsPsych release download