---
title: "SQL Server to SQLite data types"
description: "Every SQL Server data type that SQLite stores differently, what the converter writes for it, when, and the report line."
canonical: https://intosql.com/docs/types/sql-server-to-sqlite
last_updated: 2026-10-15
---

# SQL Server to SQLite data types

Each row is a type from SQL Server that SQLite stores differently: what the SQLite script gets, why, and the line the report writes about it. The rows come from the converter's own rules, so this page matches what it does.

**Convert in the browser:** [SQL Server to SQLite](/sql-server-to-sqlite) opens the converter set up for this pair. The [SQL Server to SQLite migration guide](/docs/migrations/sql-server-to-sqlite) covers dumping, loading and the rules for keys, indexes and names.

A report line is a fix when the converter changed something and lists it, or a problem when a value or object is worth a look.

## Numbers

| In SQL Server | In SQLite | Why | Report line |
| --- | --- | --- | --- |
| tinyint | INTEGER with a CHECK that it isn't negative | SQL Server's tinyint runs from 0 to 255; a check keeps its range. | `unsigned_widened` (fix) |
| decimal(p,s) past 15 digits, money | TEXT | SQLite keeps about 15 significant digits in a number. Wider numbers are kept as text so every digit stays exact. | `decimal_as_text` (fix) |

## Booleans and bits

| In SQL Server | In SQLite | Why | Report line |
| --- | --- | --- | --- |
| bit | INTEGER 0 or 1 with a CHECK | SQLite has no boolean type; its own true and false are 1 and 0. | None |

## Text

| In SQL Server | In SQLite | Why | Report line |
| --- | --- | --- | --- |
| char(n), varchar(n), nchar(n), nvarchar(n), varchar(max), text | VARCHAR(n) and TEXT, not enforced | SQLite keeps the declared type for tools to read but doesn't limit the length. | None |
| NUL characters in text | kept | SQLite text can hold a NUL character, so the value is copied as it is. | None |
| text that isn't valid in its encoding | the bad bytes as the replacement character | The script is UTF-8 and every value has to be valid text. | `invalid_utf8_replaced` (problem) |

## Dates and times

| In SQL Server | In SQLite | Why | Report line |
| --- | --- | --- | --- |
| date, datetime, datetime2, smalldatetime | DATETIME as ISO 8601 text, every digit kept | SQLite stores dates as text its date functions read. | None |
| datetimeoffset | TIMESTAMP as ISO 8601 text in UTC | SQLite has no type that keeps an offset, so each moment is written in UTC: the same instant whatever offset it had. | `moments_as_utc` (fix) |
| time | TIME as text | SQLite keeps times as text, with every digit. | None |
| dates that aren't valid | NULL | A value that isn't a date becomes NULL and the report counts it. | `date_out_of_range` (problem) |

## Binary

| In SQL Server | In SQLite | Why | Report line |
| --- | --- | --- | --- |
| binary, varbinary, image | BLOB | Bytes stay bytes. | None |
| rowversion, timestamp | BLOB, empty | SQL Server sets a row version by itself and SSMS scripts no values for it. | `rowversion_empty` (fix) |

## JSON, spatial and other types

| In SQL Server | In SQLite | Why | Report line |
| --- | --- | --- | --- |
| json (SQL Server 2025) | TEXT | The same JSON values, which SQLite's JSON functions read. | None |
| uniqueidentifier | TEXT | The same identifiers, written in lowercase. | None |
| geometry, geography | BLOB with SQL Server's own bytes | SSMS writes spatial values in SQL Server's serialization, which is kept byte for byte. | `spatial_as_native` (fix) |
| xml, sql_variant, hierarchyid, vector | TEXT | SQLite has no equivalent type; xml and sql_variant keep each value's text and hierarchyid its path, such as /1/2/. | `type_replaced` (fix) |
| types IntoSQL doesn't know | TEXT | Text holds any value; change the type after loading. | `unknown_type` (problem) |

## Counters and identities

| In SQL Server | In SQLite | Why | Report line |
| --- | --- | --- | --- |
| IDENTITY | INTEGER PRIMARY KEY AUTOINCREMENT, continuing after the highest id | SQLite counts a whole integer primary key, and AUTOINCREMENT keeps ids from being reused. | None |
| IDENTITY columns that aren't the whole primary key | INTEGER without a counter | SQLite only counts a column that is the whole primary key, so the column keeps its values and new rows need theirs. | `counter_dropped` (problem) |

## Other pairs

- SQL Server into [PostgreSQL](/docs/types/sql-server-to-postgresql), [MySQL](/docs/types/sql-server-to-mysql), [MariaDB](/docs/types/sql-server-to-mariadb), [SQL Server](/docs/types/sql-server-to-sql-server), [Oracle](/docs/types/sql-server-to-oracle)
- Into SQLite from [MySQL](/docs/types/mysql-to-sqlite), [MariaDB](/docs/types/mariadb-to-sqlite), [PostgreSQL](/docs/types/postgresql-to-sqlite), [SQLite](/docs/types/sqlite-to-sqlite), [Oracle](/docs/types/oracle-to-sqlite)
- [Every pair](/docs/types#from-one-database-to-another)
