FAQ

Frequently asked questions about Evolu.

Questions

What is the SQLite database size limit?

The size limit depends on the storage quotas and eviction criteria of the browser or platform in use. For detailed information, refer to the Storage quotas and eviction criteria documentation.

How can I check the current database filesize?

Use exportDatabase method on Evolu instance.

const database = await evolu.exportDatabase();
const sizeInBytes = database.length;
console.log(`Database size: ${sizeInBytes} bytes`);

How to delete OPFS Sqlite in browser?

To clear the OPFS (Origin Private File System) SQLite database:

  1. Install the OPFS Explorer Chrome DevTools extension
  2. Disable JavaScript in your browser
  3. Reload the page
  4. Open DevTools and navigate to the OPFS Explorer tab
  5. Remove the SQLite database file
  6. Re-enable JavaScript
  7. Reload the page

Evolu uses Kysely, the type-safe SQL query builder for TypeScript.

Kysely is not an ORM. It does not have the concept of relations. Kysely is a query builder—it builds the SQL you tell it to, nothing more, nothing less. However, there is a way to nest related rows in queries. It's described here, and Evolu supports it.

TL;DR: JSON type with subselects. With this combination, we can write efficient queries with nested relations. Evolu automatically parses stringified JSONs to typed objects and ensures that no regular strings are mistakenly parsed as JSON.

How do I store device-specific settings that shouldn't sync?

Use a separate local-only Evolu instance with transports: [] for device-specific settings like UI preferences, onboarding state, or account management flow:

const PreferencesId = id("Preferences");
type PreferencesId = typeof PreferencesId.Type;

const DeviceSchema = {
  preferences: {
    id: PreferencesId,
    // whatever
  },
};

// Local-only instance for device settings (no sync)
const deviceEvolu = createEvolu(evoluReactWebDeps)(DeviceSchema, {
  name: SimpleName.orThrow("MyApp-Device"),
  transports: [], // No sync - stays local to device
});

This approach gives us:

  • Type safety with the same Evolu APIs
  • Schema evolution for local settings
  • Reactive queries for local state
  • Complete separation from synced user data

How do I store owner-specific data that shouldn't sync?

Tables prefixed with underscores (_) are local-only within an existing Evolu instance—they're never synced.

Imagine editing a JSON-rich text document. Syncing the entire document on every keystroke would be inefficient. Instead, save drafts to a local-only table first:

const Schema = {
  // Regular synced table
  document: {
    id: DocumentId,
    title: NonEmptyString1000,
    content: NonEmptyString,
  },
  // Local-only table (underscore prefix)
  _documentDraft: {
    id: DocumentId,
    title: NonEmptyString1000,
    content: NonEmptyString,
  },
};

// Save draft locally on every keystroke (no sync)
evolu.upsert("_documentDraft", {
  id: documentId,
  title,
  content,
});

// When ready to sync (e.g., on blur, route change)
evolu.update("_documentDraft", { id: documentId, isDeleted: true });
evolu.upsert("document", { id: documentId, title, content });

Evolu batches mutations in a microtask and runs them in a transaction, ensuring atomicity and no data loss. Saving to local-only tables won't block the main thread since Evolu uses Web Workers. In React Native, InteractionManager.runAfterInteractions will be used (coming soon).

How do I integrate with external systems that have their own IDs?

Use createIdFromString to convert external IDs into valid Evolu IDs:

import { createIdFromString } from "@evolu/common";

// Convert external API ID to Evolu ID
const evoluId = createIdFromString("user-api-123");

upsert("todo", {
  id: evoluId,
  title: "Task from external system",
});

// With table branding for type safety
const todoId = createIdFromString<"Todo">("external-todo-456");

This ensures that multiple clients creating records with the same external identifier will generate the same Evolu ID.

Important: This transformation uses the first 16 bytes of SHA-256 hash of the string bytes, therefore it's not possible to recover the original external string. If you need to preserve the original external ID, store it in a separate column.

How do I seed initial data like country lists or default categories?

Evolu previously had an automatic seeding feature that ran when an AppOwner was created, but it was removed because Evolu syncs all changes immediately. Every page visit would generate and sync data to the relay, creating unnecessary traffic. Automatic seeding opens a DoS vulnerability where bots or users could repeatedly trigger data creation by simply visiting the page.

Simple approach: Create initial data on explicit user action. For example:

const seedDefaultData = () => {
  evolu.upsert("category", { id: createIdFromString("groceries"), name: "Groceries" });
  evolu.upsert("category", { id: createIdFromString("utilities"), name: "Utilities" });
  evolu.upsert("category", { id: createIdFromString("entertainment"), name: "Entertainment" });
};

// In your UI, provide a button or onboarding step
<button onClick={seedDefaultData}>Set up default categories</button>

Note: Using createIdFromString ensures deterministic IDs, so if a user clicks the button on multiple devices (e.g., after restoring their account but before sync completes), the same data will be upserted rather than duplicated.

Alternative approach: Create Evolu without transports initially, verify the user through a backend service that knows whether it's their first login, then have the client insert data explicitly before enabling sync:

// Start without sync
const evolu = createEvolu(evoluReactWebDeps)(Schema, {
  name: SimpleName.orThrow("MyApp"),
  transports: [],
});

// After backend verification for first-time user
if (isFirstLogin) {
  seedDefaultData();
}

// Then enable sync by recreating with transports

For reference data that rarely changes (like country or currency lists), consider whether it needs to be in the database at all—it might be better as a constant in your code or imported from a package.

Was this page helpful?