Add Row Numbers to a CSV

A new column counting the rows, put at the front of the file or the end. Set the name, where the count starts and how far it steps, zero-pad it so it sorts correctly as text, and restart it for each order, customer or category if you want line numbers within a group. It runs in your browser, so nothing is uploaded.

Repairing numbers a spreadsheet already flattened? Pad the existing column instead.

Why a file needs a number on every row

A CSV has an order and no way to talk about it. Rows are identified by their position and position is not written down anywhere, so the moment the file passes through anything that sorts, filters, joins or groups, the original sequence is gone and cannot be reconstructed. That matters more often than it sounds. A reviewer wants to say "the problem is on row 4,812". A round trip through a database needs to come back in the order it went in. A diff between two versions of an export needs a stable handle for each line.

Writing the position into a column fixes all of that, and it is one of the few transformations that is genuinely lossless: nothing is changed, nothing is removed, one column is added and every other value passes through exactly as it was.

The counting itself is trivial. The two options on this page are what stop it being a five-second job you still have to think about: padding, so the numbers survive being treated as text, and grouping, so the number means something within the row rather than only within the file.

Worked example: line numbers inside each order

Five order lines belonging to two orders:

order_id,product
1001,Keyboard
1001,Cable
1002,Monitor
1002,Mouse
1002,Dock

Set Column name to line_no, Pad to to 3, and Restart for each to order_id. Leave Start at, Step by and Where it goes alone:

line_no,order_id,product
001,1001,Keyboard
002,1001,Cable
001,1002,Monitor
002,1002,Mouse
003,1002,Dock

And the strip above it:

5 rows · numbered into "line_no" · from 1 · added at the front
padded to 3 digits · restarting for each of 2 values of "order_id"

Order 1001 has lines 001 and 002; order 1002 starts again at 001 and runs to 003. That pairing of order_id and line_no is now a composite key, unique across the file, which is what most order systems want and what a spreadsheet formula involving COUNTIF over an expanding range is usually being asked to produce. The count of distinct groups is in the summary too, so you can sanity-check it against the number of orders you expected.

010 before 9 is the bug

A CSV carries no types. Everything in it is characters, and anything that sorts a CSV column without being told otherwise compares those characters one at a time. That produces an order almost nobody wants:

unpadded, sorted as text     padded to 3, sorted as text
1                            001
10                           002
11                           009
2                            010
9                            011

Pad to takes a digit count and zero-fills every number to that width, which makes the character comparison agree with the numeric one. Choosing the width is the only judgment call: it needs to be at least as wide as your largest number. Set it too small and the column ends up with two widths in it, which sorts exactly as badly as no padding at all, so a warning appears above the result when any number came out longer than the padding you asked for. Twelve thousand rows want five digits; leave a little headroom if the file will grow.

Padding also makes a numbered column line up in a terminal, a plain text dump or a fixed-width export, which is a smaller benefit but a real one when you are reading the file with your eyes rather than with a program.

Grouped numbering, and how it goes wrong

Restart for each keeps a separate counter for every distinct value it sees in the column you choose. The first row of a group gets the starting value, the next row of that same group gets the starting value plus the step, and so on. Because the counters are held per value rather than per run of rows, the file does not have to be sorted for the numbering to be correct. It does have to be sorted for the numbering to look sensible when you scroll through it, so sorting first is usually worth the extra step.

There is one failure mode and it is common enough to be worth naming. If you group on a column whose values are all different, such as an id that is already unique, then every row is its own group and every row is numbered with the starting value. You get a column of ones. The tool notices this exact case and says so in a warning that names the column, rather than handing you a file that looks fine until somebody uses it.

The other thing to know is that grouping compares the cell values literally. Acme and Acme  with a trailing space are two groups, and so are acme and Acme. If the grouping column came out of a system where people typed the values, run it through whitespace trimming and a case change first, then number it.

Things the start and step boxes are good for

  • Continuing a sequence. A second batch appended to an existing file should not start at 1 again. Set Start at to the number after the last one already used.
  • Leaving room between rows. A step of 10 gives 10, 20, 30, which lets somebody insert a row later without renumbering the file. Old-fashioned, still useful.
  • Matching a system that counts from zero. Set Start at to 0 when the file is feeding something that indexes from zero and you would rather not adjust it downstream.
  • Counting down. A negative step numbers the file in reverse, which is occasionally what a ranked list wants.

Both boxes accept a plain whole number and quietly ignore commas, spaces and underscores in what you type, so pasting 1,000 works. A step of 0 becomes 1, since a column where every row holds the same number is not a row number by any definition.

Practical notes

  • Nothing else in the file changes. No rows are added or removed, no values are rewritten, and the column order is otherwise preserved. This is the least destructive tool on the site.
  • The header row is not counted. Numbering starts on the first data row, so row 1 is the first real record rather than the header.
  • Blank rows are already gone. Rows with nothing in any cell are dropped when the file is read, and you are told how many, so they do not consume numbers.
  • A name collision is handled, not ignored. Adding row_number to a file that already has one gives you row_number_2.
  • Padding is capped at 20 digits, which is more than any real file needs and stops a typo from producing a column of very long strings.
  • The download is a separate file. orders.csv comes back as orders-numbered.csv.

Frequently Asked Questions

Why would I pad the row numbers with zeros?

Because a CSV column is text, and a text sort compares character by character. Unpadded, 10 lands between 1 and 2 and 9 comes last. Pad to three digits and 001, 002, 009, 010 sort in the order a person expects. Pad to only as many digits as your largest number needs, since a column with two widths in it sorts no better than an unpadded one.

What does Restart for each do?

It numbers within a group instead of across the whole file. Pick order_id and the count goes back to the start every time the order changes, so a sorted export turns into line 1, 2, 3 of this order without a spreadsheet formula. The counter is keyed on the exact cell value, so a stray space makes a separate group.

My grouped numbering came out as 1 on every row. What went wrong?

Every row has a different value in the column you grouped on, so every row starts a new group and every group has exactly one member. A warning above the result says so by name. Pick a column with repeats in it, such as the order or customer a line belongs to, or set Restart for each back to Never restart.

Does the file have to be sorted before I group?

It does not have to be, and the numbering is still correct either way, but it will read strangely if it is not. The counter remembers each value it has seen, so a group whose rows are scattered through the file gets 1, 2, 3 in the order those rows appear. Sort by the group column first if you want the numbers to run consecutively down the page.

Can the numbers start somewhere other than 1?

Yes. Start at takes any whole number, including a negative one, and Step by controls the interval, so 100 and 10 give you 100, 110, 120. A step of 0 is treated as 1, because a column of identical numbers is not a row number. Both boxes ignore commas and spaces in what you type.

Where does the new column go?

At the front by default, which is where a row number belongs and where most readers look for it. Where it goes can be switched to At the end when you are appending to a file whose column order is fixed by something downstream.

What if a column with that name already exists?

Nothing is overwritten. A file that already has a row_number column gets the new one called row_number_2, following the same convention the reader uses for duplicate headers. Type something distinctive into Column name if you would rather choose.

Is my file uploaded?

No. The rows are read, counted and written back inside your own browser tab, with no server involved. Every other column passes through untouched, and the download is named after your file with -numbered on the end so the original is never overwritten.

Give every row a name

Free, no account, no upload. Set the width, pick a group, take the CSV.

Back to the numberer