HTML Web SQL INSERT

Beginner
⏱️ ~10–12 min
📚 Updated: Aug 2026
🎯 5 Examples
🚀 5 Try-it labs
INSERT · ? · insertId

What You’ll Learn

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.

INSERT

Core statement

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

CREATE TABLE

Define schema

Set up columns and a primary key once, before any row is inserted.

? bindings

Safe params

Bind ids and values with ? placeholders instead of concatenating SQL.

insertId

New row id

Read the auto-generated primary key back from result.insertId.

Errors

Handle failure

Catch constraint violations and syntax errors in the failure callback.

Transactions

Atomic ops

Every executeSql call runs inside db.transaction().

Introduction

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.

Why it matters?

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.

Key Highlights

Adds Rows

Creates a brand-new row in a table—the counterpart to DELETE.

Table Required

CREATE TABLE IF NOT EXISTS before the first insert—there is no implicit schema.

Bound Parameters

? placeholders keep dynamic values safe from SQL injection.

Deprecated API

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.

Setting Up the Database

Before inserting data, open the database and create the table that will hold your rows—INSERT needs a schema to write into.

Step 1 — Open the database

openDatabase(name, version, displayName, estimatedSize) opens an existing database or creates a new one.

js
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).

Step 2 — Create a table

Run CREATE TABLE IF NOT EXISTS inside the first transaction so the schema exists before any inserts.

js
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.

📝 INSERT Syntax

Standard SQL syntax for adding a row:

SQL
INSERT INTO table_name (column1, column2) VALUES (value1, value2);

In Web SQL, call it through executeSql with bound parameters:

js
tx.executeSql(
  'INSERT INTO items (id, name) VALUES (?, ?)',
  [id, name],
  function (tx, result) { /* success */ },
  function (tx, error)  { /* failure */ }
);

executeSql arguments

ArgumentDescription
sqlStatementThe INSERT INTO ... VALUES ... string, with ? placeholders for dynamic values.
argumentsArray 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.

Important rules

  • Create the table firstINSERT fails if the target table does not exist yet.
  • Bind values with ? — never concatenate user input into the SQL string.
  • Check result.insertId — confirms the row was created and gives you its id.
  • Duplicate keys throw — use INSERT OR REPLACE or INSERT OR IGNORE to handle them gracefully.

⚡ Quick Reference

OperationCode pattern
Insert one rowINSERT INTO t (a,b) VALUES (?,?)
Upsert (SQLite)INSERT OR REPLACE INTO t ...
Skip duplicatesINSERT OR IGNORE INTO t ...
New row idresult.insertId in success callback
Batch insertloop tx.executeSql(...) in one transaction
Target
INTO items

One table

Safe
VALUES (?, ?)

? binding

Check
insertId

New row

Wrapper
db.transaction()

Required

📋 INSERT vs INSERT OR REPLACE vs INSERT OR IGNORE

All three add rows—but they disagree on what happens when the primary key already exists.

INSERT
fails on duplicate

Throws a constraint error if the key already exists—the safest default.

OR REPLACE
overwrites row

Deletes the existing row with the same key, then inserts the new one—an upsert.

OR IGNORE
skips silently

Keeps 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.

Context

When to Insert Rows

Reach for INSERT whenever new data needs a home in your local database.

  1. Save a cart item

    The shopper clicks “add to cart”—insert a new row for that product.

  2. Autosave a draft

    Insert unsaved notes or form input locally so nothing is lost on a refresh.

  3. Cache a fetched record

    Insert server data into a local table so the app works offline or loads instantly next time.

  4. Bulk import at once

    Loop through a dataset and insert every row inside a single transaction for speed and atomicity.

  5. Not blindly on a known duplicate

    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.

Inserting Data

The INSERT statement adds a new row with the values you provide. Wrap the logic in a reusable function:

js
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.

Handling Success and Errors

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.

js
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.

Examples Gallery

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.

📚 Getting Started

Insert a single row, then wrap the pattern in a reusable function.

Example 1 — Insert a Single Row

Add one row with id = 1 to the items table.

js
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);
    }
  );
});
Try It Yourself

How It Works

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.

Example 2 — Reusable insertItem Function

Call the same function from buttons, list items, or form submit handlers.

js
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');
Try It Yourself

How It Works

Each call starts its own transaction. Returning true from the error callback rolls back that transaction so partial bad data is not committed.

📈 Practical Patterns

Batch inserts, upserts, and a complete demo page.

Example 3 — Insert Multiple Rows in One Transaction

Chain several executeSql calls inside a single transaction for atomic batch inserts.

js
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');
});
Try It Yourself

How It Works

If any insert fails, the transaction rolls back and none of the rows are saved. That keeps your table consistent during bulk imports.

Example 4 — INSERT OR REPLACE (Upsert)

SQLite lets you replace an existing row when the primary key already exists.

js
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 */
Try It Yourself

How It Works

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.

Example 5 — Complete HTML Page with Insert Button

Set up the database and insert a row with a unique id every time the user clicks a button.

html
<!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>
Try It Yourself

How It Works

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.

Use Cases

Real-world scenarios where adding a new row is exactly what you need.

1. Save Cart Items

Add products the shopper drops into their basket.

Example: INSERT INTO cart (product_id, qty) VALUES (?, ?).

2. Offline Form Drafts

Autosave unsaved notes or form input before the user submits.

Example: a draft row saved on every keystroke pause.

3. Activity Logs

Append timestamped events to a local audit table.

Example: INSERT INTO log (ts, action) VALUES (?, ?).

4. Default Settings

Insert default preferences on a user’s first visit, then update later.

Example: a “first run” seed row per user.

5. Bulk Data Import

Loop through a dataset and insert every row inside one transaction.

Example: importing a CSV of products on first load.

6. Legacy Maintenance

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.

Advantages

Why the standard SQL INSERT pattern is worth learning, even in a deprecated API.

  1. 1. Explicit Schema

    CREATE TABLE defines columns and types up front—nothing implicit.

  2. 2. Safe by Default

    ? bindings sanitize values automatically, avoiding SQL injection.

  3. 3. Auditable Results

    insertId and rowsAffected confirm exactly what happened—no guessing.

  4. 4. Transactional Atomicity

    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.

Usage Tips

Follow these practices to write safe, predictable inserts.

  1. 1. Validate Before Inserting

    Check required fields, types, and length limits in JavaScript before the row ever reaches SQL.

  2. 2. Always Check insertId or rowsAffected

    Confirm the row was actually created before updating your UI as if it succeeded.

  3. 3. Bind Every Dynamic Value

    Use ? placeholders for ids, names, and content—never build SQL with string concatenation.

  4. 4. Choose the Right INSERT Variant

    Plain INSERT for new keys, OR REPLACE for upserts, OR IGNORE to skip duplicates safely.

  5. 5. Plan Your Migration to IndexedDB

    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.

Common Pitfalls

Avoid these mistakes when adding rows with Web SQL.

  1. 1. Concatenating SQL Strings

    Building queries with string concatenation opens the door to SQL injection.

    → Bind every dynamic value with ? placeholders instead.

  2. 2. Duplicate Primary Key Without OR IGNORE/REPLACE

    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.

  3. 3. Forgetting the Transaction Wrapper

    Calling executeSql outside db.transaction() is not valid Web SQL usage.

    → Always wrap inserts—single or batch—in db.transaction(function (tx) { ... }).

  4. 4. Assuming Web SQL Works in Modern Chrome

    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.

  5. 5. Skipping Input Validation

    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.

🧠 How Web SQL INSERT Works

1

Open & transact

openDatabase() then db.transaction() starts an atomic unit of work.

Setup
2

Build & bind

INSERT INTO ... VALUES (?, ?) with values bound in an array.

Bind
3

Run INSERT & commit

executeSql writes the row; the transaction commits the change.

Write
=

Row saved

result.insertId reports the new row’s id.

Notes

  • Web SQL is deprecated and removed from modern Chrome—use IndexedDB for any new project.
  • Run CREATE TABLE IF NOT EXISTS before the first insert—there is no implicit schema.
  • Check result.insertId—it confirms the row was created and gives you its id.
  • Bind all dynamic values with ? placeholders—never string-concatenate SQL.
  • Duplicate primary keys throw unless you use INSERT OR REPLACE or INSERT OR IGNORE.
  • Pair with SELECT to verify what was actually saved.

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.

Browser Support

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.

Deprecated

Web SQL API

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.

Legacy Not in modern browsers
Google Chrome Removed in Chrome 97+
Removed
Mozilla Firefox Never implemented
Not supported
Apple Safari Legacy versions only
Legacy
Microsoft Edge Chromium-based · removed
Removed
Internet Explorer Never implemented
Not supported
Opera Chromium-based · removed
Removed
Web SQL API Deprecated

Bottom line: Do not build new features on Web SQL. For cross-browser client-side storage today, use IndexedDB.

Wrap Up

🎉 Conclusion

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.

💡 Best Practices

✅ Do

  • Use parameterized queries (? bindings) for every insert
  • Validate and sanitize input before writing to the database
  • Handle both statement-level and transaction-level errors
  • Check result.insertId to confirm the row was created
  • Choose OR REPLACE/OR IGNORE deliberately for duplicate keys
  • Prefer IndexedDB for new projects

❌ Don’t

  • Build SQL strings from raw user input
  • Assume inserts succeed without an error callback
  • Skip CREATE TABLE before the first insert
  • Insert duplicate primary keys without an upsert strategy
  • Rely on Web SQL for cross-browser apps today
  • Build new features on Web SQL in 2026

Key Takeaways

Knowledge Unlocked

Five things to remember about Web SQL INSERT

Use these points when adding rows to a client-side database.

5
Core concepts
🗃️ 02

Transaction

Required wrapper.

Atomic
🔒 03

? bindings

Safe params.

Security
🔢 04

insertId

Read new id.

Reliability
🔄 05

Deprecated

Use IndexedDB.

Modern

❓ Frequently Asked Questions

Open the database with openDatabase(), then call db.transaction(). Inside the transaction, use tx.executeSql() with an INSERT INTO table (col1, col2) VALUES (?, ?) statement. Pass column values as a bound parameter array [id, name] instead of concatenating strings into the SQL.
Standard SQL: INSERT INTO table_name (col1, col2) VALUES (?, ?). Web SQL uses SQLite under the hood, so INSERT OR REPLACE and INSERT OR IGNORE are also supported for upserts and duplicate handling.
Read result.insertId in the success callback of executeSql. It holds the rowid SQLite assigned, which matches an INTEGER PRIMARY KEY column when your table uses one.
Web SQL requires all executeSql calls to run within db.transaction(). Transactions group operations atomically—if one statement fails, the whole transaction can roll back, keeping your data consistent.
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.
Plain INSERT fails if a unique constraint is violated. INSERT OR REPLACE deletes the existing row with the same key and inserts the new one—useful for upserts. INSERT OR IGNORE silently skips the new row instead of throwing an error, keeping the original row untouched.

Did you Know? 🔊

INSERT 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.

Continue to Web SQL Retrieve

Now that you can add rows, learn how to read them back out with SELECT queries.

Retrieve tutorial →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

9 people found this page helpful