# Bulk upload posts from a CSV

Import up to 100 posts at once from a spreadsheet saved as CSV. Each row becomes a draft for the accounts of a posting group; you then check and schedule the drafts. The exact columns, values and every error message.

Last checked: Oct 8, 2026 on the live app. Source: https://heropost.ai/docs/posting/bulk-upload/

## How it works

Bulk upload turns a spreadsheet into posts. Each row of the file becomes a **draft** for every account in the [posting group](/docs/posting/posting-groups/) you choose, on the networks that can take that row. Heropost downloads the photos and videos from the links in the file.

Nothing is published straight away. After the import, you open the drafts, check them and schedule them. Bulk upload is part of every plan and doesn't use AI actions.

## Before you start

- **A posting group** with the accounts the posts are for. Create one under **Post Groups**: see [Posting groups](/docs/posting/posting-groups/). For several clients, make one group and one file per client.
- **A CSV file,** saved from Excel, Google Sheets or Numbers as **comma-separated** values (CSV UTF-8). Up to 100 posts per file.
- **Public links to your media.** Each link must open the file itself, without signing in. Test one in a private browser window.

## The columns

The first row must contain exactly these eight column names, spelled and capitalized as shown, separated by commas without spaces:

```text
Caption,Media,Title,Date,Link,Thumbnail,Type,Comment
```

Every row needs all eight columns. Leave a cell empty when you don't need it, but don't delete the column.

| Column | What to put in it |
|---|---|
| **Caption** | The text of the post. If it's empty, the **Title** is used as the text. |
| **Media** | Links to the photos or videos for this post: up to 10, separated by a vertical bar. See [Media links](#media-links). |
| **Title** | The post's name in Heropost (cut to 50 characters). If it's empty, the post is named after the start of the caption. When both **Title** and **Caption** are filled, the title is also used as the Pinterest Pin title. |
| **Date** | When the post should go out, month first, in the workspace's time zone. See [Dates](#dates). Empty or unreadable: the draft has no date. |
| **Link** | For **Link** posts: the web page to share. Networks show it as a preview card. |
| **Thumbnail** | Leave empty. It isn't used as a video cover, and if you fill it, the image is added to the post's media as an extra photo. Add covers in the editor after the import. |
| **Type** | The kind of post: `Image`, `Carousel`, `Video`, `Story` or `Link`. See [Types](#types). |
| **Comment** | A first comment, for the networks that support one. See [Add a first comment](/docs/posting/first-comment/). |

Press **Sample CSV** in the upload window for a working example. Here's another:

```csv
Caption,Media,Title,Date,Link,Thumbnail,Type,Comment
"New menu, new season!",https://example.com/photos/menu.jpg,Autumn menu,10/15/2026 10:00 AM,,,Image,Which dish do you try first?
Our summer in pictures,https://example.com/a.jpg|https://example.com/b.jpg|https://example.com/c.jpg,Summer carousel,10/16/2026 14:30,,,Carousel,
Behind the scenes,https://example.com/videos/bts.mp4,BTS video,10/17/2026 9:00 AM,,,Video,
Read our latest blog post,,Blog promo,10/18/2026 11:00 AM,https://example.com/blog/post,,Link,
```

**Text with a comma, a quotation mark or a line break must be wrapped in double quotes,** like the first caption above. Spreadsheet apps do this for you when they save as CSV.

### Types

| Type | What the post gets | Networks that take it |
|---|---|---|
| `Image` | One photo: the first photo link | Facebook, Instagram, X, LinkedIn, Pinterest, Reddit, Telegram, Threads, Bluesky, Google Business Profile |
| `Carousel`, or empty | Several photos, and on some networks videos (table below) | Every network above |
| `Video` | One video: the first video link | Facebook, Instagram, X, LinkedIn, Pinterest, Reddit, Telegram, Threads, YouTube, TikTok |
| `Story` | A Story with the first photo | Facebook, Instagram |
| `Link` | A link post with a preview card. Needs a **Link**. | Facebook, LinkedIn, Reddit, Telegram, Threads, Bluesky |

- Letter case doesn't matter: `video` works too. Any other value, such as `Text`, is read as `Carousel`.
- **Every row needs media, except Link rows.** There's no text-only type.
- Networks that can't take a row are left out of that post. If none of the group's networks can, the row isn't imported.

What each network takes from a **Carousel** row:

| Network | Takes |
|---|---|
| Facebook | All the photos; videos are left out |
| Instagram | All the photos and videos, up to 10 |
| X | The first 4 photos |
| LinkedIn | The first 9 photos |
| Pinterest | Up to 5 photos the same size as the first one |
| Reddit | All the photos |
| Telegram, Threads | All the photos and videos |
| Bluesky | The first 4 photos |
| Google Business Profile | The first photo |
| YouTube, TikTok | Nothing: give them a `Video` row |

### Media links

- **Separate several links with a vertical bar,** for example `https://example.com/1.jpg|https://example.com/2.jpg`. Up to 10 links per row; any more are ignored.
- **Use direct links to the files,** ideally ending in the file name, such as `.jpg` or `.mp4`. Heropost reads the ending to tell photos from videos. For a link without one, it asks the website what the file is.
- **Share pages don't work.** A Google Drive, Dropbox or OneDrive page that shows the file, or asks you to sign in, isn't the file itself. Use a direct-download link (on Dropbox, a link ending in `dl=1`), or host the files on your own website.
- **Sizes:** photos up to 20 MB and videos up to 5 GB per link. Each network's own limits apply when you schedule: see [Character limits and media specs](/docs/posting/specs/).
- **Formats:** JPG or PNG photos and MP4 or MOV videos work everywhere. Other formats may import but be refused by the networks when you schedule.

### Dates

Write dates month first, then the time, in one of these forms:

| Form | Example |
|---|---|
| Month/day/year, 12-hour clock | `10/5/2026 9:30 AM` or `10/05/2026 09:30 PM` |
| Month/day/year, 24-hour clock | `10/5/2026 14:30` or `10/05/2026 14:30` |

- Times are in the workspace's time zone.
- A date in any other form (day first, a 2-digit year, seconds, a month name) isn't read, and the draft has no date. Spreadsheet apps sometimes change dates when they save: open the CSV in a text editor to check.
- A date doesn't schedule the post. It only fills in the draft's **Schedule** field.

## Upload the file

1. Open the post editor (**Content → Create Post**).
2. Click the arrow next to **New Post**, then **Bulk Upload (CSV)**.
3. Under **Posting Group**, choose the group the posts are for.
4. Press **Choose CSV File** and pick your file. Its name appears, with **Clear** to pick another.
5. Press **Start Bulk Upload**.
6. Wait. The window shows "Uploading file...", then "Processing bulk upload...". Each media link is downloaded, so this can take a few minutes.
7. When it's done, Heropost shows "Bulk upload completed successfully!", the window closes, and the drafts appear in the editor's list.

[Screenshot: The Bulk Upload (CSV) window with a posting group chosen, posts.csv selected, the Sample CSV and See Media Requirements links, and Start Bulk Upload]

[Screenshot: The Bulk Upload window while it works, showing Processing bulk upload... and a Processing... button]

- **See Media Requirements** in the window reminds you of the networks' file limits.
- Once the window shows "Processing bulk upload...", the import keeps running even if you close the window or the browser. Check **Content → Drafts** before uploading the same file again: **each upload creates a new set of drafts.**

## After the upload: check and schedule the drafts

**Imported posts are drafts. They won't be published on their dates until you schedule them.** Drafts don't show on the calendar.
1. Open **Content → Drafts**, or the **Drafts** list in the post editor. The newest drafts are at the top.
2. Open a draft. Check the text, the media and the date on each network's tab, and the preview.
3. Fill in what the file couldn't: a **Title** on the YouTube tab, and **Who can watch this video** on the TikTok tab. Threads and Telegram link posts also need a caption.
4. Press **Schedule All**. If the date has passed, pick a new one first.
5. Repeat for each draft.

[Screenshot: Imported drafts in the editor's Drafts list, one open with its date filled in and Schedule All at the bottom]

**To schedule many at once,** you can ask Hero_Agent on Home, for example: "Schedule my drafts from today's import at their saved dates." It lists them and asks for your approval before anything is scheduled. Scheduling doesn't use AI actions.

## If it doesn't work

When a row can't be imported, the window shows a message starting with "Row {N}:" (row 1 is the first row under the column names). **Only the first problem is shown, and the other rows are still imported**: check **Content → Drafts** to see which made it, then fix the row and upload a file with only the missing rows.

| Message | What it means | What to do |
|---|---|---|
| "Please select a valid CSV file" | The file doesn't end in .csv. | Save it as CSV and choose it again. |
| "The first row of your CSV needs exactly these column names: Caption, Media, Title, Date, Link, Thumbnail, Type, Comment." (may add "Missing: …" and "Your file has: …") | The column names are wrong, missing or misspelled. Often a file exported from another tool. | Make the first row exactly `Caption,Media,Title,Date,Link,Thumbnail,Type,Comment`. Rename your columns, for example Message or Text → Caption, Media URL → Media. |
| "Your CSV separates its columns with semicolons; they have to be separated by commas. …" | Spreadsheets set to some regions save CSVs with semicolons. | Save it again as comma-separated (CSV UTF-8). In Google Sheets, use File → Download → Comma Separated Values (.csv). |
| "Row {N} of your CSV has fewer columns than the first row. Every row needs all eight columns: leave a cell empty rather than leaving it out." | A row has fewer cells than the header. Here the column-name row counts as row 1. | Add the missing empty cells. |
| "Row {N} of your CSV could not be read. Text that contains a comma, a quotation mark or a line break has to be wrapped in double quotes." | Text in that row breaks the CSV format. Here the column-name row counts as row 1. | Wrap that cell in double quotes, or save the file again from your spreadsheet app. |
| "Your CSV could not be read at row {N}." | Heropost couldn't read the file from that row on. | Check that row, or start again from the **Sample CSV**. |
| "Row {N}: No accounts available in posting group" | The posting group has no accounts. | Add accounts to the group, then upload again. |
| "Row {N}: No network in the posting group ({networks}) can publish this row as a {Type} post with {n} video(s) and {n} photo(s). …" | None of the group's networks can take that row as written. | Check **Type** (`Video` for YouTube and TikTok), add media (only Link rows can have none), and check that media links end in the file name. Or choose a group with networks that fit. |
| "Row {N}: Could not tell whether this media link is a photo or a video: the address has no file extension and …" | The link doesn't end in a file name, and the website didn't say it's a photo or video. Often a share page. | Use a direct link that ends in the file name, such as .jpg or .mp4. |
| "Row {N}: The media link did not answer within 20 seconds, so it could not be checked ({link})." | The website hosting the file didn't respond. | Check the link in your browser, or host the file elsewhere. |
| "Row {N}: The file is too large" | A linked photo is over 20 MB, or a video over 5 GB. | Use a smaller file. |
| "Row {N}: Response status code does not indicate success: 401 (Unauthorized)." | The media link needs a sign-in. | Use a public link that opens without signing in. |
| "Failed to start bulk upload" | The upload couldn't start. | Try again. |
| "An unexpected error occurred while processing your request." when you start | Often the posting group was deleted or belongs to another workspace. | Close the window, reload the page, choose the group again and retry. |
| "Failed to check bulk upload status" or "Bulk upload polling timeout" | Heropost couldn't confirm the result, but the import may have finished. | Check **Content → Drafts** before uploading again. |
| "Bulk upload completed successfully!", but there are fewer drafts than rows | The file has more than 100 rows: the rest were skipped. Empty rows count toward the 100. | Split the file into files of up to 100 rows. |
| The drafts have no date | The **Date** column isn't in a form Heropost reads. | Fix the dates (month first), or set them in each draft. |
| "Unable to post because…" or a red tab when you schedule a draft | The imported media or text doesn't fit a network. | Fix it on that tab. See [Every error message, explained](/docs/troubleshooting/error-index/). |

## Questions

**Will the posts go out on the dates in my file?**

Only after you schedule them. The import creates drafts with those dates filled in; press **Schedule All** on each one.


**Can I import an Excel file?**

Not directly. In Excel, use Save As and choose the CSV UTF-8 (Comma delimited) format, then upload that file.


**I'm moving from another scheduling tool. Can I import its export?**

Yes, once its columns are renamed to the eight above, and its dates are rewritten month first. A column naming the network or account isn't used: the posting group decides where each post goes.


**Can different rows go to different accounts?**

Not in one file: every row goes to the same posting group. Make one file per group and upload them one after the other.


**Can I import text-only posts?**

No. Every row needs a photo or video, except `Link` rows, which need a **Link**. For text-only posts, use the post editor, or ask Hero_Agent.


**Can Hero_Agent read my spreadsheet instead?**

Yes. On Home, Hero_Agent can take a CSV file and write and plan the posts for you. Each post it creates uses one AI action, while bulk upload uses none. See [Let Hero_Agent plan your first week](/docs/getting-started/first-week-with-hero-agent/).
