CSV → SAV

CSV to SAV converter, online: you decide what each column is.

Drop a .csv and the SPSS dictionary comes back as a table you can edit: variable names, types, measure levels, labels and missing-value codes. Then it writes the .sav.

In short.

  • Converts CSV, TSV and semicolon files to a real SPSS .sav, in this tab, with no SPSS licence.
  • Shows the dictionary before writing, so a renamed variable or a column typed wrong is something you fix, not something you discover later.
  • Declares 999-style missing codes and sets measure levels, the two things a CSV cannot carry and other converters do not offer.
  • Nothing is uploaded. The output is checked against both a from-scratch JavaScript reader and pyreadstat, the binding for the library underneath R's haven.

The dictionary is the hard part

Going from a .sav to a CSV throws information away, so it always succeeds. Going the other way has to invent that information back, and there is no way to do it from the file alone. A CSV is a grid of text. An SPSS dataset is a grid of text plus a dictionary: what each variable is called, what it is called in plain English, whether it is a number or a string or a date, how wide, how many decimals, whether it is nominal or ordinal or scale, which codes mean the answer is missing, and what the numbers stand for.

None of that is in the CSV, which is what any CSV to SAV converter online has to reckon with. Something has to decide it, and the usual something is the SPSS Text Import Wizard deciding silently while you click Next. That is where imports go wrong, and every failure people report comes back to a decision made for them without being shown. So this page makes the same decisions and then stops, puts them in a table, and waits.

What SPSS rejects in a variable name

If you have ever seen "invalid variable names for this application have been found and changed" and pressed OK because there was nothing else to press, this is what it was doing. A CSV header is written for a human reader. An SPSS variable name is an identifier in a command language, so it has the constraints of one.

RuleA header that breaks itWhat it becomes
No spaces or punctuationQ1. How satisfied?Q1._How_satisfied
Cannot start with a digit2024 spendV2024_spend
At most 64 bytesa 90-character question textcut at the 64th byte
Not a reserved syntax wordALLALL_
Unique, ignoring caseAge and AGE in one fileAge and AGE_2

The original header is not thrown away when a name changes. It is kept as the variable label, which is the field SPSS shows in output and in the Variable View, so "Q1. How satisfied?" survives intact as the human-readable description while the name becomes something the syntax can refer to. The R package haven surfaces the same rules as Writing failure: A provided name contains an illegal character and Writing failure: A provided name is a reserved word, and its issue tracker has people describing those errors as cryptic enough to send them looking in the wrong place entirely.

Missing codes and measure levels

These two are the reason a converter that only moves cells around is not enough, and they are the two things no other CSV to SAV tool currently offers.

Missing-value codes

Survey data codes "no answer" as a number: 999, -99, -9, depending on the shop. SPSS defaults every imported variable to having no missing values, so those codes come in as data and are averaged along with everything else. The manual fix is Variable View, one variable at a time. A Stack Overflow poster importing a large dataset described declaring 999 across "hundreds of variables" as "a lot of work", and an IBM forum thread from 2024 asked the same thing about -9. The answer there, from an SPSS veteran, is worth keeping: missing values are not coded automatically, and have to be declared unless the data source already did it.

This page scans numeric columns for the conventional codes and offers to declare them. The declaration goes into the file as real SPSS discrete missing values, so it is SPSS itself excluding them, not a recode you have to remember to run.

Measure level

SPSS marks everything numeric as scale on import. A gender coded 1 and 2 is then a scale variable, which is how a mean of 1.47 ends up in someone's output and how the chart builder offers a histogram of it. The guess here is made from how many distinct values a column holds relative to its length: two values is nominal, a handful is ordinal, a spread is scale. It is a guess, which is why it is a dropdown rather than a decision.

Decimal commas, dates and encodings

A file exported in Germany writes 1.234,50 where a file exported in the United States writes 1,234.50, and read the wrong way round each becomes a different number or stops being a number at all. IBM has a support note about the Text Import Wizard appearing to ignore the comma and a formal defect record, APAR PI99128, for the same thing. The convention is detected across the whole file rather than per column, and shown in the summary row so you can see what it decided.

Dates get their own treatment because a date that arrives as a string is not a date. Set the column type to Date and the value is written as an SPSS date variable rather than as text that looks like one. The five shapes recognised cover ISO, both slash conventions and the dotted European form; anything stranger stays text rather than being guessed at. Text is written as UTF-8 and the file declares that it is, so accented and non-Latin characters arrive intact instead of as the mojibake people report after routing survey data through a CSV.

Many files, and the same file next month

A survey that runs in waves exports the same columns every month, and the decisions about those columns are the same every month with them. Doing them again by hand each wave is the real cost of this conversion, not the conversion itself. On the IBM forum the standing answer to converting many files is to install the SPSSINC PROCESS FILES extension from the Extension Hub, write a separate syntax file, and build the output names with a macro.

Here you pick several files at once, which is the one thing a CSV to SAV converter online can do that a syntax file makes tedious. The first file produces the table above, and the rest go through that same dictionary into one zip, so two waves of a survey cannot end up disagreeing about whether a column is nominal or scale. A file whose header row does not match column for column is listed and skipped rather than converted with a dictionary that does not describe it: sharing one dictionary is the point of the feature and also the way it could quietly mislabel a column, so the mismatch is shown by name.

You can also save the dictionary and apply it to a file you upload weeks later. Matching is on the CSV header text, and applying a saved dictionary reports what did not line up: a column the saved dictionary has never seen keeps the inference from its own data instead of inheriting the settings of whatever column happened to be in that position. Saved dictionaries are held in this browser only, which follows from nothing being uploaded: they do not travel to another machine and they go when you clear site data.

How this online CSV to SAV converter compares

Checked against the other converters that came up for this search in August 2026. The row that matters most depends on who you are: the size limit if your dataset is large, the processing location if your ethics approval says the data cannot leave your machine, and the bottom three rows if you were going to fix them by hand in Variable View afterwards.

CapabilityHereTypical online converter
Where the file is processedIn this browser tabMost upload to a server first
Size limitYour machine, not a quota100KB to 50MB depending on the tool
Variable and value labelsEditable in a tableSometimes, via a hand-written JSON sidecar
Measure level (nominal, ordinal, scale)Inferred and editableNot offered
Missing-value codesDetected and declaredNot offered
Decimal comma and semicolon filesDetected per fileNot mentioned
Several files at onceOne dictionary, one zipEither batch or metadata, not both
Reusing a dictionary next monthSaved in this browserRe-entered each time

Best for: researchers and analysts handing a dataset to someone who works in SPSS, especially where an ethics approval or a data-processing agreement makes uploading the file to a third party a problem. Not ideal for: anyone who needs .zsav or .por output, files that must keep strings longer than 255 characters in one variable, or a scripted pipeline running unattended. For that last one pyreadstat is the better tool and there is no reason to put a browser in the loop.

How the output is checked

A file that opens and is quietly wrong is worse than one that refuses to open, so the checks are run by software that had no hand in producing these bytes. Two suites ship with the site. One reads the output back with a JavaScript SPSS reader written from scratch and checks the variable names, labels, types, measure levels, value labels, missing-value declarations, dates, wide strings and non-ASCII text. The other reads the same output with pyreadstat, the Python binding for the ReadStat C library that also sits underneath R's haven, so the file is confirmed against the two ecosystems researchers actually open .sav files in.

The second suite also has ReadStat write a file and then compares the two dictionaries record by record, which is a different question from whether a file round-trips. That comparison is what turned up an early bug where a variable called Age came back as AGE: the eight-byte name slot SPSS has kept since version 12 is uppercase, and the long-name record was only being written when a name exceeded eight bytes rather than whenever the slot failed to reproduce it exactly. A round trip could not see it, because the reader was faithfully returning the wrong name that had been written.

What has not been done is opening these files in IBM SPSS itself, which is not something a test suite here can do. The format is implemented from the GNU PSPP documentation and cross-checked against ReadStat, and both of those are themselves reverse engineered, since IBM publishes no specification. If a file from this page ever fails to open in SPSS, that is worth reporting.


Going the other direction

Everything else here reads .sav files rather than writing them.

Never opened one of these before? How to open a .sav file without SPSS covers five free ways to read one.


Questions

How do I convert a CSV to SAV without SPSS?

Open the CSV here and press build. The page reads the file in this browser tab, writes the SPSS system file format directly, and hands you the .sav. No SPSS licence is involved at any point, because nothing about the format requires IBM software to produce it. The bytes are a documented binary layout: a 176-byte header, one record per variable, records for value labels and measure levels, then the data. What you cannot do is have Excel or Google Sheets save a .sav; neither has an export filter for it, which is why the question keeps getting asked.

Why does SPSS say "invalid variable names have been found and changed"?

Because a CSV header is written for people and an SPSS variable name has rules: no spaces or punctuation, cannot start with a digit, at most 64 bytes, and cannot be one of the words SPSS reserves for its syntax (ALL, AND, BY, EQ, GE, GT, LE, LT, NE, NOT, OR, TO, WITH). When the Text Import Wizard hits one it renames it and moves on, and you find out later that "Q1. How satisfied?" is now something else. This page applies the same rules but shows you the result in a table with the reason next to each change, and lets you type a different name if you do not like the one it picked.

How do I set 999 as a missing value instead of a real number?

Leave the missing-value toggle on before you build. SPSS defaults every imported variable to "no missing values", so a survey coded with 999 for "no answer" arrives with 999 as ordinary data and it turns up inside every mean you compute. The usual fix is opening Variable View and declaring the code on each variable in turn, which is why someone on Stack Overflow described doing it across "hundreds of variables" as "a lot of work". Here the review table flags columns containing the conventional codes (-99, -98, -9, 99, 999, 9999) and writes them into the file as declared discrete missing values, so SPSS excludes them from the start.

Why did my text column import into SPSS as numeric and lose its data?

Because the wizard decides a column type from the first rows it looks at. A column that is blank for the first several cases and only has text further down reads as numeric, and once it is numeric the text has nowhere to go, so it is dropped. Someone on the IBM SPSS community forum described exactly this across a 1,200-variable dataset. The review table on this page shows the inferred type for every column with sample values beside it, and the type is a dropdown, so a column that should be text stays text.

How do I get a CSV date column to arrive as an SPSS date?

Set the column type to Date in the table and the value is written as an SPSS date variable, stored the way SPSS stores dates (seconds since 14 October 1582) with a DATE11 display format. Recognised shapes are YYYY-MM-DD, DD-MM-YYYY, DD.MM.YYYY, MM/DD/YYYY and an ISO timestamp. The alternative route is what one Stack Overflow poster hit: import the column as a string, discover the Date and Time Wizard has no option matching the format, and go looking for syntax to convert it after the fact.

What happens to a decimal comma?

It is detected and read correctly. A file written in a locale that uses "1.234,50" is checked across the whole file rather than column by column, because a European export is comma throughout and judging each column separately produces a file where half the numbers quietly became text. IBM has its own support note about the Text Import Wizard appearing to ignore the comma, and a defect record, APAR PI99128, titled "problems when uploading CSV file with comma as decimal separator". The detected convention is shown in the summary row above the table so you can see which way it read your file.

Does the CSV get uploaded anywhere?

No. The file is read in this tab with the browser file API, the dictionary is built in memory, and the .sav is assembled and handed to the download as bytes that never touch a network. You can watch that in the network panel of developer tools, and the page keeps working with the connection off once it has loaded. This matters more here than on most converters: the files people convert to .sav are survey responses and patient records covered by ethics approvals that often prohibit sending data to a third party at all.

Can I do this in Python or R instead?

Yes, and if you are already scripting, pyreadstat in Python is the sound choice, because it is the same ReadStat library that sits underneath R's haven and this page's output is checked against it. The R route through foreign::write.foreign() is rougher: its own documentation and user reports note that numeric value labels stored via attr() are lost, that it stops on any string longer than 255 characters even where SPSS allows more, and that non-ASCII characters such as umlauts break the import. Scripting also assumes you already know the dictionary you want. The table on this page is for the part before that, where you are still deciding what each column is.