Unique Constraints

Guarantee that a field's value is unique across a collection — no two users with the same email, no duplicate slugs — enforced by the database itself.

Declaring a constraint

Constraints are declared per collection from the dashboard. Open the collection, click Constraints, enter the field name (e.g. email), optionally mark it case-insensitive, and add it. From that point, every write to the collection is checked.

One field in the dashboard, composite-ready underneath

The dashboard declares single-field constraints. The backend model already stores a constraint as a set of fields, so composite (multi-field) uniqueness is supported at the platform level and will surface in the dashboard as it's rolled out.

Case-insensitive uniqueness

For values like emails and usernames, enable case-insensitive when declaring the constraint. The database then treats values that differ only in case as the same — [email protected] and [email protected] cannot both exist.

Enforcement

Any insert, update, or upsert that would create a duplicate is rejected with a conflict. In the SDK, catch KoolbaseConflictException:

lib/database.dart
Dart
try {
  await Koolbase.db.collection('users').insert({
    'email': email,
    'name': name,
  });
} on KoolbaseConflictException {
  showError('That email is already registered.');
}

React Native and Web

The TypeScript SDKs — @koolbase/react-native and @koolbase/js — throw KoolbaseConflictError on the same conflict — catch it with e instanceof KoolbaseConflictError. (There, conflicts surface on upsert; insert and update are optimistic and sync in the background.)

Upsert becomes atomic

When an upsert's match fields are backed by a unique constraint, the operation is race-free: the constraint makes a duplicate impossible, so two simultaneous upserts on the same key resolve to one insert and one update rather than two rows.

lib/database.dart
Dart
// 'email' has a unique constraint -> this upsert is atomic
final result = await Koolbase.db.upsert(
  collection: 'profiles',
  match: {'email': email},
  data: {'last_seen': DateTime.now().toIso8601String()},
);

print(result.created); // true if inserted, false if updated

Without a covering constraint, upsert is best-effort — still a single server call, but two concurrent inserts could race. Declaring the constraint is what makes it fully race-free.

Adding a constraint to existing data

If the collection already contains records that would violate the constraint, declaring it is refused and the conflicting values are reported back to you. Koolbase never merges or deletes records on your behalf — resolve the duplicates first, then declare the constraint.

Empty and missing values are not constrained

Records where the field is absent or null are not subject to the constraint — you can have many records without the field. Uniqueness applies only among records that actually have a value. Soft-deleting a record also frees its value, so the same value can be used again afterward.