SQLite BOOLEAN: there is no boolean type
SQLite stores booleans as integers 0 and 1. A BOOLEAN column works, but it is INTEGER underneath and will accept anything.
Quick answer
SQLite has no boolean type. Booleans are integers: 1 is true, 0 is false.
You can write BOOLEAN in a CREATE TABLE and SQLite will accept it, but it does not create a boolean type. The section below shows what it actually does.
What BOOLEAN actually does
Declare a column as BOOLEAN and it works, which is exactly why this is confusing:
The values came back as 1 and 0, not TRUE and FALSE. SQLite converted them on the way in.
The declared type is remembered, though:
So the schema says BOOLEAN. But the values are not:
This is SQLite's type affinity system. The declared type is a hint that determines affinity, not a constraint that determines what can be stored. BOOLEAN contains no recognized affinity keyword, so it gets NUMERIC affinity, and SQLite stores whatever fits.
Which means a BOOLEAN column will take this without complaint:
A string, in your boolean column. No error. If you want that prevented, you have to say so yourself, with a CHECK constraint.
TRUE and FALSE keywords
TRUE and FALSE are recognized and are simply aliases for 1 and 0:
These were added in SQLite 3.23 (2018). Any SQLite you are likely to be running has them. If you are targeting something ancient or embedded, 1 and 0 are always safe.
Note that a comparison returns 1 or 0 too. There is no separate boolean result type anywhere in SQLite.
The 'true' string trap
This is the one that causes real bugs, usually when data arrives from JSON, a CSV import, or a language that stringifies loosely.
Read that carefully. The string 'true' is falsy.
Not because SQLite is being perverse, but because it applies numeric coercion: a text value that does not look like a number converts to 0, and 0 is false. So 'true' and 'false' are both false, and a query like WHERE is_active silently returns nothing for every row your importer wrote as a string.
Nothing errors. Nothing warns. The rows just quietly stop matching.
Guard against it at the schema level:
That turns a silent wrong answer into a loud failure at the point of insert, which is where you want it.
Querying booleans
Because they are integers, all of these work:
Counting is where the integer representation is actively convenient:
sum() over a 0/1 column counts the true rows. That is not a trick, it is just what the data is.
NULL is not false
A nullable boolean has three states, and NULL is not one of the two you were thinking about:
IS NOT is SQLite's null-safe comparison, and it is genuinely useful here. Prefer NOT NULL DEFAULT 0 on boolean columns unless "unknown" is a state you actually need.
Booleans from other languages
The drivers do not agree with each other, which is worth knowing before you debug:
Python converts automatically in both directions if the column type is declared and detect_types is on. Without it, you get integers back:
JavaScript (better-sqlite3, node:sqlite) will not accept a JS boolean as a bind parameter at all. Convert explicitly:
Go with database/sql scans into a bool fine, because the driver does the conversion.
The pattern is the same everywhere: store 0/1, convert at the boundary, and never rely on the driver to guess.
Should you use BOOLEAN or INTEGER?
Both produce an integer column. The difference is what a human reads.
BOOLEAN documents intent and shows up in pragma_table_info, which some ORMs and tools read to decide how to convert values. INTEGER is honest about what is happening.
Either is fine. The CHECK constraint is the part that matters, and it is the part people leave out.
Common problems
My boolean column contains 'true' strings. An importer wrote text. They are all falsy. Fix the data and add a CHECK:
WHERE is_active returns nothing. Check typeof(is_active). If it says text, see above.
My ORM returns 1 instead of true. Expected. SQLite has no boolean to return. Convert in your model layer.
CHECK constraint failed. Working as designed. Something tried to write a non-boolean.
Quick reference
| Task | Syntax |
|---|---|
| Boolean column | INTEGER NOT NULL DEFAULT 0 CHECK (col IN (0,1)) |
| True / false literals | TRUE / FALSE, or 1 / 0 |
| Test true | WHERE col or WHERE col = 1 |
| Test false | WHERE NOT col or WHERE col = 0 |
| Null-safe test | WHERE col IS NOT 1 |
| Count true rows | sum(col) |
| Check what is stored | SELECT typeof(col) FROM t |
| Check the declared type | SELECT type FROM pragma_table_info('t') |