INSERT
Core statement
Add a new row to a table with a standard SQL INSERT INTO statement.

Web SQL Database is a deprecated browser API that stores structured data in a local SQLite database. The SQL INSERT statement adds new rows you need to keep—cart items, form drafts, or cached records. This tutorial covers INSERT syntax, defining a table first with CREATE TABLE, binding safe ? parameters, reading back insertId, handling errors, five worked examples, and why Web SQL is deprecated in favor of IndexedDB.
Core statement
Add a new row to a table with a standard SQL INSERT INTO statement.
Define schema
Set up columns and a primary key once, before any row is inserted.
Safe params
Bind ids and values with ? placeholders instead of concatenating SQL.
New row id
Read the auto-generated primary key back from result.insertId.
Handle failure
Catch constraint violations and syntax errors in the failure callback.
Atomic ops
Every executeSql call runs inside db.transaction().
Web SQL Database is a deprecated browser API for client-side relational storage. It uses SQLite under the hood, so standard SQL statements—including INSERT—work just like they do on server-side databases.
The W3C deprecated Web SQL in favor of IndexedDB, and Chrome removed it in version 97. Although the API is no longer recommended for new projects, understanding INSERT still helps you maintain legacy applications and grasp the SQL fundamentals that carry over to other databases.
Every app needs to save data—a cart item, a form draft, a cached record. INSERT is how a row is born in a SQL database, and getting the schema, bindings, and duplicate-key handling right keeps that data trustworthy from the start.
Creates a brand-new row in a table—the counterpart to DELETE.
CREATE TABLE IF NOT EXISTS before the first insert—there is no implicit schema.
? placeholders keep dynamic values safe from SQL injection.
Web SQL is educational for legacy code—use IndexedDB for new work.
In short: INSERT INTO table (cols) VALUES (?, ?), bound with ?, inside a transaction, confirmed with insertId—that pattern adds rows safely in Web SQL and in most SQL databases.
Before inserting data, open the database and create the table that will hold your rows—INSERT needs a schema to write into.
openDatabase(name, version, displayName, estimatedSize) opens an existing database or creates a new one.
const db = openDatabase('myDatabase', '1.0', 'Test DB', 2 * 1024 * 1024); name — unique database name (string).version — version string shown to the user.displayName — human-readable label.estimatedSize — expected size in bytes (here, 2 MB).Run CREATE TABLE IF NOT EXISTS inside the first transaction so the schema exists before any inserts.
db.transaction(function (tx) {
tx.executeSql(
'CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT NOT NULL)'
);
}); Here id is the primary key and name stores the item label. Adjust columns to match your app’s data model—this table is reused across every example below.
Standard SQL syntax for adding a row:
INSERT INTO table_name (column1, column2) VALUES (value1, value2); In Web SQL, call it through executeSql with bound parameters:
tx.executeSql(
'INSERT INTO items (id, name) VALUES (?, ?)',
[id, name],
function (tx, result) { /* success */ },
function (tx, error) { /* failure */ }
); | Argument | Description |
|---|---|
sqlStatement | The INSERT INTO ... VALUES ... string, with ? placeholders for dynamic values. |
arguments | Array of values bound to each ?, in order—e.g. [id, name]. |
successCallback (optional) | Called as (tx, result); read result.insertId for the new row’s id. |
errorCallback (optional) | Called as (tx, error); return true to roll back the transaction. |
INSERT fails if the target table does not exist yet.? — never concatenate user input into the SQL string.result.insertId — confirms the row was created and gives you its id.INSERT OR REPLACE or INSERT OR IGNORE to handle them gracefully.| Operation | Code pattern |
|---|---|
| Insert one row | INSERT INTO t (a,b) VALUES (?,?) |
| Upsert (SQLite) | INSERT OR REPLACE INTO t ... |
| Skip duplicates | INSERT OR IGNORE INTO t ... |
| New row id | result.insertId in success callback |
| Batch insert | loop tx.executeSql(...) in one transaction |
INTO itemsOne table
VALUES (?, ?)? binding
insertIdNew row
db.transaction()Required
All three add rows—but they disagree on what happens when the primary key already exists.
fails on duplicateThrows a constraint error if the key already exists—the safest default.
overwrites rowDeletes the existing row with the same key, then inserts the new one—an upsert.
skips silentlyKeeps the original row untouched and drops the new insert with no error.
Reach for the full UPDATE tutorial when you need to change specific columns on a row that should otherwise stay intact.
Reach for INSERT whenever new data needs a home in your local database.
The shopper clicks “add to cart”—insert a new row for that product.
Insert unsaved notes or form input locally so nothing is lost on a refresh.
Insert server data into a local table so the app works offline or loads instantly next time.
Loop through a dataset and insert every row inside a single transaction for speed and atomicity.
If a row might already exist, use INSERT OR REPLACE/OR IGNORE, or check first—plain INSERT will throw.
Key benefit: a validated schema, safe placeholders, and the right INSERT variant let you add data confidently—whether you are creating brand-new rows or upserting existing ones.
The INSERT statement adds a new row with the values you provide. Wrap the logic in a reusable function:
function insertItem(id, name) {
db.transaction(function (tx) {
tx.executeSql(
'INSERT INTO items (id, name) VALUES (?, ?)',
[id, name],
function (tx, result) {
console.log('Row inserted. insertId:', result.insertId);
},
function (tx, error) {
console.error('Insert failed:', error.message);
return true;
}
);
});
}
insertItem(1, 'Sample Item'); openDatabase — opens or creates the Web SQL database.transaction — wraps the insert in an atomic unit.executeSql — runs INSERT INTO ... VALUES (?, ?) with bound parameters.Errors can come from invalid SQL, a missing table, or a duplicate primary key. A successful insert also gives you result.insertId—use it to reference the row you just created.
tx.executeSql(
'INSERT INTO items (id, name) VALUES (?, ?)',
[id, name],
function (tx, result) {
console.log('Saved row, insertId:', result.insertId);
},
function (tx, error) {
console.error('Error inserting record:', error.message);
return true;
}
); For transaction-wide feedback, pass a second and third argument to db.transaction(fn, errorFn, successFn)—errorFn runs if the transaction rolls back, and successFn runs once every statement inside it commits.
These examples use the Web SQL API pattern. Use the Try It Yourself links to run live demos. Web SQL works only in browsers that still expose openDatabase; otherwise the demo shows a clear status message.
Insert a single row, then wrap the pattern in a reusable function.
Add one row with id = 1 to the items table.
const db = openDatabase('myDatabase', '1.0', 'Test DB', 2 * 1024 * 1024);
db.transaction(function (tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)');
tx.executeSql(
'INSERT INTO items (id, name) VALUES (?, ?)',
[1, 'Sample Item'],
function (tx, result) {
console.log('Inserted row id:', result.insertId);
},
function (tx, error) {
console.error('Error:', error.message);
}
);
}); The transaction first ensures the table exists, then inserts id 1 with name Sample Item. Bound parameters ([1, 'Sample Item']) keep the query safe from injection.
Call the same function from buttons, list items, or form submit handlers.
function insertItem(id, name) {
db.transaction(function (tx) {
tx.executeSql(
'INSERT INTO items (id, name) VALUES (?, ?)',
[id, name],
function () { console.log('Saved:', name); },
function (tx, error) {
console.error('Insert error:', error.message);
return true;
}
);
});
}
insertItem(2, 'Notebook');
insertItem(3, 'Pen'); Each call starts its own transaction. Returning true from the error callback rolls back that transaction so partial bad data is not committed.
Batch inserts, upserts, and a complete demo page.
Chain several executeSql calls inside a single transaction for atomic batch inserts.
const products = [
{ id: 10, name: 'Apple' },
{ id: 11, name: 'Banana' },
{ id: 12, name: 'Cherry' }
];
db.transaction(function (tx) {
products.forEach(function (p) {
tx.executeSql(
'INSERT INTO items (id, name) VALUES (?, ?)',
[p.id, p.name]
);
});
}, function (error) {
console.error('Batch failed:', error.message);
}, function () {
console.log('All ' + products.length + ' rows inserted');
}); If any insert fails, the transaction rolls back and none of the rows are saved. That keeps your table consistent during bulk imports.
SQLite lets you replace an existing row when the primary key already exists.
function saveItem(id, name) {
db.transaction(function (tx) {
tx.executeSql(
'INSERT OR REPLACE INTO items (id, name) VALUES (?, ?)',
[id, name],
function () { console.log('Upserted id', id); }
);
});
}
saveItem(1, 'Original name');
saveItem(1, 'Updated name'); /* replaces the first row */ The second call does not throw a duplicate-key error—it overwrites the row with id 1. Use this for “save” buttons that should create or update in one step.
Set up the database and insert a row with a unique id every time the user clicks a button.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Web SQL Insert Example</title>
</head>
<body>
<h1>Insert Data into Web SQL</h1>
<button type="button" id="insertBtn">Insert Sample Item</button>
<p id="status"></p>
<script>
if (!window.openDatabase) {
document.getElementById('status').textContent =
'Web SQL is not supported in this browser.';
} else {
const db = openDatabase('myDatabase', '1.0', 'Test DB', 2 * 1024 * 1024);
db.transaction(function (tx) {
tx.executeSql(
'CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)'
);
});
function insertItem(id, name) {
db.transaction(function (tx) {
tx.executeSql(
'INSERT INTO items (id, name) VALUES (?, ?)',
[id, name],
function (tx, result) {
document.getElementById('status').textContent =
'Inserted: ' + name + ' (insertId ' + result.insertId + ')';
},
function (tx, error) {
document.getElementById('status').textContent =
'Error: ' + error.message;
return true;
}
);
});
}
document.getElementById('insertBtn').addEventListener('click', function () {
insertItem(Date.now(), 'Sample Item');
});
}
</script>
</body>
</html> The page checks for openDatabase support first. Each button click inserts a row with a unique timestamp id and shows the resulting insertId in the status text.
Real-world scenarios where adding a new row is exactly what you need.
Add products the shopper drops into their basket.
Example: INSERT INTO cart (product_id, qty) VALUES (?, ?).
Autosave unsaved notes or form input before the user submits.
Example: a draft row saved on every keystroke pause.
Append timestamped events to a local audit table.
Example: INSERT INTO log (ts, action) VALUES (?, ?).
Insert default preferences on a user’s first visit, then update later.
Example: a “first run” seed row per user.
Loop through a dataset and insert every row inside one transaction.
Example: importing a CSV of products on first load.
Add rows to existing Web SQL–based apps still running in the wild.
Example: a migration script backfilling old records.
Pro Tip: pair every insert with a SELECT during development so you can confirm the row landed with the values you expected.
Why the standard SQL INSERT pattern is worth learning, even in a deprecated API.
CREATE TABLE defines columns and types up front—nothing implicit.
? bindings sanitize values automatically, avoiding SQL injection.
insertId and rowsAffected confirm exactly what happened—no guessing.
Wrapped in db.transaction(), a whole batch of inserts succeeds or fails together.
Pro Tip: this exact pattern—schema first, bound parameters, transactions, and checking the result—transfers directly to IndexedDB, SQLite, and server-side SQL.
Follow these practices to write safe, predictable inserts.
Check required fields, types, and length limits in JavaScript before the row ever reaches SQL.
Confirm the row was actually created before updating your UI as if it succeeded.
Use ? placeholders for ids, names, and content—never build SQL with string concatenation.
Plain INSERT for new keys, OR REPLACE for upserts, OR IGNORE to skip duplicates safely.
Web SQL is deprecated—treat new features as an opportunity to move to IndexedDB.
Pro Tip: log every insert’s SQL, bound values, and insertId during development—it turns “why wasn’t that saved?” bugs into a quick log lookup.
Avoid these mistakes when adding rows with Web SQL.
Building queries with string concatenation opens the door to SQL injection.
→ Bind every dynamic value with ? placeholders instead.
A plain INSERT throws a constraint error the moment the key already exists.
→ Use INSERT OR REPLACE to upsert, or INSERT OR IGNORE to skip silently.
Calling executeSql outside db.transaction() is not valid Web SQL usage.
→ Always wrap inserts—single or batch—in db.transaction(function (tx) { ... }).
Chrome removed Web SQL in version 97—code that assumes openDatabase exists will crash.
→ Feature-detect with if (window.openDatabase) before running any Web SQL code.
Inserting empty names, wrong types, or oversized text produces messy data no one catches until later.
→ Validate required fields, types, and length limits in JavaScript before calling executeSql.
Pro Tip: when in doubt about duplicates, run a quick SELECT for the key first—if nothing comes back, a plain INSERT is safe.
openDatabase() then db.transaction() starts an atomic unit of work.
INSERT INTO ... VALUES (?, ?) with values bound in an array.
executeSql writes the row; the transaction commits the change.
result.insertId reports the new row’s id.
CREATE TABLE IF NOT EXISTS before the first insert—there is no implicit schema.result.insertId—it confirms the row was created and gives you its id.? placeholders—never string-concatenate SQL.INSERT OR REPLACE or INSERT OR IGNORE.Quick Takeaway: Web SQL is deprecated—practice INSERT here to learn the pattern, then apply the same schema + bindings + insertId habits in IndexedDB and other SQL databases.
Web SQL was never implemented in Firefox or Internet Explorer. Chrome and other Chromium browsers removed it in Chrome 97. Only older Safari versions historically supported it. Feature-detect with window.openDatabase before running any demo or legacy code.
Web SQL is not part of any current web standard. Modern Chrome, Edge, and Opera have removed it entirely; Firefox never implemented it. Treat this tutorial as educational for legacy maintenance only.
Bottom line: Do not build new features on Web SQL. For cross-browser client-side storage today, use IndexedDB.
Inserting records with Web SQL means creating the table first, running INSERT INTO ... VALUES ... inside db.transaction, binding values safely with ?, and checking insertId to confirm the row was saved.
Although Web SQL is deprecated, understanding inserts helps you maintain older systems. For new projects, use IndexedDB—but the SQL habits you learned here (safe parameters, explicit schemas, and checking results) apply everywhere.
Practice the five examples above, then continue to Retrieve so you can read back the rows you just inserted.
? bindings) for every insertresult.insertId to confirm the row was createdOR REPLACE/OR IGNORE deliberately for duplicate keysCREATE TABLE before the first insertUse these points when adding rows to a client-side database.
Add rows.
BasicsRequired wrapper.
AtomicSafe params.
SecurityRead new id.
ReliabilityUse IndexedDB.
ModernINSERT OR REPLACE is SQLite shorthand for an upsert—it deletes any row sharing the same primary key and inserts the new one in a single statement, so you never need a separate SELECT-then-decide-to-UPDATE-or-INSERT check. Because Web SQL is deprecated, treat every INSERT you write here as a rehearsal for the same pattern in IndexedDB.
Now that you can add rows, learn how to read them back out with SELECT queries.
9 people found this page helpful