Zulip URLs

This page details how to properly construct and parse the URLs that the Zulip web app uses for various types of views.

Because other clients needs to be able to resolve and process these links in order to implement equivalent behavior that navigates directly in say the mobile apps, it's important to have a clear specification of exactly how these URLs work.

Essentially all of the data is encoded in the URL fragment (#) part of a URL; the protocol, host and path will just be the canonical URL for the Zulip server (In these examples, https://zulip.example.com/).

Message feed views

Most links in Zulip are to message feed views, and for that reason these have the most developed syntax and legacy behavior.

Message feed URLs always start with #narrow/, follow by one or more search operator/operand pairs, separated by /s. The operator may be negated by putting a - at the start of it. For example:

https://zulip.example.com/#narrow/is/starred/sender/17/-channel/14

is the feed of starred messages sent by user ID to everywhere but channel 14. The search documentation covers the valid operators and their meaning.

See also the relevant message formatting documentation for details on Markdown representations of Zulip-internal links that will be translated into HTML containing links that use these URLs.

Here, we describe some special encoding rules.

Operand encoding and decoding

Strings in operands are URL-encoded, and then additional substitution rules are applied to avoid over-zealous browser handling of certain characters in the URL fragment:

  • % => .
  • ( => .28
  • ) => .29
  • . => .2E

They can decoded by applying the reverse transformation: Replace all . characters with %, and then do standard URL-decoding.

Encoding channels

Channel operands must be encoded in one of the two modern fully supported formats:

  • 42: Just the ID of the channel. Clients should simply parse the channel ID to look up the channel, which is of course not guaranteed to be accessible to the acting user or even exist.
  • 42-channel-name. The ID of the channel, with a human-readable hint of the channel name. Clients generating Zulip URLs are recommended to include channel name hints where there is a readable URL-encoding of the channel name, but to skip doing so for channel names written in non-ascii languages or where otherwise the slug would not make the URL nicer for humans. Clients must parse this format by discarding everything after the - and treating it identically to the simpler integer-only format. Note that means nothing enforces that the string have anything to do with the channel name; functionally, it just an optional hint.

These two formats allow Zulip URLs to stably refer to a specific channel, even though channels can be renamed, while still allowing the URLs to have user-friendly name hints most of the time.

There is an additional legacy format that was used prior to 2018 that clients are required to support:

  • channel-name: Legacy format of just the channel name, URL-encoded and with spaces replaced with dashes. The legacy format should never take precedence over the modern format, so a link with 2016-election as the slug must be parsed as the channel with ID 2016, even if theoretically it could have been originally intended as referring to a channel named 2016 election.

Clients are not recommended to ever generate this legacy format.

Zulip's single-sign on login process for the mobile app ends with a redirect to zulip://login with the following query parameters:

  • email: The email address for the authenticated account.
  • otp_encrypted_api_key: The API key for the client, encrypted using the mobile_flow_otp that the client provided when initiating the login attempt.
  • realm: The full URL of the Zulip organization.
  • user_id: The Zulip user ID for the authenticated account.

Changes: The user_id field was added to the set of included query parameters in Zulip 5.0 (feature level 128).