Migrate from Convex
Move a Convex deployment to Sovaria from its snapshot export: Convex Auth users with their passwords, every table as a document collection with ids kept, file storage, and how queries, mutations and actions map.
How Convex maps to Sovaria
| Convex | Sovaria |
|---|---|
| Tables of documents | Document collections with the same names and document ids (Documents) |
useQuery (reactive queries) | Live listeners: onSnapshot re-runs when documents change (Live listeners) |
| Mutations | Document writes, or Transactions, batches and field transforms for several at once |
Actions (fetch, Node packages) | Edge Functions |
| Scheduled functions and crons | Function schedules and triggers (Event triggers) |
| Convex Auth (Password, OAuth, magic links) | Sovaria Auth with the same methods; password users keep their passwords |
File storage (_storage) | A storage bucket (Storage) |
| Access checks inside functions | Access policies and rules, checked on every request (How access is decided) |
| Vector search | Vector search and embeddings |
1. Export from Convex
# In your Convex project. --prod for the production deployment.
npx convex export --path snapshot.zip --include-file-storage --prodThe ZIP holds one documents.jsonl per table, the _storage files and their metadata. It contains your users' password hashes: keep it private and delete it after the import.
2. Import
sov login && sov scope pick # the Sovaria project to import into
sov import convex --snapshot snapshot.zip --dry-run # reads everything, writes nothing
sov import convex --snapshot snapshot.zip| Part | What happens |
|---|---|
Users (--only auth) | Users from Convex Auth's users and authAccounts tables. Password users keep their passwords (Convex Auth hashes them with Lucia scrypt, which Sovaria checks and re-hashes with bcrypt at the first sign-in). OAuth users sign in with the same provider once you turn it on (Social sign-in). Anonymous users without an email are skipped and counted. No emails are sent. |
Data (--only data) | Every table becomes a collection with the same name. Document ids are kept and _creationTime stays a field. Convex Int64 values become numbers and bytes stay { "$bytes": … }. Convex Auth's session, token and code tables are never copied. |
| User ids | Convex user ids become Sovaria user ids. Every field that held an old user id (userId, ownerId, arrays of members) is rewritten to the new one, so auth.uid() matches (--keep-ids turns this off). The map is saved in sovaria-import-uid-map.json. |
Files (--only storage) | Every _storage file goes to the bucket convex-storage (--to-bucket for another), named by its storage id, with its content type. Fields that hold a storage id still find the file there. |
| Components | Tables of Convex components (_components/…) belong to the component's own code and are skipped; the report lists them. |
3. Decide who may read and write
In Convex, access checks live inside each query and mutation. In Sovaria, collections are closed to app users until a rule allows them, and the same check applies to every client and every live listener. Documents are rows of sov_documents in your project database, so the rules are one line of SQL each. Imported documents record the importing team member as their creator, so compare the field that names the user (userId in most Convex apps) rather than owner_id:
-- Each signed-in user reads and writes only their own orders
create policy orders_own on sov_documents for all
using (collection = 'orders' and data->>'userId' = auth.uid()::text)
with check (collection = 'orders' and data->>'userId' = auth.uid()::text);
-- Anyone may read the product catalogue
create policy products_read on sov_documents for select using (collection = 'products');More patterns, and per-collection policies under Policies: Documents, Security recipes.
4. Point your app at Sovaria
const orders = useQuery(api.orders.listMine)
const place = useMutation(api.orders.place)
await place({ items })- Sign-in:
sov.auth.signIn(email, password)replacessignIn("password", …)(Email and password). - Logic that ran in a mutation and must stay on the server (prices, stock) moves to an Edge Function or a database function.
- Prefer SQL tables? Create them in the Table Editor and copy the documents across with a function; collections and tables can live side by side.
5. Check, then cut over
sovaria-import-report.jsonhas the users imported, skipped and failed, documents per collection, renamed ids and skipped components.- Sign in as a migrated user with their old password, and as a second user: each sees only their own documents.
- Freeze writes in Convex, export and import once more (re-importing updates documents with the same id), then switch the app.