2.3.0
Appwrite 2.3 removes dev keys, builds every console link from one _APP_CONSOLE_URL, reports execution duration the way the caller experienced it, adds Jaspr to Sites and a nightly channel for self-hosting, and folds in two weeks of fixes across auth, databases, functions, messaging, storage and the installer. It ships with the 1.1.159 self-hosted Console (was 1.1.96). Upgrading from 2.2.0 is a single command and a migration that touches every project's users and identities collections.
Highlights
- Dev keys are gone. Creating dev keys was already switched off, but existing ones were still accepted and still bypassed abuse limits, CSRF protection and origin and redirect checks, including the OAuth2 success and failure URLs. The dev key endpoints, the
X-Appwrite-Dev-Keyheader anddevKeyquery parameter, thedevKeys.read/devKeys.writescopes and thedevKeyscollection are removed;migratedrops the collection and strips the scopes from API keys, functions and sites. Until you run it, the API keeps serving. (#13696, #13800) - One setting for console links.
_APP_CONSOLE_URLis the origin every console link is built from: OAuth2 success and failure defaults, magic URL, build and webhook-failure emails, VCS comments and commit statuses, and VCS installation callbacks, which now land on/projects/:projectId/settings. Leave it empty to use_APP_CONSOLE_DOMAIN(or_APP_DOMAIN) as before; set it when the console runs on a different host from the API._APP_CONSOLE_URL_SCHEMEand the legacy/console/...link shapes are gone, and, when the console is served from a different host than the API,/invite,/login,/recoverand/registerredirect to the matching console pages again. (#13689) - Execution duration includes cold start.
durationon an execution is now the time the caller waited, however it was triggered. Executions created through the API or run asynchronously used to report only the time after the runtime was warm, so a cold start that took 6 seconds could show as 311ms in the Console. Expect higher numbers for cold-started executions and in compute usage. (#13754) - Native ID token sign-in keeps the token.
POST /v1/account/sessions/id-tokenstores the verified ID token on the identity asproviderIdToken(encrypted at rest, refreshed on every sign-in), so clients can read claims Appwrite does not model, such as Google'slocale. The Console shows it on the user's identities. Theproviderparameter now only accepts providers that support ID tokens (Apple and Google) and gets its ownIdTokenProviderenum in the SDKs. (#13698, #13759) - Jaspr on Sites. Jaspr is available as an SSR framework, built with
jaspr buildon the Flutter 3.44 runtime. Addflutter-3.44to_APP_SITES_RUNTIMESto use it (the default list hasflutter-3.29). Pick it by hand for now; repository detection does not suggest it yet. (#13710, #13773, appwrite/vibes#407) - A nightly channel.
installandupgradetake--channel nightly, or set_APP_VERSION=X.Y-nightly, to follow the newest CI-green commit on the active release branch, rebuilt daily. Nightly is unsupported and stays within one patch line; stable remains the default. (#13659) - Console 1.1.159, self-hosted build. Site preview authorization works, self-hosted member invites offer Owner and Developer roles, impersonation loads files and avatars as the impersonated user, the datetime picker shows and switches time zones, and Jaspr is selectable.
Fixes
Console
- Site preview domains send visitors to a console
/auth/previewpage that exists, instead of the console's 404. (appwrite/vibes#366, #13714, fixes #13713) - Member invitations and edits on self-hosted offer the organization-wide Owner and Developer roles and save the one selected. (appwrite/vibes#387, #13734)
- While impersonating a user, file previews and downloads, avatars and deployment downloads load as that user instead of returning 401. (appwrite/vibes#393, appwrite/vibes#394)
- The datetime picker accepts hours of 10 or more and shows and switches the time zone. (appwrite/vibes#361, appwrite/vibes#356)
- Enum values containing commas are kept intact, and creating a row with a custom ID creates it instead of trying to update a missing row. (appwrite/vibes#363, appwrite/vibes#391)
- The users list pages past 5,000 users, and deleted users and teams show a proper not-found page. (appwrite/vibes#425, appwrite/vibes#441)
- Function and site pages render when a referenced deployment is missing; the deployment details panel shows on small screens. (appwrite/vibes#386, appwrite/vibes#442)
- The function create wizard takes its domains from console variables and no longer pins the domain to a branch. (appwrite/vibes#379, appwrite/vibes#416)
- Deleting function executions and site logs works again, and the logs Method filter matches the stored request method. (appwrite/vibes#423, appwrite/vibes#422)
- Usage and activity pages keep working when ClickHouse is unavailable. (Console 1.1.139)
- Self-hosted shows the S3 settings, and hides the database Specification tab, account Applications and the old console link. (appwrite/vibes#362, appwrite/vibes#367, appwrite/vibes#371)
Auth and users
POST /v1/account/tokens/email,/tokens/magic-urland/tokens/phonereturn409 user_already_existswhen the givenuserIdbelongs to another user, instead of a 500. (#13790)- Refreshing an OAuth2 session with
PATCH /v1/account/sessions/:sessionIdalso refreshes the identity's photo, so providers with expiring avatar URLs stay current. (#13671) - Authentik sign-in opens the consent screen instead of an Authentik 404. (#13819, fixes #9567)
- Flutter iOS apps are recognized as mobile clients, so sessions and OTP and magic URL emails name the app. (#13739, fixes #9372)
- A team member always sees their own membership, whatever the membership privacy policy, so members can find their
membershipIdand leave a team. (#13786, fixes #13767)
Databases and TablesDB
- A transaction whose commit fails a permission check is marked
failedand returns 401, instead of staying incommitting. (#13703) - Cleanup of expired transactions removes their logs in batches, so more than 5,000 expired transactions no longer leave logs behind. (#13563)
- The relationship column update lives at
PATCH /v1/tablesdb/:databaseId/tables/:tableId/columns/relationship/:key, like every other column type; the oldcolumns/:key/relationshippath keeps working. (#13822, fixes #9022) - Sending an object on the child side of a one-way one-to-one relationship explains that the value can't be set from that side, instead of "array given". (#13738, fixes #8345)
Functions and Sites
- A deployment reports
readyonly after the function points at it, so executing right after seeingreadyno longer returns 404. (#13741) - Domains pinned to a git branch bind to that branch's newest build: activating a deployment repoints them, function builds on non-production branches bind them, and a new rule no longer starts on another branch's build. Rules already stuck bind on their branch's next build. (#13795)
- Redeploying a function or site whose code lives in a subdirectory builds from the configured root directory, and
APPWRITE_VCS_ROOT_DIRECTORYis set for push and redeploy builds. (#13574, fixes #13587) - When several functions subscribe to one event, a failing function no longer stops the others from running. (#13661, fixes #7205)
- Build logs containing binary output or a split multibyte character keep updating instead of failing to save. (#13706)
- A GitHub push for a repository whose function or site was deleted is skipped, instead of failing the whole webhook. (#13793)
- Pull request comments show the building image for a site deployment in
processing, not a broken image. (#13771) - GitHub App installations on private profiles list their repositories;
_APP_VCS_GITHUB_PRIVATE_KEYaccepts a PEM, a base64-encoded PEM or a PEM with escaped newlines; a top-level root directory named0is cloned correctly. (#13695, #13738)
Messaging
- An email to a single recipient keeps them in the
Toheader; messages with several recipients still send everyone as BCC. (#13534, fixes #12365, #10474) - Concurrent sends in the mails and notifications workers each use their own SMTP session, instead of sharing one socket. (#13722)
- Addresses no provider can deliver to (an invalid domain, a malformed sender or reply-to) are skipped instead of retried, and display names such as
Doe, Johnare quoted so the mail is delivered. (#13831) - Registering a push token for a session that already has a target updates that target instead of adding a second one, so a rotated token no longer delivers messages twice. Re-registering the same token returns the existing target, and targets marked expired are no longer sent to. (#13836)
PUT /v1/account/targets/:targetId/pushreturns409 user_target_already_existswhen the identifier is taken, instead of a 500. (#13789)
Storage
- File responses always include
encryptionandcompression, fixing SDKs that crashed on a missing field. (#12405, fixes #11647) - Uploads that sniff as their container keep their real type:
.jar,.aar,.kmz,.3mf,.usdz,.xpi,.msix,.ttfand.otf. Files uploaded before keep their stored type. (#13825, fixes #9418) HeadObjectthrough the S3-compatible API works for clients that sign it withx-id=HeadObject(such as boto3) when a proxy rewrites HEAD to GET. (#13744)
Domains and certificates
- Deleting a rule no longer removes the certificate of a new rule for the same domain when the delete job runs late. (#13570)
- A domain verified by the API is not checked again by the certificates worker, so a DNS hiccup can't flip a just-verified rule to unverified. (#13765)
- Rules saved without a project no longer fail in the certificates worker after verification. (#13709)
API and GraphQL
- Sending
nullfornameon account create,scheduleon function create orpathon execution create applies the default instead of returning a 500. (#13707) - GraphQL accepts JSON array literals, keeps nested variables and strings, and keeps integer literals exact. (#13667)
PATCH /v1/presences/:presenceIdreturns404 presence_not_foundwhen the presence is deleted or expires mid-update, instead of a 500. (#13792)
Installer and workers
- MongoDB starts reliably on a fresh volume; it initialises the data directory on a separate port instead of racing the real server for 27017. (#13593)
migraterestores the five email metadata attributes onuserswhen an earlier V23 step failed, which made every user write fail withUnknown attribute: "emailCanonical". (#13820, fixes #11906)installandupgradetake--domainto set_APP_DOMAINand_APP_DOMAIN_TARGETin headless installs. The CLI installer now sends the same anonymous install report as the web installer, including--no-startand loopback installs; setDO_NOT_TRACK=1to opt out. (#13844)
Under the hood
- utopia-php/queue 2.2 → 5.0. Each queue consumer now holds two dedicated Redis connections, stranded jobs are recovered while a worker is idle, and worker concurrency is read as
coroutines;_APP_WORKER_MAX_COROUTINESstill works. (#13762, #13809, #13814) - Redis cache entries are stored with igbinary instead of JSON, about a quarter of the size for lists. Old entries read as misses and refill on first use, so expect a short burst of misses after the upgrade. (#13732)
stats-resourcesspreads project counts across the interval instead of queuing every project at once. (#13669)- New projects no longer get a project-database
executionscollection; executions have been ClickHouse only since 2.2. (#13699) - The Utopia libraries
agents,bus,lock,psr7,telemetry,compression,system,smtpandpoolsnow live inpackages/and load directly; no version changes. The agents client no longer leaks a connection per call. (#13700, #13715, #13718, #13723, #13731, #13798, #13804, #13829, #13845, #13794) - OpenAPI specs:
x-appwrite.idGeneratorremoved, aliasdescrenamed tosummary, theProjectPathsecurity scheme replaced byx-appwrite.config. (#13742, #13745, #13757) - Compose orchestrator 2.2.0 → 2.2.2, Autogravity 0.0.10 → 0.0.11 with
MODEL_BACKEND=focalnet, base imageappwrite/base2.0.5 → 2.1.0 (adds igbinary), utopia-php/database 7.3.11, vcs 5.5, messaging 2.5, user-agent 0.1.2, detector 0.2.8, cdn 0.0.12, schedule 0.3. (#13720, #13753, #13732, #13738, #13851, #13739, #13773, #13760, #13746)
Removed
- Dev keys: the endpoints, the
X-Appwrite-Dev-Keyheader anddevKeyparameter, thedevKeys.read/devKeys.writescopes, theDevKeymodel and thedevKeyscollection. (#13696) _APP_CONSOLE_URL_SCHEMEand the legacy/console/project-{region}-{id}/...link shapes. Use_APP_CONSOLE_URL. (#13689)- The
/cardweb redirect; the new console has no matching page. (#13689)
Install
docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:2.3.0
Upgrade
Upgrading from 2.2.0 uses the same command with --entrypoint="upgrade", then run the migration:
cd appwrite
docker compose exec appwrite migrate
The migration drops the devKeys collection and project attribute from the console database, removes the dev key scopes from API keys, functions and sites, and in every project adds providerIdToken to identities and a passwordPwned attribute and index to users, restoring any missing email metadata attributes on the way. Its run time grows with the number of projects. The upgraded compose file adds an appwrite-mqtt container and has Traefik publish ports 8883 and 8084; set _APP_MQTT_PORT and _APP_MQTT_WSS_PORT in .env if those are taken. Remove _APP_CONSOLE_URL_SCHEME from your .env, and set _APP_CONSOLE_URL if your console is served from a different host than the API. To roll back to 2.2.0, stop the workers and let queued jobs drain first, since the older queue client can't recover jobs the new one has reserved. Back up your data first.
Contributors
Thank you to everyone who contributed to 2.3.0:
@HarshMN2345 @ChiragAgg5k @Meldiron @levivannoort @loks0n @TorstenDittmann @ArnabChatterjee20k @imtia33 @jaysomani @deepshekhardas @abnegate @lohanidamodar