# zulip-mobile: the React Native Zulip app kept as a reference

> 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.

**zulip/zulip-mobile** — Legacy Zulip mobile apps for Android and iOS

- Repository: https://github.com/zulip/zulip-mobile
- Website: https://zulip.com/apps/
- Stars: 1,352 · Forks: 672
- Language: JavaScript
- License: Apache-2.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/zulip-zulip-mobile

## 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:

```bash
git clone https://github.com/zulip/zulip-mobile.git
cd zulip-mobile
yarn
yarn start
```

The 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:

```bash
yarn android
```

That 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.

## 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.

## FAQ

### 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.

## Sources

- [License: Apache-2.0](https://github.com/zulip/zulip-mobile/blob/main/LICENSE)
- [Project website](https://zulip.com/apps/)
- [README](https://github.com/zulip/zulip-mobile/blob/main/README.md)
- [Releases](https://github.com/zulip/zulip-mobile/releases)
- [zulip/zulip-mobile on GitHub](https://github.com/zulip/zulip-mobile)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/zulip-zulip-mobile
