snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:05:30 +05:00
commit 64898b2c79
8999 changed files with 1757358 additions and 0 deletions

View File

@@ -0,0 +1,630 @@
# Message formatting
Zulip supports an extended version of Markdown for messages, as well as
some HTML level special behavior. The Zulip help center article on [message
formatting](/help/format-your-message-using-markdown) is the primary
documentation for Zulip's markup features. This article is currently a
changelog for updates to these features.
The [render a message](/api/render-message) endpoint can be used to get
the current HTML version of any Markdown syntax for message content.
## Code blocks
**Changes**: As of Zulip 4.0 (feature level 33), [code blocks][help-code]
can have a `data-code-language` attribute attached to the outer HTML
`div` element, which records the programming language that was selected
for syntax highlighting. This field is used in the
[playgrounds][help-playgrounds] feature for code blocks.
## Global times
**Changes**: In Zulip 13.0 (feature level 503), the global time
syntax only recognizes ISO 8601 formatted timestamps.
**Changes**: In Zulip 12.0 (feature level 451), invalid timestamp formats
are now rendered as escaped literal text instead of a `<span>` element with
`timestamp-error` class and an error message.
Previously, an invalid timestamp string would be rendered as:
``` html
<span class="timestamp-error">Invalid time format: invalid</span>
```
Now, it is rendered as:
``` html
&lt;time:invalid&gt;
```
**Changes**: In Zulip 3.0 (feature level 8), added [global time
mentions][help-global-time] to supported Markdown message formatting
features.
## Links to channels, topics, and messages
Zulip's markup supports special readable Markdown syntax for [linking
to channels, topics, and
messages](/help/link-to-a-message-or-conversation). See also [Zulip
URLs](/api/zulip-urls)
Sample HTML formats are as follows:
``` html
<!-- Syntax: #**announce** -->
<a class="stream" data-stream-id="9"
href="/#narrow/channel/9-announce">
#announce
</a>
<!-- Syntax: #**announce>Zulip updates** -->
<a class="stream-topic" data-stream-id="9"
href="/#narrow/channel/9-announce/topic/Zulip.20updates/with/214">
#announce &gt; Zulip updates
</a>
<!-- Syntax: #**announce>Zulip updates**
Generated only if topic had no messages or the link was rendered
before Zulip 10.0 (feature level 347) -->
<a class="stream-topic" data-stream-id="9"
href="/#narrow/channel/9-announce/topic/Zulip.20updates">
#announce &gt; Zulip updates
</a>
<!-- Syntax: #**announce>Zulip updates@214** -->
<a class="message-link"
href="/#narrow/channel/9-announce/topic/Zulip.20updates/near/214">
#announce &gt; Zulip updates @ 💬
</a>
```
The `near` and `with` operators are documented in more detail in the
[search and URL documentation](/api/construct-narrow). When rendering
topic links with the `with` operator, the code doing the rendering may
pick the ID arbitrarily among messages accessible to the client and/or
acting user at the time of rendering. Currently, the server chooses
the message ID to use for `with` operators as the latest message ID in
the topic accessible to the user who wrote the message.
The older stream/topic link elements include a `data-stream-id`, which
historically was used in order to display the current channel name if
the channel had been renamed. That field is **deprecated**, because
displaying an updated value for the most common forms of this syntax
requires parsing the URL to get the topic to use anyway.
When a topic is an empty string, it is replaced with
`realm_empty_topic_display_name` found in the [`POST /register`](/api/register-queue)
response and wrapped with the `<em>` tag.
Sample HTML formats with `"realm_empty_topic_display_name": "general chat"`
are as follows:
```html
<!-- Syntax: #**announce>** -->
<a class="stream-topic" data-stream-id="9"
href="/#narrow/channel/9-announce/topic/with/214">
#announce &gt; <em>general chat</em>
</a>
<!-- Syntax: #**announce>**
Generated only if topic had no messages or the link was rendered
before Zulip 10.0 (feature level 347) -->
<a class="stream-topic" data-stream-id="9"
href="/#narrow/channel/9-announce/topic/">
#announce &gt; <em>general chat</em>
</a>
<!-- Syntax: #**announce>@214** -->
<a class="message-link"
href="/#narrow/channel/9-announce/topic//near/214">
#announce &gt; <em>general chat</em> @ 💬
</a>
```
**Changes**: In Zulip 11.0 (feature level 400), the server switched
its strategy for `with` URL construction to choose the latest
accessible message ID in a topic. Previously, it used the oldest.
Before Zulip 10.0 (feature level 347), the `with` field
was never used in topic link URLs generated by the server; the markup
currently used only for empty topics was used for all topic links.
Before Zulip 10.0 (feature level 346), empty string
was not a valid topic name in syntaxes for linking to topics and
messages.
In Zulip 10.0 (feature level 319), added Markdown syntax
for linking to a specific message in a conversation. Declared the
`data-stream-id` field to be deprecated as detailed above.
In Zulip 11.0 (feature level 383), clients can decide what
channel view a.stream channel link elements take you to -- i.e.,
the href for those is the default behavior of the link that also
encodes the channel alongside the data-stream-id field, but clients
can override that default based on `web_channel_default_view` setting.
## Emoji
Zulip's [emoji][help-emoji] support includes standard Unicode emoji, a
built-in Zulip custom emoji like `:zulip:` and [custom realm
emoji][help-custom-emoji]. To maximize legibility, emoji should be
displayed inline with text, at the maximum size that does not
interfere with line spacing.
**Large emoji**. Clients are recommended to display single-paragraph
messages that contain only emoji elements with a greatly increased
size. For example, Zulip the web app scales large emoji to be 2x the
size of other message emoji.
Unicode emoji, such as `:smiling_face:` (☺️ / `263a`), are represented
in the HTML by spans with the following format:
```html
<span aria-label="smiling face" class="emoji emoji-263a" role="img"
title="smiling face">:smiling_face:</span>
```
Note that Unicode emoji in messages that were originally sent on Zulip
versions older than Zulip 1.9.2 will not have the `role` or
`aria-label` attributes. Thus, clients should not rely on those fields
existing.
The `server_emoji_data_url` key in the [`POST
/register`](/api/register-queue) response contains the server's
mapping between Unicode codepoints and emoji names.
Custom emoji are represented in the HTML by image tags that also use
the `.emoji` class. The `src` URL should be resolved with respect to
the Zulip server's hostname:
```html
<-- Configured realm-specific custom emoji -->
<img alt=":example_custom_emoji:" class="emoji" title="example_custom_emoji"
src="/user_avatars/2/emoji/images/dbe43627.png">
<-- Built-in custom emoji like :zulip: -->
<img alt=":zulip:" class="emoji" title="zulip"
src="/static/generated/emoji/images/emoji/unicode/zulip.png">
```
**Changes**: Large emoji are new in Zulip 12.0 (feature level 436).
## Images
When a Zulip message is sent with a link to an uploaded image, Zulip will
also insert an image preview element with the following format:
``` html
<div class="message_inline_image">
<a href="/user_uploads/path/to/example.png" title="example.png">
<img data-original-dimensions="1920x1080"
data-original-content-type="image/png"
src="/user_uploads/thumbnail/path/to/example.png/840x560.webp">
</a>
</div>
```
Clients can recognize if an image was thumbnailed by its `src`
attribute starting with `/user_uploads/thumbnail/`. The `href` will
always link to the originally-uploaded file, at its original
resolution.
When a Zulip message is sent referencing an **uploaded image** in Markdown
image syntax, (e.g.,`![example image](/user_uploads/path/to/example.png)`),
Zulip will generate an image element with the following format:
```html
<img alt="example image" class="inline-image"
data-original-dimensions="1050x700"
data-original-content-type="image/png"
data-original-src="/user_uploads/path/to/example.png"
src="/user_uploads/thumbnail/path/to/example.png/840x560.webp">
```
Note that the Markdown image syntax is only supported/permitted for
uploaded images, not third-party image URLs.
As with link-derived image previews, clients can recognize if an image
was thumbnailed by its `src` attribute starting with
`/user_uploads/thumbnail/`. If an image is a thumbnail, the
`data-original-src` attribute will always reference the
originally-uploaded file, at its original resolution.
Note that images generated using Markdown image syntax may appear
inside arbitrary block-level elements, unlike the image previews from
links, which will always be top-level elements.
Regardless of which form (link-derived preview, or Markdown image
syntax), the `data-original-dimensions` and
`data-original-content-type` attributes will only be present if the
image was thumbnailed and the message was sent (or last edited)
after those attributes were added (see [Changes to image
formatting](#changes-to-image-formatting)). If the image is *not* a
thumbnail, clients should make a best-effort attempt to render the
given `src`, in a manner which minimizes layout shift when the
resource is loaded -- see the next section for a special case of this.
**Changes**: See [Changes to image formatting](#changes-to-image-formatting).
### Image-loading placeholders
If the server has yet to generate thumbnails for the image by the time
the message is sent, the `img` element will temporarily reference a
loading indicator image and have the `image-loading-placeholder`
class, which clients can use to identify loading indicators and
replace them with a more native loading indicator element if desired.
This spinner placeholder will have `data-original-dimensions`
and `data-original-content-type` attributes
if the message was sent (or last edited) after those attributes were added
(see [Changes to image formatting](#changes-to-image-formatting)).
In link-derived image previews, the placeholder is structured like this:
``` html
<div class="message_inline_image">
<a href="/user_uploads/path/to/example.png" title="example.png">
<img class="image-loading-placeholder"
data-original-dimensions="1920x1080"
data-original-content-type="image/png"
src="/path/to/spinner.png">
</a>
</div>
```
For image elements presented in Markdown image syntax, this placeholder
structure is used:
```html
<img alt="example image"
class="inline-image image-loading-placeholder"
data-original-content-type="image/png"
data-original-dimensions="1050x700"
data-original-src="/user_uploads/path/to/example.png"
src="/path/to/spinner.png">
```
Once the server has a working thumbnail, such messages will be updated
via an `update_message` event, with the `rendering_only: true` flag
(telling clients not to adjust message edit history), with appropriate
adjusted `rendered_content`. A client should process those events by
just using the updated rendering. If thumbnailing failed, the same
type of event will edit the message's rendered form to remove the
image preview element, so no special client-side logic should be
required to process such errors.
Note that in the uncommon situation that the thumbnailing system is
backlogged, an individual message containing multiple image previews
may be re-rendered multiple times as each image finishes thumbnailing
and triggers a message update.
Clients displaying message-edit history should rewrite image-loading
placeholder images in edit history to the generic deleted-file image
(`/static/images/errors/image-not-exist.png`).
### Transcoded images
Image elements whose formats are not widely supported by web browsers
(e.g., HEIC and TIFF) may contain a `data-transcoded-image` attribute,
which specifies a high-resolution thumbnail format that clients may
opt to present instead of the original image. If the
`data-transcoded-image` attribute is present, clients should use the
`data-original-content-type` attribute to decide whether to display the
original image or use the transcoded version.
Transcoded images are presented with this structure in image previews:
``` html
<div class="message_inline_image">
<a href="/user_uploads/path/to/example.heic" title="example.heic">
<img data-original-dimensions="1920x1080"
data-original-content-type="image/heic"
data-transcoded-image="1920x1080.webp"
src="/user_uploads/thumbnail/path/to/example.heic/840x560.webp">
</a>
</div>
```
Transcoded images presented in Markdown image syntax are structured like this:
```html
<img alt="example HEIC image" class="inline-image"
data-original-dimensions="1920x1080"
data-original-content-type="image/heic"
data-original-src="/user_uploads/path/to/example.heic"
data-transcoded-image="1920x1080.webp"
src="/user_uploads/thumbnail/path/to/example.heic/840x560.webp">
```
### Recommended client processing of image previews
Clients are recommended to do the following when processing image
previews:
- Clients that would like to use the image's aspect ratio to lay out
one or more images in the message feed may use the
`data-original-dimensions` attribute, which is present even if the
image is a placeholder spinner. This attribute encodes the
dimensions of the original image as `{width}x{height}`. These
dimensions are for the image as rendered, _after_ any EXIF rotation
and mirroring has been applied.
- If the client would like to control the thumbnail resolution used,
it can replace the final section of the URL (`840x560.webp` in the
example above) with the `name` of its preferred format from the set
of supported formats provided by the server in the
`server_thumbnail_formats` portion of the `register`
response. Clients should not make any assumptions about what format
the server will use as the "default" thumbnail resolution, as it may
change over time.
- Download button type elements should provide the original image
(encoded via the `href` of the containing `a` tag).
- The content-type of the original image is provided on a
`data-original-content-type` attribute, so clients can decide if
they are capable of rendering the original image.
- For images whose formats which are not widely-accepted by browsers
(e.g., HEIC and TIFF), the image may contain a
`data-transcoded-image` attribute, which specifies a high-resolution
thumbnail format which clients may use instead of the original
image.
- Lightbox elements for viewing an image should be designed to
immediately display any already-downloaded thumbnail while fetching
the original-quality image or an appropriate higher-quality
thumbnail from the server, to be transparently swapped in once it is
available. Clients that would like to size the lightbox based on the
size of the original image can use the `data-original-dimensions`
attribute, as described above.
- Animated images will have a `data-animated` attribute on the `img`
tag. As detailed in `server_thumbnail_formats`, both animated and
still images are available for clients to use, depending on their
preference. See, for example, the [web setting][help-previews]
to control whether animated images are autoplayed in the message
feed.
- Clients should not assume that the requested format is the format
that they will receive; in rare cases where the client has an
out-of-date list of `server_thumbnail_formats`, the server will
provide an approximation of the client's requested format. Because
of this, clients should not assume that the pixel dimensions or file
format match what they requested.
- No other processing of the URLs is recommended.
### Changes to image formatting
**In Zulip 12.0** (feature level 467), limited the Markdown
image syntax to only support uploaded images, not linking to
third-party image URLs.
**In Zulip 12.0** (feature level 437), added support for Markdown
image syntax, in addition to the previous link-derived image previews;
these can be inserted into any block-level element.
**In Zulip 10.0** (feature level 336), added
`data-original-content-type` attribute to convey the type of the
original image, and optional `data-transcoded-image` attribute for
images with formats which are not widely supported by browsers.
**In Zulip 9.2** (feature levels 278-279, and 287+), added
`data-original-dimensions` to the `image-loading-placeholder` spinner
images, containing the dimensions of the original image.
**In Zulip 9.0** (feature level 276), added `data-original-dimensions`
attribute to images that have been thumbnailed, containing the
dimensions of the full-size version of the image. Thumbnailing itself
was reintroduced at feature level 275.
Previously, with the exception of Zulip servers that used the beta
Thumbor-based implementation years ago, all image previews in Zulip
messages were not thumbnailed; the `a` tag and the `img` tag would both
point to the original image.
Clients that correctly implement the current API should handle
Thumbor-based older thumbnails correctly, as long as they do not
assume that `data-original-dimensions` is present. Clients should not
assume that messages sent prior to the introduction of thumbnailing
have been re-rendered to use the new format or have thumbnails
available.
## Video embeddings and previews
When a Zulip message is sent linking to an uploaded video, Zulip may
generate a video preview element with the following format. Supported
MIME types are `video/mp4`, `video/quicktime` (i.e., `.mov` files), and
`video/webm`. Because `video/quicktime` is not supported in every
browser, the preview element is hidden at render time when the browser
reports that it cannot play the video; the download link in the
surrounding `a` tag remains available.
``` html
<div class="message_inline_image message_inline_video">
<a href="/user_uploads/path/to/video.mp4">
<video preload="metadata" src="/user_uploads/path/to/video.mp4">
</video>
</a>
</div>
```
## Audio Players
When the Markdown media syntax is used with an uploaded file with an
audio `Content-Type`, Zulip will generate an HTML5 `<audio>` player
element. Supported MIME types are currently `audio/aac`, `audio/flac`,
`audio/mpeg`, and `audio/wav` (and its `audio/x-wav` and
`audio/vnd.wave` variants).
For example, `![file.mp3](/user_uploads/path/to/file.mp3)` renders as:
``` html
<audio controls preload="metadata"
src="/user_uploads/path/to/file.mp3" title="file.mp3">
</audio>
```
If the Zulip server has rewritten the URL of the audio file, it will
provide the URL in a `data-original-url` parameter. The Zulip server
does this for all non-uploaded file audio URLs.
``` html
<audio controls preload="metadata"
data-original-url="https://example.com/path/to/original/file.mp3"
src="https://zulipcdn.example.com/path/to/playable/file.mp3" title="file.mp3">
</audio>
```
Clients that cannot render an audio player are recommended to convert
audio elements into a link to the original URL.
The Zulip server does not validate whether uploaded files with an
audio `Content-Type` are actually playable.
**Changes**: New in Zulip 11.0 (feature level 405).
## Mentions and silent mentions
Zulip markup supports [mentioning](/help/mention-a-user-or-group)
users, user groups, and a few special "wildcard" mentions (the three
spellings of a channel wildcard mention: `@**all**`, `@**everyone**`,
`@**channel**` and the topic wildcard mention `@**topic**`).
Mentions result in a message being highlighted for the target user(s),
both in the UI and in notifications, and may also result in the target
user(s) following the conversation, [depending on their
settings](/help/follow-a-topic#follow-topics-where-you-are-mentioned).
Silent mentions of users or groups have none of those side effects,
but nonetheless uniquely identify the user or group
identified. (There's no such thing as a silent wildcard mention).
Permissions for mentioning users work as follows:
- Any user can mention any other user, though mentions by [muted
users](/help/mute-a-user) are automatically marked as read and thus do
not trigger notifications or otherwise get highlighted like unread
mentions.
- Wildcard mentions are permitted except where [organization-level
restrictions](/help/restrict-wildcard-mentions) apply.
- User groups can be mentioned if and only if the acting user is in
the `can_mention_group` group for that group. All user groups can be
silently mentioned by any user.
- System groups, when (silently) mentioned, should be displayed using
their description, not their `role:nobody` style API names; see the
main [system group
documentation](/api/group-setting-values#system-groups) for
details. System groups can only be silently mentioned right now,
because they happen to all use the empty `Nobody` group for
`can_mention_group`; clients should just use `can_mention_group` to
determine which groups to offer in typeahead in similar contexts.
- Requests to send or edit a message that are impermissible due to
including a mention where the acting user does not have permission to
mention the target will return an error. Mention syntax that does not
correspond to a real user or group is ignored.
Sample markup for `@**Example User**`:
``` html
<span class="user-mention" data-user-id="31">@Example User</span>
```
Sample markup for `@_**Example User**`:
``` html
<span class="user-mention silent" data-user-id="31">Example User</span>
```
Sample markup for `@**topic**`:
``` html
<span class="topic-mention">@topic</span>
```
Sample markup for `@**channel**`:
``` html
<span class="user-mention channel-wildcard-mention"
data-user-id="*">@channel</span>
```
Sample markup for `@*support*`, assuming "support" is a valid group:
``` html
<span class="user-group-mention"
data-user-group-id="17">@support</span>
```
Sample markup for `@_*support*`, assuming "support" is a valid group:
``` html
<span class="user-group-mention silent"
data-user-group-id="17">support</span>
```
Sample markup for `@_*role:administrators*`:
``` html
<span class="user-group-mention silent"
data-user-group-id="5">Administrators</span>
```
When processing mentions, clients should look up the user or group
referenced by ID, and update the textual name for the mention to the
current name for the user or group with that ID. Note that for system
groups, this requires special logic to look up the user-facing name
for that group; see [system
groups](/api/group-setting-values#system-groups) for details.
**Changes**: Prior to Zulip 10.0 (feature level 333), it was not
possible to silently mention [system
groups](/api/group-setting-values#system-groups).
In Zulip 9.0 (feature level 247), `channel` was added to the supported
[wildcard][help-mention-all] options used in the
[mentions][help-mentions] Markdown message formatting feature.
## Spoilers
**Changes**: In Zulip 3.0 (feature level 15), added
[spoilers][help-spoilers] to supported Markdown message formatting
features.
## Removed features
### Removed legacy Dropbox link preview markup
In Zulip 11.0 (feature level 395), the Zulip server stopped generating
legacy Dropbox link previews. Dropbox links are now previewed just
like standard Zulip image/link previews. However, some legacy Dropbox
previews may exist in existing messages.
Clients are recommended to prune these previews from message HTML;
since they always appear after the actual link, there is no loss of
information/functionality. They can be recognized via the classes
`message_inline_ref`, `message_inline_image_desc`, and
`message_inline_image_title`:
``` html
<div class="message_inline_ref">
<a href="https://www.dropbox.com/sh/cm39k9e04z7fhim/AAAII5NK-9daee3FcF41anEua?dl=" title="Saves">
<img src="/path/to/folder_dropbox.png">
</a>
<div><div class="message_inline_image_title">Saves</div>
<desc class="message_inline_image_desc"></desc>
</div>
</div>
```
### Removed legacy avatar markup
In Zulip 4.0 (feature level 24), the rarely used `!avatar()`
and `!gravatar()` markup syntax, which was never documented and had an
inconsistent syntax, were removed.
## Related articles
* [Markdown formatting](/help/format-your-message-using-markdown)
* [Send a message](/api/send-message)
* [Render a message](/api/render-message)
[help-code]: /help/code-blocks
[help-emoji]: /help/emoji-and-emoticons
[help-custom-emoji]: /help/custom-emoji
[help-playgrounds]: /help/code-blocks#code-playgrounds
[help-spoilers]: /help/spoilers
[help-global-time]: /help/global-times
[help-mentions]: /help/mention-a-user-or-group
[help-mention-all]: /help/mention-a-user-or-group#mention-everyone-on-a-channel
[help-previews]: /help/image-video-and-website-previews#configure-how-animated-images-are-played