# Transactions

The JavaScript SDK supports atomic transactions for executing multiple queries that succeed or fail together.

Transactions allow you to execute a group of queries atomically, meaning either all changes are applied or none are. This is essential for maintaining data consistency when performing related operations that must not be partially applied.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#begintransaction"> <code> db.beginTransaction() </code></a></td>
			<td scope="row" data-label="Description">Starts a new transaction</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-transaction.md#commit"> <code> txn.commit() </code></a></td>
			<td scope="row" data-label="Description">Commits the transaction, applying all changes</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-transaction.md#cancel"> <code> txn.cancel() </code></a></td>
			<td scope="row" data-label="Description">Cancels the transaction, discarding all changes</td>
		</tr>
	</tbody>
</table>

## Starting a transaction

Call `.beginTransaction()` on any [`Surreal`](/docs/reference/javascript/api/core/surreal.md) or [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md) instance to start a new transaction. The returned [`SurrealTransaction`](/docs/reference/javascript/api/core/surreal-transaction.md) object provides all the same [query methods](/docs/reference/javascript/concepts/executing-queries.md) as a regular session, but every operation is executed within the transaction scope.

```ts
const txn = await db.beginTransaction();
```

## Executing queries within a transaction

Use the transaction object to execute queries just as you would on the `Surreal` instance. All operations are held in a pending state until you commit or cancel.

```ts
const txn = await db.beginTransaction();

await txn.create(new RecordId('users', 'alice'))
    .content({ name: 'Alice', email: 'alice@example.com' });

await txn.create(new RecordId('users', 'bob'))
    .content({ name: 'Bob', email: 'bob@example.com' });
```

## Committing changes

Call `.commit()` to apply all pending changes to the database. After committing, the transaction cannot be used again.

```ts
await txn.commit();
```

## Cancelling and rolling back

Call `.cancel()` to discard all pending changes. This is typically done when an error occurs during the transaction. After cancelling, the transaction cannot be used again.

```ts
await txn.cancel();
```

## Retrying on write conflict

Queries executed within a transaction can fail with a write conflict under concurrent load, just like queries outside a transaction. Since `SurrealTransaction` exposes the same query methods as a regular session, you can chain [`.retry()`](/docs/reference/javascript/api/queries/query.md#retry) onto any statement inside the transaction to replay it with exponential backoff.

```ts
const txn = await db.beginTransaction();

await txn.update(new RecordId('accounts', 'alice'))
    .merge({ balance: from.balance - 100 })
    .retry({ attempts: 3 });

await txn.commit();
```

See [Retrying on write conflict](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#retrying-on-write-conflict) for how to configure a connection-wide default.

## Handling errors in transactions

Always wrap transaction logic in a try-catch block to ensure the transaction is cancelled if any operation fails. This prevents partial changes from being committed.

```ts
const txn = await db.beginTransaction();

try {
    const from = await txn.select(new RecordId('accounts', 'alice'));
    const to = await txn.select(new RecordId('accounts', 'bob'));

    if (from.balance < 100) {
        throw new Error('Insufficient funds');
    }

    await txn.update(new RecordId('accounts', 'alice'))
        .merge({ balance: from.balance - 100 });

    await txn.update(new RecordId('accounts', 'bob'))
        .merge({ balance: to.balance + 100 });

    await txn.commit();
} catch (error) {
    await txn.cancel();
    throw error;
}
```

## Best practices

### Keep transactions short

Execute transactions quickly to avoid holding resources longer than necessary. Perform any validation or external API calls before starting the transaction.

```ts
if (!isValidEmail(email)) {
    throw new Error('Invalid email');
}

const txn = await db.beginTransaction();
await txn.create(new RecordId('users', id)).content({ email });
await txn.commit();
```

### Do not reuse transactions

Once a transaction is committed or cancelled, create a new one for subsequent operations.

```ts
const txn1 = await db.beginTransaction();
await txn1.create(record1).content(data1);
await txn1.commit();

const txn2 = await db.beginTransaction();
await txn2.create(record2).content(data2);
await txn2.commit();
```

On a remote WebSocket server, each open client-managed transaction counts toward [`SURREAL_MAX_TRANSACTIONS_PER_CONNECTION`](/docs/reference/cli/surrealdb-cli/environment-variables.md#websocket-config) or [`SURREAL_MAX_TRANSACTIONS_PER_SESSION`](/docs/reference/cli/surrealdb-cli/environment-variables.md#websocket-config) (default 64 each). Exceeding the limit fails with `Too many open transactions`.

## Learn more

- [SurrealTransaction API reference](/docs/reference/javascript/api/core/surreal-transaction.md) for the complete transaction interface
- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) for the query methods available on transactions
- [Multiple sessions](/docs/reference/javascript/concepts/multiple-sessions.md) for running transactions on isolated sessions
