Web SQL lets you read rows with a normal SQL SELECT statement, run through tx.executeSql() inside a transaction. In the success callback, loop result.rows with rows.item(i) to get each row as an object.
Web SQL is obsolete and removed from modern Chrome. Use this page to maintain legacy code or learn SQL selects. New projects should use IndexedDB.
Related ideas.
rows is array-like, not a real array—use rows.item(i) and rows.length. Filter with WHERE, sort with ORDER BY, and page with LIMIT / OFFSET.
Syntax
Syntax
syntax.sql
SELECTcolumns FROM table_name WHERE condition
In Web SQL, wrap that statement in executeSql with bound arguments:
JS
tx.executeSql(
'SELECT id, name FROM items WHERE id = ?',
[id],
function (tx, result) { /* success */ },
function (tx, error) { /* failure */ }
);
Reference
Parameters
The executeSql call for a select takes these arguments:
Argument
Type
Description
sqlStatement
String
The SELECT ... FROM ... text, with ? placeholders for dynamic values.
arguments
Array
Values bound to each ? in order—e.g. [id] or [minAge].
successCallback
Function (optional)
Called as (tx, result). Loop result.rows with item(i).
errorCallback
Function (optional)
Called as (tx, error). Return true to roll back the transaction.
Prefer listing columns over SELECT * when you only need a few fields. Bind filter values with ?—never paste user input into the SQL string.
Result
Return Value
executeSql does not return a useful value itself. On success, your callback receives (tx, result). For SELECT, use result.rows to read the matched data.
Property
Type
Meaning after SELECT
result.rows
SQLResultSetRowList
The matched rows (array-like; use item(i))
result.rows.length
Number
How many rows matched
result.rows.item(i)
Object
One row; column names are properties (e.g. row.name)
result.rowsAffected
Number
Usually 0 for SELECT (no rows changed)
result.insertId
Number
Not useful for SELECT (used after INSERT)
rows.length values:
Value
What it means
0
Query succeeded but nothing matched. Not an error—show an empty state.
1 or more
That many rows are available via rows.item(0) … rows.item(n - 1).
Flow
How It Works
A typical read follows these steps:
Open the database with openDatabase(name, version, displayName, size).
Start a transaction: db.transaction(function (tx) { ... }) (or readTransaction for reads only).
Run SELECT via tx.executeSql(sql, args, success, error).
Loop result.rows with item(i) in the success callback.
Handle empty results when rows.length === 0.
JS
db.transaction(function (tx) {
tx.executeSql(
'SELECT id, name, email FROM contacts ORDER BY id',
[],
function (tx, result) {
for (var i = 0; i < result.rows.length; i++) {
var row = result.rows.item(i);
console.log(row.id, row.name, row.email);
}
},
function (tx, error) {
console.error(error.message);
return true;
}
);
});
Try it
Live Preview
Seed sample contacts, then select all, filter by id, or clear. Status updates below. Many modern browsers no longer support Web SQL—if so, the status box will say so.
Uses a private demo database when Web SQL is available. Prefer IndexedDB for new work.
Status
Checking Web SQL support…
Code
Examples
Short, copy-ready snippets. Use Try It Yourself for a live editor. Demos need a browser that still exposes openDatabase.
Example 1 — SELECT All Rows
Read every contact and loop with rows.item(i).
JS
db.transaction(function (tx) {
tx.executeSql(
'SELECT id, name, email FROM contacts',
[],
function (tx, result) {
console.log('Found', result.rows.length, 'row(s)');
for (var i = 0; i < result.rows.length; i++) {
var row = result.rows.item(i);
console.log(row.id, row.name);
}
}
);
});
Run SELECT with an empty args array when there are no ? placeholders.
Check rows.length for the count.
Loop with item(i) to read each row object.
Example 2 — SELECT with WHERE
Fetch one row by primary key using a bound parameter.
JS
db.transaction(function (tx) {
tx.executeSql(
'SELECT id, name, email FROM contacts WHERE id = ?',
[1],
function (tx, result) {
if (result.rows.length === 0) {
console.warn('No contact with id 1');
return;
}
var row = result.rows.item(0);
console.log(row.name, row.email);
}
);
});
Show “not found” instead of treating it as a failure.
Example 4 — ORDER BY + LIMIT
Sort and take only the first few rows for a simple list page.
JS
db.transaction(function (tx) {
tx.executeSql(
'SELECT id, name FROM contacts ORDER BY name ASC LIMIT ? OFFSET ?',
[2, 0],
function (tx, result) {
for (var i = 0; i < result.rows.length; i++) {
console.log(result.rows.item(i).name);
}
}
);
});
Feature-detectopenDatabase before running any SQL.
Create and seed the table on load.
On button click, run SELECT ... ORDER BY id.
Build status text from each rows.item(i).
Edge Cases & Pitfalls
Most SELECT bugs come from treating rows like a real array or ignoring empty results.
rows
Use item(i), not [i]
result.rows is array-like. Prefer rows.item(i) and rows.length—do not assume forEach works.
empty
Zero rows is not an error
Check rows.length === 0 before calling item(0), or you risk reading undefined.
inject
Bind with ?
Never concatenate user input into the SQL string. Pass filter values in the arguments array.
tx
Must use a transaction
All executeSql calls belong inside db.transaction() or readTransaction().
legacy
API may be missing
Feature-detect window.openDatabase. Modern Chrome has no Web SQL—point users to IndexedDB.
Compatibility
Browser Support
Web SQL is a deprecated client-side API. Firefox never shipped it. Chrome removed it (about Chrome 97+). Detect with window.openDatabase before running demos.
✓ Deprecated
Web SQL SELECT
Educational / legacy only. Prefer IndexedDB get(), getAll(), and openCursor() for new production apps.
LegacyModern browser support
Google ChromeRemoved ~97+
Removed
Mozilla Firefox—
Never supported
Apple SafariLegacy
Historical / limited
Microsoft EdgeRemoved
Removed (Chromium)
OperaRemoved
Removed (Chromium)
Samsung InternetLegacy
Do not rely on
Internet Explorer—
Not supported
Web SQL SELECTDeprecated
Bottom line: For reads in new web apps, use IndexedDB retrieve. Keep Web SQL only for maintaining old codebases.
Remember
Key Takeaways
Read rows:SELECT ... FROM t inside db.transaction().
Access data: loop result.rows with item(i) and length.
Filter safely: use WHERE ... ? with a bound args array.
Legacy: Web SQL is deprecated—use IndexedDB for new projects.
One line: select with SELECT ... FROM t via executeSql, loop rows.item(i), prefer IndexedDB going forward.
Frequently Asked Questions
Open the database with openDatabase(), then call db.transaction(). Inside the transaction, use tx.executeSql() with a SELECT statement. In the success callback, loop result.rows and read each row with result.rows.item(i).
Standard SQL: SELECT column1, column2 FROM table_name WHERE condition. Use SELECT * to fetch all columns. Add ORDER BY, LIMIT, and OFFSET for sorting and pagination.
The success callback receives a result object. result.rows.length is the row count. result.rows.item(index) returns one row as an object with column names as properties (e.g. row.name, row.email).
Yes. Use ? placeholders and pass values in the bindings array: SELECT * FROM users WHERE age > ? with [30]. This prevents SQL injection.
Web SQL is deprecated and removed from most modern browsers (including current Chrome). Treat this tutorial as educational for legacy code; use IndexedDB for new projects.
result.rows.length will be 0. That is not an error—the query succeeded but found no matches. Show an empty state in your UI instead of treating it as a failure.