---
title: "CSV import"
description: "Build a rundown from a spreadsheet instead of retyping it."
---

# CSV import

If your running order already exists in a spreadsheet, import it rather than retyping
it. Up to 500 rows at a time.

Open the [Controller](controller.md) and click **Import CSV** in the header.

## Your spreadsheet does not need specific column names

This is the most important thing to know, and the opposite of what most importers
expect: **there is no required format.** The importer reads whatever headers are in your
first row and lets you map each column to a timer field yourself.

The four fields you can map to are **title**, **speaker**, **notes**, and **duration**.
Anything you do not need, leave as `(ignore)`.

It guesses the mapping for you where it can — case-insensitively:

| Maps to      | Header names it recognises                                     |
| ------------ | -------------------------------------------------------------- |
| **title**    | anything containing `title`, or exactly `name` / `item`        |
| **speaker**  | anything containing `speaker`, or exactly `presenter` / `host` |
| **notes**    | anything containing `note`, or exactly `description`           |
| **duration** | anything containing `duration`, or exactly `time` / `length`   |

Anything it does not recognise starts as `(ignore)` — just set it yourself from the
dropdown.

**No single column is required.** The only rule is that at least one column must map to
something, or you will get "Map at least one column to a timer field before importing."

## Duration format

The duration cell accepts either:

- **`mm:ss`** — `45:00` is forty-five minutes
- **A plain number of seconds** — `2700` is also forty-five minutes

> **Never use `h:mm:ss`.** `1:30:00` does not import as ninety minutes — it becomes
> **90 seconds**, silently, with no warning. For anything over an hour, use minutes:
> `90:00`.

A missing, blank, or unparseable duration becomes **5:00** rather than failing. That is
a silent default, so check the preview.

## A worked example

```csv
Session Title,Presenter,Duration,Notes
Registration & Coffee,,30:00,Foyer
Opening Keynote,Dr Amara Okafor,45:00,Autocue needed
Panel: The Year Ahead,Various,50:00,4 chairs on stage
Closing Remarks,Sam Whitfield,10:00,
```

The importer will map `Session Title → title`, `Presenter → speaker`,
`Duration → duration`, and `Notes → notes` on its own — all four headers are recognised.

There is no downloadable template in the app; copy the block above as your starting
point.

## Importing, step by step

1. Click **Import CSV** in the Controller header.
2. Choose your `.csv` file. It parses immediately.
3. Under **Map columns**, check each header's dropdown and correct anything mis-guessed.
4. Check **Preview** — it shows the **first 10 rows only**, with a "… and N more rows"
   note underneath.
5. Click **Import N timers**.
6. On success you will see "Imported N timers successfully." and be returned to the
   Controller after a moment.

## What the import does to your existing rundown

**Imported sessions are added to the end.** Nothing is replaced or overwritten, so
importing twice gives you two copies of everything.

Every imported session is created as a plain **Countdown** with no schedule. That means:

- No time-of-day start times, so the [Agenda](agenda.md) will show `—` for each row and
  **Schedule health** will report "no planned schedule".
- Nothing is flagged as a break — set that yourself afterwards by editing the session.
- Add start times afterwards via **Edit** on each session if you need drift tracking.

## Errors and gotchas

> **The importer never tells you a row was wrong.** There is no per-row validation and
> no row is ever skipped — every row in your file becomes a session. A blank title becomes
> an empty title (displayed as `Untitled`); a bad duration becomes 5:00. **The preview
> is your only check**, and it only shows the first 10 rows. For a long file, spot-check
> the rundown afterwards.

| Message                                                      | Cause                                                |
| ------------------------------------------------------------ | ---------------------------------------------------- |
| "File has N rows. Maximum is 500."                           | Too many rows. Split the file and import in batches. |
| "Import limited to 500 rows."                                | The same limit, enforced again server-side           |
| "Map at least one column to a timer field before importing." | Every column is set to `(ignore)`                    |
| A parser error naming a line                                 | Malformed CSV — usually an unescaped quote or comma  |

Tips for a clean file:

- Export as **CSV UTF-8** from Excel or Google Sheets.
- Make sure row 1 is your headers, not a title banner or a merged cell.
- Delete trailing empty rows — blank rows still import as `Untitled` sessions at 5:00.
- Quote any field containing a comma: `"Panel: Risk, Reward, and Regulation"`.

## Related

- [Controller](controller.md#build-your-rundown) — editing sessions after import, and
  adding schedule times
- [Agenda](agenda.md) — why imported sessions show no start times
