How to Schedule Social Media Posts From a CSV

How to Schedule Social Media Posts From a CSV

If you plan content in a spreadsheet, you should not be retyping it into a composer. Here is the column format, what happens to bad rows, and the limits worth knowing.

Plenty of people plan content in a spreadsheet. It’s a good place to do it: you can see the whole month, sort it, share it with someone for review, and edit ten posts faster than any composer will let you.

The waste is what happens next, when all of it gets retyped into a scheduling tool one post at a time. CSV import removes that step.

The format

BulkPublish’s bulk composer reads a CSV with a header row and three recognised columns:

ColumnRequiredContents
contentYesThe post text
scheduled_atNoPublish date and time, e.g. 2026-11-03T09:00
media_urlsNoImage or video URLs to attach, several separated by ;

A minimal file is one column and however many rows you have:

content
"Big news: our spring update is live."
"Behind the scenes: how we plan a week of content in 20 minutes."

A fuller one uses all three:

content,scheduled_at,media_urls
"Big news: our spring update is live.",2026-11-03T09:00,https://example.com/spring.jpg
"How we plan a week of content in 20 minutes.",2026-11-03T13:00,https://example.com/planning.jpg

You can download a template with the correct header from the composer, which saves guessing at the column names.

What happens to imperfect rows

Two behaviours worth knowing before you import a hundred rows.

A missing content column stops the import. You get told the file needs a content column rather than an import that silently brings in nothing.

Rows with neither content nor media are skipped. Trailing blank lines and spacer rows, which every real spreadsheet has, don’t turn into empty posts.

Everything else imports and lands in an editable list, so a row that’s slightly wrong is something you fix in the composer rather than something you fix in Excel and re-import.

Quoting, which is where CSVs actually break

Nearly every CSV problem is a quoting problem.

  • If your text contains a comma, wrap the field in double quotes
  • If your text contains a double quote, double it and quote the whole field: """like this"""
  • If your text contains a line break, quote the field. It’ll still parse
  • Save as UTF-8 so emoji and accented characters survive

Exporting from Google Sheets or Excel as CSV handles all of this. Hand-editing a CSV in a text editor is where it goes wrong.

What CSV import doesn’t decide

The file carries text, timing and media. It doesn’t carry which channels each post goes to, or per-platform variations of the text.

That’s deliberate. Channel selection is the thing most likely to differ per post and the thing you least want to get wrong in bulk, so it’s set in the composer after import, where you can see the posts. You can apply channels across the batch and then override individual posts.

After the import

The imported posts join whatever’s already in the composer as an editable list. From there you can:

  • Edit any post’s text
  • Change or set its scheduled time
  • Override which channels it goes to
  • Add media that wasn’t in the file
  • Delete rows you’ve changed your mind about

Then you queue the batch. Your posting quota is checked across the whole batch before anything is scheduled, so an import larger than your plan allows tells you before it publishes anything rather than halfway through.

Plan limits worth checking first

  • Channels: Free: 3 (1 per platform). Pro: 30 (2 per platform). Business: 75 (5 per platform).
  • Posts: Free: 3/day. Pro: 30/day. Business: unlimited.
  • Storage: Free: 100 MB. Pro: 2 GB. Business: 10 GB.

If you’re importing a month of daily posts, the daily posting limit is the one to check against, not the size of the file.

The short version

Keep planning in the spreadsheet. Export it with a content column, optionally scheduled_at and media_urls, quote anything containing a comma, save as UTF-8, and import. Set channels afterwards in the composer, where you can actually see what you’re assigning.