zulip-mobile: the React Native Zulip app kept as a reference
Legacy Zulip mobile apps for Android and iOS
At a glance
- What is it?
- The legacy React Native client for Zulip, unsupported since the Flutter app launched on 2025-06-07 and scheduled for archiving, still holds a complete mobile code base worth reading even though no new work belongs here.
- Who is it for?
- Adopt nothing from zulip-mobile at runtime. Use it as a reading reference if you want to see how a React Native chat client was structured, how Zulip packaged beta builds, or how its native Android and iOS layers were wired, and point your own work at the Flutter repository instead.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 108 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What zulip-mobile is today
The first line of the README settles the framing: Zulip Mobile, legacy, unsupported. This codebase was written in JavaScript with React Native and it produced the Android and iOS apps that Zulip shipped for years, superseding two even older apps, zulip-ios and zulip-android. What remains is a full mobile application source tree that is no longer developed, and the README asks readers to keep that in mind because the details below come from when the app was in active development.
The timeline explains why. On 2025-06-07 the Zulip team launched a new mobile app written in Flutter, and the legacy repository was announced as heading for archival, with the tracking issue in the repository at zulip/zulip-mobile issue 5931. The published release history stops earlier than that announcement would suggest: versions 27.235 from 2025-05-21, 27.234 from 2024-04-18 and 27.233 from 2024-02-11 are the last three tags. The last push to the branch landed on 2026-07-07, so the tree is not frozen in place, but nothing in the README promises continued feature work here, and the project points contributors to the Flutter repository instead.
So the useful question is not whether to run this app. It is what a reader can still take from it.
A React Native chat client as a reading exercise
The repository layout describes a conventional React Native application with a native layer on each platform. There are android and ios directories for the native projects, a src directory for application code, static for assets, tools for the shell scripts that the package.json calls, jest and flow-typed for the JavaScript test and type infrastructure, and a babel.config.js, metro.config.js and react-native.config.js at the root. A .buckconfig sits alongside them, a leftover from Buck based builds, and shell.nix suggests a Nix development shell is available for the native toolchain.
Two dependency choices are worth noticing because they explain the shape of the codebase. The navigation packages are not the stock ones. The manifest pins bottom tabs to an npm alias of a Zulip fork, npm:@zulip/react-navigation-bottom-tabs at version 5.11.16-0.zulip.1, and the stack package the same way at 5.14.10-0.zulip.1. That is why the code base can carry behaviour that upstream React Navigation did not offer. A shared internal package, @zulip/shared at 0.0.18, holds logic used across the Zulip client surfaces, and the native stack, camera roll, clipboard, masked view, netinfo and push notification packages are all present in the dependency list.
For an engineer studying mobile architecture, the value is in seeing a real product handle message lists, notification delivery, media handling and account switching in one place, and in seeing how a team forked a navigation library to get there.
Getting the legacy app running locally
The developer guide in the docs directory is the entry point, and the package.json gives the concrete commands. Because the manifest declares Node 10 or newer and the tree ships a yarn.lock, the historical setup is a Yarn install followed by the start script:
git clone https://github.com/zulip/zulip-mobile.git
cd zulip-mobile
yarn
yarn startThe start script runs react-native start, which serves the JavaScript bundle in development and leaves the native apps to pick it up. The postinstall hook runs tools/postinstall, so the install step also prepares native dependencies.
On Android the repo provides a wrapper script rather than asking you to remember the platform invocation:
yarn androidThat maps to tools/run-android, and the sibling scripts build and test the app: tools/android apk for a full APK build, tools/gradle :app:assembleRelease for a release variant without signing keys, and the iOS archive command in the build:ios script. The scripts are the useful part of this repository for anyone who wants the exact sequence rather than a paraphrase of it.
Tests, formatting and static checks
Quality tooling is wired into the package.json as named scripts. The test script delegates to tools/test, formatting goes through tools/fmt with a prettier-full variant for the whole tree, and the underlying tools are Prettier for formatting, ESLint for linting with an .eslintrc.yaml, Flow for types with a .flowconfig, and Jest with a jest.config.js and a jest directory.
The Flow story has two extra scripts worth understanding. test:flow-coverage runs flow-coverage-report over src, excluding test directories and emitting HTML, which tells you how much of the codebase carries type annotations. test:flow-todo runs ESLint with a separate configuration, tools/flow-todo.eslintrc.yaml, that flags files still carrying Flow type annotations that should be removed. A second script, update-libdefs, regenerates the flow-typed library definitions with flow-typed update after a yarn install, which is the step to run when a dependency's type definitions change.
That combination, typed JavaScript with a coverage measure and a ratchet to delete annotations, is the most transferable idea in this repository. A legacy codebase with strict linting and measurable type coverage is a better reference than one without.
How beta builds used to reach devices
The distribution section describes a two channel beta process. On Android, users install the app and join the testing program on Google Play, or, if they do not use Google Play, download an APK from the repository releases, where the latest release including pre-releases is the current beta. On iOS, the route is TestFlight with a public invitation link opened on the device.
The repository also carries a fastlane directory, which is the tool such pipelines are usually built on, plus a tools directory for the release scripts. The release tags carry version strings in the 27.x range, and the version field inside package.json reads 1.0.0, which tells you the manifest version was not the published app version. That is normal for a private package, since the manifest declares private true, but it means you cannot read the shipped version from the file a developer edits daily.
All of this is now history. The Play testing programme and the TestFlight link describe a process for an app the team no longer updates.
Why this repository is the wrong place to build
The failure mode here is not subtle, but it is easy to land on by accident. A developer searching for the Zulip mobile app finds a popular repository with real releases, a full developer guide and an inviting contribution section, and the notice about unsupported status is a short paragraph near the top. Anyone who starts a feature here, opens a pull request, or plans an internal fork on this base is building on a codebase the project itself has superseded.
The dependency pins make that concrete. React Native and Expo are held at the versions of the era, with expo at ^45.0.0 and the React Navigation forks at fixed patch versions, and the Node floor is still 10. Even setting the archival plan aside, a fork of this tree inherits a dependency set that no longer receives the same upstream attention as the Flutter app does.
There is one more cost. The application code is entangled with the old product decisions, from the notification stack to the media handling to the navigation forks. Reading it teaches you how that app was built. It does not tell you how the current Zulip mobile client is built, and the Flutter repository is where that lives now.
Licence, history and what you may reuse
The code is under the Apache License, Version 2.0, with the manifest declaring Apache-2.0 as well. The copyright line names Kandra Labs, Inc. and contributors for 2016 to 2024, and Dropbox, Inc. for 2016, which reflects the two organisations that carried the mobile client over that period. The README also states that some works from third parties are included under other free and open source licences and are redistributed under the terms they were received under.
Apache-2.0 is permissive and carries an explicit patent grant, so reading the code or lifting a file into a project of your own is straightforward from a licensing standpoint. Whether any given file carries additional obligations from a bundled third party component is something to check per file rather than assume, and that is a question for your own legal review rather than one this article can settle.
Judged as a reference, the value is in the parts that are hard to reconstruct from scratch: a worked example of typed React Native with a measurable annotation coverage ratchet, a legacy friendly native build, a two channel beta distribution flow, and a maintained set of forked navigation packages that reveal what a chat interface needed from its navigation layer.
Editorial conclusion
Adopt nothing from zulip-mobile at runtime. Use it as a reading reference if you want to see how a React Native chat client was structured, how Zulip packaged beta builds, or how its native Android and iOS layers were wired, and point your own work at the Flutter repository instead. Do not start a feature on this tree, and do not copy its dependency pins into a new project without checking them, since the React Navigation packages are Zulip forks and the Node floor is still 10. Verify first that the archive issue referenced in the README has not already closed and that the release you pick still matches the store listing you intend to test against.
Frequently asked questions
Is the zulip-mobile React Native app still supported?
No. The README describes it as an unsupported legacy app. A new Flutter based Zulip mobile app launched on 2025-06-07, the repository is expected to be archived, and contributions are directed to the Flutter repository instead.
How do I build and run the legacy zulip-mobile app locally?
Install the dependencies with yarn, start the bundler with yarn start, and use the repo's own wrapper for Android with yarn android, which runs tools/run-android. Release builds go through tools/android apk or tools/gradle :app:assembleRelease, and the package.json requires Node 10 or newer.
What testing and linting tools does zulip-mobile use?
The package.json maps test to tools/test and formatting to tools/fmt, on top of Prettier, ESLint, Flow with a .flowconfig, and Jest. It also exposes test:flow-coverage for annotation coverage and test:flow-todo to flag files that still carry Flow types.
Which navigation library does the legacy zulip-mobile app use?
It uses Zulip's own forks of React Navigation rather than the stock packages, pinned as npm aliases: npm:@zulip/react-navigation-bottom-tabs at 5.11.16-0.zulip.1 and npm:@zulip/react-navigation-stack at 5.14.10-0.zulip.1.
Under which licence is the zulip-mobile source code released?
It is released under the Apache License, Version 2.0, with copyright held by Kandra Labs, Inc. and contributors for 2016 to 2024 and by Dropbox, Inc. for 2016. The README notes that some third party works are redistributed under the licences they were received under.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zulip-zulip-mobile)Was this useful?