What is happening
On August 20, 2026, Instant announced the end of Instant Cloud. These are the facts from the announcement:- Instant closes new signups soon.
- Instant Cloud apps stop on August 31, 2027.
- Backups stay available until August 31, 2028.
- Instant refunds subscriptions that started after July 31, 2026.
- Instant is open source. Instant recommends that you self-host it.
Your two options
1. Self-host Instant. Instant is open source, so you can run it yourself. The stack is large. To self-host Instant, you operate:- a Clojure/JVM server on PostgreSQL 16 (with
wal_level = logicaland thepg_hint_planextension); - a reverse proxy;
- an email provider (Postmark or SendGrid);
- the Java heap, tuned with
JAVA_OPTS.
Why Pylon fits
Pylon shares Instant’s model. Most of your app maps directly:- a schema of entities with typed fields and relations;
- permissions as code (per-entity, per-action boolean expressions that run on the server);
- a live client with reactive queries against a local replica, plus optimistic writes that reconcile;
- built-in auth (email codes and OAuth) and presence/rooms for ephemeral state.
- Hosted server functions. Instant runs logic on the client behind permission
rules. For server logic, you run your own backend with the Admin SDK. Pylon
runs your
query,mutation, andactionfunctions for you, each in a transaction. - Server-side rendering. Pylon uses file-based routes (
app/page.tsx). One binary serves the frontend and the API. - A wider auth set: password, magic code, about 25 OAuth providers, passkeys, and TOTP.
- Vector search and aggregates in the query layer.
Concept map
Migrating your schema
Instant declares entities and links separately. Pylon declares an entity with its fields, and it expresses a relation as a foreign-key field. Instant (instant.schema.ts):
app.ts):
- Instant’s
.unique()and.indexed()map to Pylon’s.unique()and theindexesoption. - A one-to-many link becomes a
field.id("Parent")on the many side. Add an index on that field for query speed. - Instant creates
idautomatically. Pylon does the same. Instant’s$usersmaps to a PylonUserentity. Instant’s$filesmaps to Pylon’s file storage.
Migrating your permissions
Do this step carefully. It is a security change, not only a syntax change. Instant allows by default. A namespace or action with no rule is open. Pylon denies by default. An entity with no policy is fully blocked. So you write apolicy() for every entity. This includes entities that had no
Instant rule, because those were open. For each one, decide whether it is truly
public (allow: "true") or was open by accident.
The rule shapes match almost one to one.
Instant (instant.perms.ts):
policy(...) in app.ts):
view/create/update/deletemap toallowRead/allowInsert/allowUpdate/allowDelete.auth.idmaps toauth.userId. Pylon policies have noauth.email. Gate onauth.userId,auth.roles,auth.hasAnyRole(...), orauth.tenantId.data.*is the row in both systems. Inline Instant’sbindmacros.- To lock an immutable field, use
field.string().owner()in Pylon. It stampsauth.userIdon insert. It rejects a false owner value. It locks the value on update. You do not need Instant’snewData.x == data.xrule.
Migrating reads and writes
Reads. Instant nests linked namespaces in the query object. Pylon uses aninclude map.
db.transact(db.tx...) becomes a client hook (optimistic) or
a server function.
Migrating your data
Use Instant’s export. Do not read the triple store directly. 1. Export from Instant. The backup is a zip file. It contains one NDJSON file per entity, aconfig.json (schema and rules), and a files/ folder.
db.query({ profiles: {}, posts: {} })
reads everything, because the admin token bypasses permissions.
2. Convert ids and relationships. Instant ids are UUIDs. Instant links are
separate triples. Pylon ids are 40-character lowercase hex strings. So you
cannot reuse Instant’s ids directly, and Pylon rejects a UUID with INVALID_ID.
Use one of two methods:
- Derive the id (simplest). Convert each Instant UUID to a Pylon id with a fixed
function. Apply the same function to every foreign key.
sha1(uuid)gives 40 lowercase hex characters, so it fits Pylon’s format. It needs no lookup table, and the relationships stay correct. Derived ids are not time-ordered, so sort by yourcreatedAtfields. - Map the id. Generate a new Pylon id per row with
generateId()from@pylonsync/sync. Keep an old-UUID-to-new-id map. Rewrite the foreign keys in a second pass.
field.id
field during the conversion.
3. Import into Pylon. The admin batch endpoint loads rows quickly and
bypasses per-entity policies. It needs an admin token. Send batches of a few
hundred rows.
files/ folder. Upload them
to Pylon’s file storage. Rewrite the stored file paths.
5. Users. Import each Instant $users row into your Pylon User entity. Key
each user by email. Sessions do not transfer. Both systems use passwordless
email auth, so each user signs in again with a magic code. The email links the
user to the imported data. You do not migrate passwords.
Migrating auth
Instant defaults to magic codes. Pylon has the same passwordless email flow, so the user experience stays the same.signInWithIdToken and createAuthorizationURL
become a redirect to GET /api/auth/login/:provider?callback=<url>. Pylon
supports Google, GitHub, Apple, Microsoft, and about 20 more providers. Pylon
also supports passkeys and TOTP if you want to add them.
What does not map cleanly
Check these points before you commit:- Per-viewer field permissions. Instant’s
fieldsblock shows a field to its owner and hides it from others, while the row stays public. Pylon hides a field from all clients (field.serverOnly()or.syncOmit()), not per viewer. To keep per-viewer access, serve the field through a server function, or move it to its own owner-scoped entity. - Schemaless data. Instant lets you add arbitrary attributes and deep-merge into
JSON with
merge(). Pylon is schema-first. Freeform data goes in afield.json()field. An update replaces the whole field, so there is no per-key merge. - Sessions. Sessions do not transfer. Users sign in again. This is a non-issue for passwordless email.
- Instant-specific query features. Deep InstaQL nesting and
$users/$filestricks need a rewrite to Pylon’sincludeand itsUserand files model.
Get started
- Create a new Pylon app:
npm create @pylonsync/pylon@latest my-app. - Rewrite the schema (
entityandfield). Write apolicy()for every entity. Remember default-deny. - Run the export, conversion, and
/api/batchimport against a staging app first. - Point the client at Pylon:
init, the auth calls,db.useQuery, anddb.useEntity. - Run
pylon deploy, or self-host the single binary.