| Updated 25.07.2025 | Languages: EN, FR, CZ, PL |

Contributing guide

Focus on user problems

We do not make code changes to improve code - any change must address a specific user problem or request.

Discuss the plans as early as possible

Please discuss the problem you want to solve and your detailed implementation plan with the project team prior to contributing, to avoid wasted time and additional changes. Acceptance of your contribution depends on your willingness and ability to iterate the proposed contribution to achieve the required quality level, coding style, test coverage, and alignment with user requirements as they are understood by the project team.

Follow project structure, coding style and approaches

./contributing/PROJECT.md has information about the structure of this simplex-chat repository.

./contributing/CODE.md has details about general requirements common for simplexmq and simplex-chat repositories.

CLAUDE.md in the repository root imports these files, this guide and ../scripts/ci/README.md for Claude Code; apps/ios/CLAUDE.md and apps/multiplatform/CLAUDE.md import the README of each app.

Compiling with SQLCipher encryption enabled

Add cabal.project.local to project root with the location of OpenSSL headers and libraries and flag setting encryption mode:

cp scripts/cabal.project.local.mac cabal.project.local
# or
# cp scripts/cabal.project.local.linux cabal.project.local

OpenSSL on MacOS

MacOS comes with LibreSSL as default, OpenSSL must be installed to compile SimpleX from source.

OpenSSL can be installed with brew install openssl@3.0

You will have to add /opt/homebrew/opt/openssl@3.0/bin to your PATH in order to have things working properly

Project branches

In simplex-chat repo

  • stable - stable release of the apps, can be used for updates to the previous stable release (GHC 9.6.3).

  • master - branch for beta version releases (compatible with both GHC 9.6.3 and 8.10.7). Nix builds Android armv7a core library with GHC 8.10.7 and all other core libraries with GHC 9.6.3.

In simplexmq repo

  • master - compatible with both GHC 9.6.3 and 8.10.7.

Development & release process

  1. Make PRs to master branch only for both simplex-chat and simplexmq repos.

  2. To build core libraries for Android, iOS and Windows, push master branch to GitHub.

  3. All libraries should be built from master branch.

  4. To build Desktop and CLI apps, make tag in master branch, APK files should be attached to the release.

  5. After the public release to App Store and Play Store, merge master to stable.

  6. Independently, master branch of simplexmq repo should be merged to stable branch on stable releases.

Branches and PRs

Use change scope (or comma separated scopes) as the first word in the PR names, followed by the colon. Commit name itself should be lowercase, in present tense.

The PR names in simplex-chat repo are used in release notes, they should describe the solved problem and not the change. Possible PR scopes:

  • ios
  • android
  • desktop
  • core
  • docs
  • website
  • ci

We squash PRs, do not rewrite branch history after the review.

For some complex features we create feature branches that will be merged once ready - do not make commits directly to them, make PRs to feature branches.

Pull requests and CI

  1. Open a pull request to master as a draft. On a draft, CI runs the reduced build: one Linux job that builds and tests the changed scopes, and one macOS job only when iOS code changed (CI guide: scripts/ci/README.md).
  2. Select the core tests in a ci-tests block in the description, one hspec match pattern per line. / runs all tests; without a block, the reduced build runs no core tests.
  3. Push the changes for review comments to the same branch; each push runs the reduced build.
  4. Reviewers approve the draft. A draft cannot be merged.
  5. After the approval, mark the pull request ready for review. CI then runs the full matrix, and the pull request is merged by squash once it passes.
  6. A change to a scope without CI tests adds its build and tests to CI in the same pull request.

Differences between GHC 8.10.7 and GHC 9.6.3

  1. The main difference is related to DuplicateRecordFields extension.

It is no longer possible in GHC 9.6.3 to specify type when using selectors, instead OverloadedRecordDot extension and syntax are used that need to be removed in GHC 8.10.7:

{-# LANGUAGE DuplicateRecordFields #-}
-- use this in GHC 9.6.3 when needed
{-# LANGUAGE OverloadedRecordDot #-}

-- GHC 9.6.3 syntax
let x = record.field

-- GHC 8.10.7 syntax removed in GHC 9.6.3
let x = field (record :: Record)

It is still possible to specify type when using record update syntax, use this pragma to suppress compiler warning:

-- use this in GHC 9.6.3 when needed
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}

let r' = (record :: Record) {field = value}
  1. Most monad functions now have to be imported from Control.Monad, and not from specific monad modules (e.g. Control.Monad.Except).
-- use this in GHC 9.6.3 when needed
import Control.Monad

This PR has all the differences.

Improving compatibility between versions for remote desktop connection

UI already can handle failed JSON conversions of chats and chat items, and it helps both debugging and downgrading.

While we can increase versions for remote connections to make different versions incompatible, it degrades remote connection UX, as in many cases users can't upgrade mobile or desktop apps at the same time because of different release cycles.

It is especially problematic for Android app users, as they can only downgrade via Export/Import - older version can't be installed on top of newer version.

PR #6105 improved it by:

  • adding CInfoInvalidJSON constructor, so that chats that cannot be parsed will show as "invalid chat" via remote connection (as when UI has field not present in API),
  • changing JSON parsing for CIContent, so that it falls back to CIInvalidJSON in platform-specific JSON parser.

To avoid "invalid" chats in the list we need to maintain forward compatibility on JSON encoding level of AChat type and subtypes:

  • add new fields as optional to these types,
  • add omittedField method to FromJSON instances of types of new fields to provide a default value, where appropriate,
  • define primitive non-optional fields as newtype with omittedField in JSON instance.

To avoid fallback to invalid JSON in chat items we should do the same for ChatItem type and subtypes. It's especially important when adding fields to types used for all CIContent, as otherwise all items will be broken.