TL;DR, Quick Answer
6 min readBluesky reply controls live in an app.bsky.feed.threadgate record whose record key matches the thread's root post. Its allow array takes up to 5 rules: mentionRule, followerRule, followingRule and listRule. An empty array means nobody can reply, and a missing array means anybody can. The same record holds up to 300 hidden reply URIs. Quote posts are controlled by a separate app.bsky.feed.postgate record, whose disableRule turns off embedding and whose detachedEmbeddingUris holds up to 50 detached quotes.
What is a Bluesky threadgate?
A Bluesky threadgate is the app.bsky.feed.threadgate record that decides who can reply to a thread. The lexicon describes it as a "Record defining interaction gating rules for a thread (aka, reply controls)." Reply controls on Bluesky are not a setting on your account or a flag on the post. They are this record, stored in your own repository.
It is a record of its own, not a field on the post. That design has a consequence people miss: you can add, change or delete reply controls on a post that is already live, because the post record itself never changes.
The lexicon pins down where the record has to live: "The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository." A threadgate belongs to a root post, in the author's own repo, under the same rkey.
What fields does the threadgate record have?
Four, and two of them are required.
| Field | Type | Required | Lexicon description |
|---|---|---|---|
post | at-uri | Yes | "Reference (AT-URI) to the post record." |
createdAt | datetime | Yes | None |
allow | array, max 5 | No | "List of rules defining who can reply to this post." |
hiddenReplies | array of at-uri, max 300 | No | "List of hidden reply URIs." |
A full record that lets mentioned accounts and one list reply looks like this:
{
"$type": "app.bsky.feed.threadgate",
"post": "at://did:plc:abc123/app.bsky.feed.post/3kxyzpost",
"allow": [
{ "$type": "app.bsky.feed.threadgate#mentionRule" },
{
"$type": "app.bsky.feed.threadgate#listRule",
"list": "at://did:plc:abc123/app.bsky.graph.list/3kxyzlist"
}
],
"createdAt": "2026-09-24T10:00:00.000Z"
}You write it with com.atproto.repo.createRecord, setting collection to app.bsky.feed.threadgate and rkey to the rkey of the post, which is the last segment of the post's AT-URI. Bluesky's thread gates page shows the same step in its TypeScript sample: const { rkey } = new AtUri(postUri).
Which allow rules can a Bluesky threadgate use?
Four rule types, each an object inside allow. Bluesky's documentation lists them with one-line definitions:
| Rule | Who can reply |
|---|---|
app.bsky.feed.threadgate#mentionRule | "Allow replies from actors mentioned in your post." |
app.bsky.feed.threadgate#followingRule | "Allow replies from actors you follow." |
app.bsky.feed.threadgate#followerRule | "Allow replies from actors following you." |
app.bsky.feed.threadgate#listRule | "Allow replies from actors on a list." |
Three of the four carry no properties at all. listRule is the exception: its list field is required and takes the AT-URI of an app.bsky.graph.list record.
The rules add up rather than narrow down. Bluesky's sample puts mentionRule, followingRule and listRule in one array with the comments "allow mentioned users," "allow followed users" and "allow list members," so each rule opens replies to one more group.
mentionRule depends on how the post encodes its mentions. In a post record, a mention is a app.bsky.richtext.facet#mention facet, and the facet lexicon notes that "The text is usually a handle, including a '@' prefix, but the facet reference is a DID." Typing an @handle as plain text without a facet does not make it a mention in the record. Getting the facet's byte offsets right is its own problem, covered in the note on Bluesky facets byteStart and byteEnd.
The allow array caps at 5 items while there are only four rule types. The schema does not forbid repeating listRule, so the fifth slot is room for a second list.

What is the difference between an empty and a missing allow array?
Everything. The lexicon draws the line in one sentence: "If value is an empty array, no one can reply. If value is undefined, anyone can reply."
allow value | Who can reply |
|---|---|
| Missing | Anyone |
[] | Nobody |
| One or more rules | Anyone matching any rule |
Bluesky's documentation phrases this at the record level instead: "If a thread gate record is present but empty, then nobody can reply," and "If a thread gate record is not present, then anybody can reply." Read the two sources together and there is a case the docs page skips. A threadgate record can exist with no allow field, for example one that only lists hiddenReplies. Under the lexicon's wording, that thread is still open to anyone. If your code hides a reply by creating a threadgate, and then someone reads "record present means gated," they will get the logic backwards.
Can you gate a reply in the middle of a thread?
No. The threadgate's rkey has to match "the thread's root post," so there is one threadgate per thread, attached to its first post. Replies further down cannot carry their own reply controls.
AdaptlyPost
Start Your 7-Day Free Trial
All-platform analytics
Social Inbox
AI-powered assistant
The hiddenReplies array lives on the same root record, and it caps at 300 URIs. Bluesky's announcement of the feature, in app version 1.90, says "Only the original creator of the thread can hide replies," and that "All hidden replies will be placed behind a Hidden replies screen." They stay reachable there, "but much less visible." The same post warns that hidden replies, along with which posts the author hid, "are still public data." Anything in a threadgate record is readable by anyone who reads your repository.

What is the postgate record?
The postgate is the threadgate's counterpart for quote posts. app.bsky.feed.postgate is a "Record defining interaction rules for a post," and it follows the same placement rule: its rkey "must match the record key of the post, and that record must be in the same repository."
| Field | Type | Required | Lexicon description |
|---|---|---|---|
post | at-uri | Yes | "Reference (AT-URI) to the post record." |
createdAt | datetime | Yes | None |
embeddingRules | array, max 5 | No | "List of rules defining who can embed this post." |
detachedEmbeddingUris | array of at-uri, max 50 | No | "List of AT-URIs embedding this post that the author has detached from." |
embeddingRules has a single rule type today, app.bsky.feed.postgate#disableRule, described as "Disables embedding of this post." That is the switch behind turning off quote posts.
Note that the postgate's defaults differ from the threadgate's. For embeddingRules, "If value is an empty array or is undefined, no particular rules apply and anyone can embed." An empty array locks replies in a threadgate but leaves quotes open in a postgate. To block quotes you need an explicit disableRule.
The postgate also differs in scope. It matches "the record key of the post," not the thread's root, so any post, including a reply, can carry its own postgate.
detachedEmbeddingUris records the quotes you have already cut loose. Bluesky's 1.90 announcement describes it: "you can detach your original post from someone's quote post," and "Like blocks, quote post removals are public data." The array holds 50 URIs.
How does a client know a post is gated?
Through the post view, not by fetching the records. app.bsky.feed.defs#postView carries a threadgate field that returns the record along with the lists it references. The viewer state for the logged-in account adds two booleans, replyDisabled and embeddingDisabled, which a client can use to grey out the reply and quote buttons for that person.
A scheduling tool has one extra step. The threadgate is a second record, so a scheduled post with reply controls means two writes: the post, then the gate under the same rkey. The note on how to schedule Bluesky posts covers the posting side, and both writes count against the limits described in the note on the Bluesky API rate limit.
Frequently asked questions
How do I stop everyone from replying to a Bluesky post through the API?
Create an app.bsky.feed.threadgate record with the post's rkey and "allow": []. The lexicon says "If value is an empty array, no one can reply."
Can a Bluesky threadgate allow only my followers to reply?
Yes. Add app.bsky.feed.threadgate#followerRule to allow. Bluesky defines it as "Allow replies from actors following you."
How many rules can one threadgate hold?
Five. The allow array has a maxLength of 5 in the lexicon.
Can I change reply controls after a post is published?
Yes. The threadgate is a separate record, so you can create, update or delete it without touching the post itself.
How do I turn off quote posts on Bluesky through the API?
Create an app.bsky.feed.postgate record with the post's rkey and add app.bsky.feed.postgate#disableRule to embeddingRules. An empty array does not block quotes.
Are hidden replies private?
No. Bluesky says hidden replies, and which posts the author hid, "are still public data." They sit in the threadgate's hiddenReplies array, which anyone can read.
AdaptlyPost
Start Your 7-Day Free Trial
All-platform analytics
Social Inbox
AI-powered assistant
Who can hide replies on a Bluesky thread?
Only the original creator of the thread can hide replies, according to Bluesky's 1.90 announcement. The hidden URIs go in the hiddenReplies array of the threadgate, up to 300 of them. Hidden replies sit behind a Hidden replies screen and stay reachable there.
Does an empty embeddingRules array turn off quote posts?
It does not. In a postgate, an empty or undefined embeddingRules means no particular rules apply and anyone can embed the post. To block quotes, add app.bsky.feed.postgate#disableRule.
How do I detach a quote post from my original post on Bluesky?
Add the AT-URI of the quote to the detachedEmbeddingUris array of the postgate for your post. The array holds up to 50 URIs. Bluesky notes that, like blocks, quote post removals are public data.
Can one threadgate combine several allow rules?
Rules add up. Each rule in allow opens replies to one more group, so mentioned users, followed users and a list can sit in the same array. The array holds up to 5 rules, and listRule needs the AT-URI of an app.bsky.graph.list record.
Put this into practice with AdaptlyPost
Was This Article Helpful?
Let us know what you think!
See us more often in Google
One click marks AdaptlyPost as a preferred source, so our articles sit higher in your Top Stories, AI Mode, and AI Overviews.
Before you go...
AdaptlyPost
Schedule your content across all platforms
Manage all your social media accounts in one place with AdaptlyPost.
All-platform analytics
Social Inbox
AI-powered assistant
Related Glossary Terms


How LinkedIn initializeUpload Turns a File Into an Image URN
The LinkedIn initializeUpload action returns an image URN and an upload URL. Here is the PUT, the post that references the URN, and the errors at each step.


Every /rest/ Call Needs the LinkedIn-Version Header in YYYYMM Form
The LinkedIn-Version header takes a YYYYMM value like 202608. Omit it and LinkedIn returns 400 VERSION_MISSING; a sunset version returns 426.


Why 429 Retry-After Is Missing From Most Social Media APIs
The 429 Retry-After pair is HTTP's way of saying wait. X, Meta, LinkedIn, TikTok and YouTube document no Retry-After, so here is what each sends instead.
Related Articles


Why Your Social Media Scheduler Keeps Disconnecting Accounts
A social media scheduler keeps disconnecting when a token expires or dies early. Token lifetimes for Meta, LinkedIn, X and TikTok, and what kills them.


What Instagram's content_publishing_limit Endpoint Returns
Instagram's content_publishing_limit endpoint returns quota_usage plus a config block holding quota_total 50 and quota_duration 86400 seconds.


The Facebook Long Lived Access Token and Its 60-Day Clock
A Facebook long lived access token lasts about 60 days, and the Page token you derive from it has no expiry date at all. The exchange call and the caveats.

