Skip to content

Web Locks

Multiple tabs of the same app can run simultaneously and may attempt to write to the same IndexedDB record or the same OPFS file at exactly the same moment. Without coordination, writes can race and the last writer silently wins (or, worse, data is corrupted). The Web Locks API provides a browser-native mutual-exclusion primitive that solves this — no server required.

To acquire a lock, pass a string name and an async callback. The browser holds the lock for as long as the callback’s returned Promise is pending, then releases it automatically.

await navigator.locks.request('demo:my-resource', async (lock) => {
// Inside here you hold the lock exclusively.
// No other tab can acquire 'demo:my-resource' until this async block resolves.
await doWork();
});
// Lock released here — next waiter can proceed.

If the lock is already held by another tab, request queues your callback until the lock is released. The queue is FIFO.

The default mode is exclusive — only one holder at a time. For read-heavy workloads you can use shared mode: multiple shared holders can overlap, but an exclusive request waits until all shared holders finish (like a read-write lock).

// Multiple tabs can hold this concurrently
await navigator.locks.request('demo:db-read', { mode: 'shared' }, async (lock) => {
const data = await db.getAll();
return data;
});
// This waits until every shared holder above has finished
await navigator.locks.request('demo:db-read', { mode: 'exclusive' }, async (lock) => {
await db.put({ key: 'x', value: 42 });
});

Pass ifAvailable: true to get null instead of queuing when the lock is already held. This is useful for “do work only if I can start immediately, otherwise skip this cycle”:

const result = await navigator.locks.request(
'demo:background-sync',
{ ifAvailable: true },
async (lock) => {
if (!lock) {
console.log('Lock busy — skipping this cycle');
return null;
}
// We have the lock
return await syncData();
}
);

query() returns a snapshot of all currently held and waiting locks — useful for debugging:

const state = await navigator.locks.query();
console.log('Held:', state.held);
console.log('Pending:', state.pending);
Browser Storage
What happens if Tab A holds an exclusive lock named "x" and Tab B calls navigator.locks.request("x", cb) with no options?
In shared mode, how many tabs can hold the same lock simultaneously?
You pass `ifAvailable: true` and the lock is currently held. What does the callback receive?
When is a Web Lock released?