# Bulk upload

> Schedule many posts to one channel at once from a CSV file, check every row before anything is scheduled, and undo an import.

Bulk upload turns a spreadsheet into scheduled posts on one channel. You see how every row will be posted (its time,
its tags, its image, anything wrong with it) before anything is scheduled, and an import you regret can be undone.

## Upload a file

1. Go to **Settings**, choose the channel under **Platforms**, and select **Bulk upload**.
2. Choose **Download template** for a file that already has the right columns, or use your own. Add one row per post,
   up to 200 rows.
3. Drop the file into the dialog. Every row is checked and listed with its posting time, tags and image, and is marked
   **ready**, **with warnings** or **with errors**.
4. Fix what needs fixing: **Edit** a row to change its text, time or image link, or remove it. Then choose
   **Import**. Rows with errors are skipped and the rest are scheduled.

## The columns

| Column         |          | What goes in it                                                                                   |
| -------------- | -------- | ------------------------------------------------------------------------------------------------- |
| `text`         | Required | The post. Emoji and line breaks are kept. It must fit the network's character limit.              |
| `image_url`    | Optional | A direct link to a JPEG, PNG, GIF or WebP image, up to 10 MB. Separate several links with spaces. |
| `tags`         | Optional | Tags that already exist in the workspace, separated by commas.                                    |
| `posting_time` | Optional | `YYYY-MM-DD HH:MM` in 24-hour time, for example `2026-11-05 17:30`.                               |

Column names are matched loosely, so a file with headers like `Text`, `Image URL` and `Posting Time` works too. Files
saved as CSV UTF-8 or UTF-16 (Excel's "Unicode Text") are both read, and so are files that use semicolons between
columns.

### Posting times

Times are read in the channel's posting timezone, which the check step names. A row without a time takes the next
free slot in the channel's [posting schedule](https://orbitwrite.com/docs/guides/channels/posting-schedule), after everything already
booked, in the order the rows appear. Choose **Save as drafts** instead to leave those rows unscheduled.

A time in the past, a date without a time, and a date that could be read two ways (is `05/11/2026` 5 November or
11 May?) are errors rather than guesses. Write dates as `YYYY-MM-DD` to avoid the last one.

### Images

The image is copied into your media library when you import, so the link only has to work while you import. It has
to open the image itself: a link to a web page, or a cloud-drive preview, is an error. Right-click the image and
choose **Copy image address** to get a direct link.

### Tags

Tags are matched to the workspace's own regardless of capitals. A tag that doesn't exist is left off the post with
a warning, and the post is still imported. Create new tags in **Settings → Tags** first.

> **Note:** On a plan with approvals, posts imported by someone who can't publish wait for approval before they go out, exactly
> as they would from the composer.

## Undo an import

The dialog offers **Undo import** as soon as the import finishes, and the channel's **Bulk upload** section lists
recent imports with an **Undo** button. Undoing deletes every post from that import that hasn't gone out yet.
Anything already published stays where it is.

## From the API, CLI and MCP

The same import is available everywhere:

- **API**: `POST /api/v1/posts/import` with `dryRun: true` to check rows, then without it to import, and
  `POST /api/v1/posts/imports/{importId}/undo` to undo. See the [API reference](https://orbitwrite.com/docs/api-reference/posts).
- **CLI**: `orbitwrite import posts.csv -c <channel> --dry-run`, then without `--dry-run`. See the
  [CLI guide](https://orbitwrite.com/docs/developers/cli).
- **SDK**: `orbit.imports.check()`, `orbit.imports.create()` and `orbit.imports.undo()`. See the
  [SDK guide](https://orbitwrite.com/docs/developers/sdk).
- **MCP**: the `import_posts` and `undo_post_import` tools.
