Chapter 02 · Mastering the sqlite3 Command-Line Shell

Readable Output: .mode, .headers, .width, .nullvalue, and Current CLI Formatting

Separate SQLite result values from shell presentation and master the current 3.52+ output formatting model.

Beginner65–85 minutesOutput-format experimentsLast reviewed: August 2026

Learning outcomes

A query result has two layers: the values produced by SQLite and the way the sqlite3 program renders those values. If you do not separate those layers, changing a display mode can look like the database changed. This lesson builds a precise mental model of output modes and explicitly addresses the major formatting changes introduced in SQLite 3.52.0.

01

Explain why output mode, headers, width, and NULL rendering are presentation state rather than stored database state.

02

Use current .mode syntax to switch among human-readable and machine-oriented formats.

03

Recognize the interactive-versus-batch default difference introduced in SQLite 3.52.0.

04

Use current mode options while understanding older .headers and .width tutorials.

05

Choose an output format based on the consumer: a person, a shell pipeline, JSON tooling, CSV software, or Markdown documentation.

Same rows, different renderer

Run one query several times without changing the database. Only the shell's formatter changes. The safest way to prove this is to use a deterministic query and compare values.

sql · baseline query
SELECT d.device_code,       d.device_name,       d.status,       s.site_nameFROM device AS dJOIN site AS s ON s.site_id = d.site_idORDER BY d.device_code;

Regardless of whether the shell prints boxes, pipes, JSON, or CSV, SQLite has returned the same four columns and the same three rows. Formatting happens after statement execution.

The modern interactive default changed in SQLite 3.52

Before SQLite 3.52.0, many tutorials assumed an interactive shell would start in plain list mode. Current CLI documentation instead defines a richer terminal-oriented default based on qbox, with Unicode box drawing, relaxed SQL-literal quoting, screen-width fitting, and value-length limits. Batch sessions intentionally keep the legacy list-style default for compatibility.

Why screenshots age badly

A screenshot copied from an older SQLite version can show different separators, quoting, line-mode punctuation, widths, or default headers. This course uses textual examples and asks you to inspect .mode on your own build.

text · inspect instead of assume
sqlite> .mode.mode qbox --limits on --quote relaxed --sw auto --textjsonb onsqlite> .mode -v-- Prints the full set of current rendering options.sqlite> .mode --list-- Lists modes supported by this CLI build.

Human-oriented table modes

For exploratory work, a person usually wants aligned columns and visible column names. Current SQLite provides several tabular modes. Their purpose overlaps, but their aesthetics and quoting differ.

ModeGood forImportant characteristic
qboxInteractive inspectionUnicode box with SQL-like quoting to disambiguate values.
boxReadable terminal tablesUnicode box table.
tableASCII-friendly tablesUses ASCII borders rather than Unicode box glyphs.
columnCompact aligned textColumn alignment without surrounding border.
markdownDocs/issues/README snippetsProduces Markdown table syntax.
lineVery wide recordsDisplays one column per line for each row.
text · try the same query in several human modes
.mode qboxSELECT device_code, status FROM device ORDER BY device_code;.mode tableSELECT device_code, status FROM device ORDER BY device_code;.mode columnSELECT device_code, status FROM device ORDER BY device_code;.mode markdownSELECT device_code, status FROM device ORDER BY device_code;

Machine-oriented modes are contracts, not decoration

If another program consumes the output, a visually pretty table is usually the wrong contract. Choose a mode designed for interchange, and do not parse decorative borders.

ModeTypical consumerCaution
csvSpreadsheet/import pipelineCSV quoting and NULL representation need an explicit contract.
jsonJSON-aware toolingVerify exact JSON form for your CLI version and query shape.
listSimple text pipelinesDefault separator is typically |; embedded separators can be ambiguous.
quoteSQL-literal-like diagnosticsGood for distinguishing strings/numbers/NULL, not a complete database backup.
text · machine-oriented examples
.mode csv --titles onSELECT device_code, status FROM device ORDER BY device_code;.mode jsonSELECT device_code, status FROM device ORDER BY device_code;.mode list --colsep "|"SELECT device_code, status FROM device ORDER BY device_code;

Headers versus modern titles

Older examples often use .headers on. In the modern 3.52+ formatting system, header behavior is integrated into .mode through options such as --titles on or --title. A compatibility build may still accept older header commands, but new course examples prefer mode-local configuration because it keeps the rendering contract together.

text · current-first and compatibility forms
-- Current-first style.mode csv --titles onSELECT device_code, status FROM device;-- Older compatibility style you may encounter.headers on.mode csvSELECT device_code, status FROM device;

When maintaining an old script, do not rewrite syntax merely for fashion. First determine its minimum SQLite version and test the replacement output byte-for-byte if downstream tooling depends on it.

.width is now legacy; use .mode --widths

SQLite's current formatting documentation explicitly deprecates the old .width dot-command. It is retained for compatibility, but the supported model is to configure widths through .mode --widths (or --width in examples accepted by the current parser).

text · set widths in current mode configuration
.mode column --titles on --widths 12,28,18SELECT device_code, device_name, statusFROM deviceORDER BY device_code;-- Reset to automatic widths.mode column --widths 0

A width controls display space, not the declared column type and not the stored value. A long device_name does not become shorter in the database because a terminal column is narrow.

NULL must not disappear into ambiguity

In some text modes, a SQL NULL can appear as an empty field unless you configure a visible representation. That can make “missing value” look identical to an empty string. For investigation, make NULL conspicuous.

text · visible NULL experiment
SELECT 'empty string' AS label, '' AS valueUNION ALLSELECT 'sql null', NULL;.nullvalue <NULL>SELECT 'empty string' AS label, '' AS valueUNION ALLSELECT 'sql null', NULL;-- Current mode-local alternative.mode table --null "<NULL>" --titles on

.nullvalue changes shell rendering only. It does not update NULLs in the table and it does not redefine SQL NULL semantics.

Interactive and batch output can intentionally differ

SQLite 3.52+ uses a terminal-friendly default when connected interactively and legacy list output in batch mode. That is sensible for compatibility, but it means a command that “looks fine” at the prompt may produce a different shape when redirected in CI. Automation should choose an explicit mode instead of inheriting a context-sensitive default.

shell · make batch output explicit
# Human inspectionsqlite3 -box fieldnotes.db "SELECT device_code,status FROM device ORDER BY device_code;"# CSV contractsqlite3 -csv -header fieldnotes.db "SELECT device_code,status FROM device ORDER BY device_code;"# JSON contractsqlite3 -json fieldnotes.db "SELECT device_code,status FROM device ORDER BY device_code;"

Formatting lab: one query, six representations

Use a disposable copy of fieldnotes.db. Run the query below in qbox, table, column, markdown, csv, and json. Record which formats preserve column labels by default on your build, how NULL appears, and which output you would choose for a person versus another program.

sql · query to format
SELECT d.device_code,       d.status,       n.noted_at,       n.note_textFROM device AS dLEFT JOIN maintenance_note AS n ON n.device_id = d.device_idORDER BY d.device_code, n.noted_at;

Choose the consumer

For each case, choose a format and justify it.

  1. A pull request comment where teammates should read a small result table.
  2. A file to be opened by spreadsheet software and later re-imported.
  3. A shell pipeline that expects stable pipe-separated fields.
  4. A program that already has a JSON parser.
  5. An interactive diagnosis where distinguishing the text "NULL" from SQL NULL matters.
Review the answers

Markdown is convenient for a pull request; CSV fits spreadsheet interchange; an explicitly configured list mode can serve a simple pipeline; JSON is natural for a JSON-aware consumer; and an interactive qbox/quote-style view with a visible NULL marker helps diagnostic disambiguation. The exact choice is less important than making the output contract explicit.

Production judgment: output is an interface

Once a script's stdout is consumed by another tool, formatting is no longer cosmetic—it is an interface. Pin an explicit mode, title/header policy, NULL representation, separators, and minimum CLI version. If you need a durable application API, prefer a language SQLite driver and structured row objects instead of scraping the CLI's human-oriented rendering.

Summary and next lesson

Query values and CLI rendering are separate layers. Current SQLite has a substantially richer result formatter than older releases, and automation should never depend on the interactive default. The next lesson moves from interactive exploration to repeatable scripts, where input sources, error handling, stdout/stderr, and process exit status become part of correctness.

References

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.