Posts
Content warnings
A post can carry a content warning: what it holds, said by its author, so that a reader sees the warning first and chooses whether to see the post. Nothing is removed for being sensitive; the reader is asked. This page covers the kinds, what is stored and sent, and what the web app does.
The routes are in the API reference: text-service, photo-service and video-service. The plan, with what is still to come, is Content warnings (opens in a new tab) (roadmap row 122).
The kinds
Bluesky's four labels, so that they can cross one for one, and a fifth for a warning in the author's own words, as the fediverse writes them.
warning |
Said to people | For |
|---|---|---|
adult |
Adult content | Explicit sexual images |
suggestive |
Sexually suggestive | Sexual, without nudity |
graphic |
Graphic media | Violence, gore or disturbing images |
nudity |
Non-sexual nudity | Artistic nudes, for example |
other |
The author's words, up to 100 characters | Spoilers, grief, anything else |
The kinds are in anahita-libs/data/entities/warning.go.
On a post
Any post can have one: a note, a photo, a video or a sound, an article, a topic, and a reply of any kind.
- Made with one:
warningand, forother,warning_text, onPOST /notes/:owner_id/,/photos/:owner_id/,/articles/:owner_id/,/topics/:owner_id/,POST /videos/:owner_id/uploads(a video or a sound, kept with the upload until the post is made), and every reply:POST /notes/:id/replies,/photos/:id/replies, and a video upload withparent_id. - A thread takes one warning, on
POST /notes/:owner_id/threador/notes/:id/replies/thread, and every note of it has it. - Changed, or taken off:
PATCH /notes/:id/warning, and the same under/photos,/videos,/articlesand/topics, withwarningandwarning_text; an emptywarningtakes it off. Whoever may edit the post may; a reply, its author alone. - Refused with 400
warning_invalid: a kind that is not one, orotherwith no words or more than 100 characters. Words sent with another kind are dropped. - Sent with every post, in lists too, and with a post shown inside
another (what a repost, a quote or a reply shows):
warning: { kind, text, by_moderator }, or nowarningat all. A person's download keeps it, aswarningandwarning_texton each post inposts.ndjson(Download your data).
It is stored on nodes: warning (varchar(16), empty for none),
warning_text (varchar(100)) and warning_by_moderator (tinyint(1)),
migration 20261122000017_content_warnings; and on video_uploads,
warning and warning_text, for a video still being sent.
A warning the administrators gave
warning_by_moderator says that the site's administrators gave a post its
warning. Its author cannot change it or take it off: they are answered 403
warning_not_allowed, unless they ask for the warning it already has. The
web app does not offer the choice on such a post.
Nothing sets it yet: the report reason, the case's action and the post's menu for administrators are phase 4 of the plan.
Where it is shown
- Everywhere a post is drawn in the web app (below).
- A link pasted elsewhere shows nothing of the post: web-shell gives no picture of it (the site's own is used), and the warning in place of its words, "Content warning: graphic media" or "Content warning: " and the author's words (The web shell).
- Other servers are not told yet: a post goes to the fediverse and Bluesky as it did, without its warning, and a warning from there is not read. That is phase 3 of the plan.
What the web app does
- The composer, short and long, has Content warning among a post's choices: on the short composer's Details page a row, saying the kind chosen or None; in the long composer an icon beside the audience, with the kind beside it once chosen. It opens a dialog of None and the five kinds, each with its line; Other asks for the words right under it. It is asked of each post: the next starts with none.
- The reply form has the same, an icon among its pictures and GIF.
- A post's menu, on its own page, has Content warning for whoever may edit it, and a reply's menu for its writer; not on a warning the administrators gave.
- A post with one shows the warning with its kind's icon and View over its photo, GIF, video or sound, drawn blurred behind it so that the card keeps its height and nothing jumps, and its words as they are, as Bluesky and Mastodon show a post marked sensitive (the user, 2026-11-09); a link's card under them is blurred too. A post of words alone, and an article or a topic, whose words may hold pictures, have the warning over all of it. View opens it for that showing of the page. The same in feeds, on profiles, on a post's own page, in replies, quotes, reposts and posts shown in an article; in the photo theatre, a veil over the picture, each post behind its own as one steps through them, and the warning for its title.
- A profile's Photos card leaves out a photo with a warning: the card is a glance at who somebody is.
Everybody sees every kind behind its warning; each reader's own choice of Show, Warn or Hide is phase 2.
On the fediverse and Bluesky
A post's warning goes with it to both networks, as it is shown here:
| Network | A post with a photo, a GIF, a video or a sound | A post of words alone |
|---|---|---|
| The fediverse | sensitive: true: its pictures blurred, its words shown |
sensitive: true, and the warning as its summary, which Mastodon shows to be clicked past |
| Bluesky | The self-label of its kind, in the record's labels: its pictures blurred for those who asked to be warned |
The same label, which hides nothing there: Bluesky warns of pictures |
The warning's words are its author's for Other, and its kind's name in
English for the rest, "Graphic media" for example: one post goes to every
server. An article keeps its opening as its summary, and is sent
sensitive.
Bluesky's labels are its four kinds, one for one: adult is porn,
suggestive is sexual, graphic is graphic-media, and nudity is
nudity. Other has none: a warning in one's own words is not something
Bluesky has, and a post with one goes there without a warning.
A warning given or changed after a post was sent goes to the fediverse as
an Update. Bluesky has no changing a post: there, a post keeps the
warning it was sent with.
Warnings from other servers are not read yet: a post from elsewhere marked sensitive is still kept without its pictures (Federation).
Where the code is
| What | Where |
|---|---|
| The kinds, and checking what a request says | anahita-libs/data/entities/warning.go (NormalizeWarning) |
Setting it, 400 warning_invalid, 403 warning_not_allowed |
anahita-libs/handlers/warning.go (SetWarning), tested in warning_test.go |
| Sent with a post | anahita-libs/responses/warning.go, used in medium.go, parent.go, node.go |
PATCH /…/:id/warning |
text-service/cmd/api/handlers/medium_warning.go, photo-service/…/photo_warning.go, video-service/…/video_warning.go |
| Where it is stored | table nodes; graph-grpc-service/data/repositories/nodes_add.go, nodes_edit.go, helpers.go; node.proto fields 101 to 103 |
| A video's, until it is made | video-service/data/repositories/uploads.go, video-service/worker/worker.go |
| In a person's download | import-export-service/archive/archive.go |
| A link pasted elsewhere | web-shell/shell/pages.go (warningDescription) |
| Sent to the fediverse | activitypub-service/ap/note.go (WithWarning), its words from entities.WarningSaid |
| Sent to Bluesky | atproto-service/bsky/outgoing.go (WithLabel), its label from entities.WarningLabel |
| The web app | src/utils/warnings.js, src/components/ContentWarningDialog.jsx, ContentWarningButton.jsx, WarningCover.jsx, src/containers/controls/medium/ContentWarning.jsx (anahita-web) |
What is not built
The rest of the plan, each a phase of it:
- Each reader's choices of Show, Warn or Hide for each kind, in a Content tab of their settings.
- Warnings from elsewhere:
sensitiveandsummaryread from the fediverse, self-labels from Bluesky, and their pictures kept and blurred, not left out as they are now (Federation). - The administrators' warning: a report reason, the case's action, the post's menu, and a notification to the author.
- Made with AI, a sixth kind, chosen for an author when the file says so.
From anahitasocial/anahita-services, 2026-11-09.
Written by Claude, reviewed by Rastin Mehr · About this