Nimbryxo Studio

YouTube Video Comment & Reply Thread Scraper

Get a YouTube video's comments with nested replies attached, author identity, like counts, and pinned/hearted/verified flags, filterable by keyword and minimum likes.

Input schema

Input fields, types, required flags, descriptions, defaults, and prefills come from the published definition. Prefill examples are distinct from defaults.

FieldTypeRequiredDescription
videoUrlsarrayoptionalWatch, youtu.be, Shorts, embed, live, v/ links, or bare 11-character IDs. Every video is normalized to a watch URL internally.
channelUrlsarrayoptionalChannel /videos sources: /@handle, /channel/UC..., /c/name, /user/name, a bare @handle, or a UC... channel ID. Scrapes up to Max videos per source.
playlistUrlsarrayoptionalPlaylist sources: youtube.com/playlist?list=... or a bare playlist ID. Scrapes up to Max videos per source.
Show all input fields
FieldTypeRequiredDescription
videoUrlsarrayoptionalWatch, youtu.be, Shorts, embed, live, v/ links, or bare 11-character IDs. Every video is normalized to a watch URL internally.
channelUrlsarrayoptionalChannel /videos sources: /@handle, /channel/UC..., /c/name, /user/name, a bare @handle, or a UC... channel ID. Scrapes up to Max videos per source.
playlistUrlsarrayoptionalPlaylist sources: youtube.com/playlist?list=... or a bare playlist ID. Scrapes up to Max videos per source.
searchQueriesarrayoptionalYouTube search terms or search-result URLs. Only video results are used, deduplicated, up to Max videos per source.
maxVideosPerSourceintegeroptionalDiscovery cap for channel, playlist and search sources. 0 = every video the source exposes.
maxCommentsintegeroptionalTop-level comments to examine per video. 0 = all. Comments rejected by filters still count toward this scan cap (so restrictive filters cannot paginate forever); replies do not. Both emitted comments and replies count toward Max total rows.
includeRepliesbooleanoptionalFetch reply threads. Emitted as flat rows by default; use Output mode tree to embed them instead.
maxRepliesPerCommentintegeroptionalPer-thread reply cap. 0 = all replies, stopping when the continuation token is exhausted. YouTube serves roughly 9-10 on the first reply page and 44-49 afterwards.
maxReplyDepthintegeroptional0 = top-level comments only, 1 = direct replies (the shape observed publicly). Values above 1 are reserved for nested sub-threads, which YouTube has not exposed in the samples used to build this actor.
maxTotalRowsintegeroptionalGlobal budget for emitted comment + reply rows. 0 = unlimited. Status rows are never counted and are not billable data rows.
sortBystringoptionaltop = YouTube relevance order; newest = chronological, recommended for date cutoffs and complete windows. A date cutoff forces newest automatically.
commentsSincestringoptionalOptional cutoff as an ISO date/datetime (2026-10-16) or relative duration (7d, 24h, 30m). Forces newest sort. Pinned comments are always kept; pagination stops at the first non-pinned comment older than the cutoff.
keywordsarrayoptionalCase-insensitive OR filter on comment and reply text. Empty = no keyword filter. A parent comment that does not match is skipped, so its replies are not fetched.
minLikesintegeroptionalDrop comments and replies below this like count. Applied after fetching. YouTube only exposes rounded counts (K/M), so the comparison uses the rounded value.
onlyCreatorHeartedbooleanoptionalKeep only comments/replies that the video creator hearted. HeartedBy identifies the creator where YouTube exposes the tooltip.
outputModestringoptionalflat = one row per comment and reply (recommended for CSV/Excel/SQL); tree = parent rows with embedded replies[] (backward compatible); both = flat rows plus parent rows carrying replies[].
emitStatusRowsbooleanoptionalWrite one recordType=status row for blocked, private, unavailable, comments-off, empty-token, invalid-input, partial and budget-stop cases. Status rows are never counted toward Max total rows; disable this to keep the dataset data-only.

Download the published input schema

View complete JSON
{
  "description": "Scrape public YouTube comments and reply threads without login, user cookies, or an API key. Provide one or more videos, channels, playlists, or search queries.",
  "properties": {
    "channelUrls": {
      "default": [],
      "description": "Channel /videos sources: /@handle, /channel/UC..., /c/name, /user/name, a bare @handle, or a UC... channel ID. Scrapes up to Max videos per source.",
      "editor": "stringList",
      "items": {
        "type": "string"
      },
      "prefill": [],
      "title": "Channel URLs or handles",
      "type": "array"
    },
    "commentsSince": {
      "description": "Optional cutoff as an ISO date/datetime (2026-10-16) or relative duration (7d, 24h, 30m). Forces newest sort. Pinned comments are always kept; pagination stops at the first non-pinned comment older than the cutoff.",
      "editor": "textfield",
      "title": "Comments since",
      "type": "string"
    },
    "emitStatusRows": {
      "default": true,
      "description": "Write one recordType=status row for blocked, private, unavailable, comments-off, empty-token, invalid-input, partial and budget-stop cases. Status rows are never counted toward Max total rows; disable this to keep the dataset data-only.",
      "prefill": true,
      "title": "Emit status rows",
      "type": "boolean"
    },
    "includeReplies": {
      "default": true,
      "description": "Fetch reply threads. Emitted as flat rows by default; use Output mode tree to embed them instead.",
      "prefill": true,
      "title": "Include replies",
      "type": "boolean"
    },
    "keywords": {
      "default": [],
      "description": "Case-insensitive OR filter on comment and reply text. Empty = no keyword filter. A parent comment that does not match is skipped, so its replies are not fetched.",
      "editor": "stringList",
      "items": {
        "type": "string"
      },
      "prefill": [],
      "title": "Keywords (ANY match)",
      "type": "array"
    },
    "maxComments": {
      "default": 100,
      "description": "Top-level comments to examine per video. 0 = all. Comments rejected by filters still count toward this scan cap (so restrictive filters cannot paginate forever); replies do not. Both emitted comments and replies count toward Max total rows.",
      "editor": "number",
      "maximum": 50000,
      "minimum": 0,
      "prefill": 3,
      "title": "Max top-level comments per video",
      "type": "integer"
    },
    "maxRepliesPerComment": {
      "default": 50,
      "description": "Per-thread reply cap. 0 = all replies, stopping when the continuation token is exhausted. YouTube serves roughly 9-10 on the first reply page and 44-49 afterwards.",
      "editor": "number",
      "maximum": 10000,
      "minimum": 0,
      "prefill": 3,
      "title": "Max replies per comment",
      "type": "integer"
    },
    "maxReplyDepth": {
      "default": 1,
      "description": "0 = top-level comments only, 1 = direct replies (the shape observed publicly). Values above 1 are reserved for nested sub-threads, which YouTube has not exposed in the samples used to build this actor.",
      "editor": "number",
      "maximum": 6,
      "minimum": 0,
      "prefill": 1,
      "title": "Max reply depth",
      "type": "integer"
    },
    "maxTotalRows": {
      "default": 5000,
      "description": "Global budget for emitted comment + reply rows. 0 = unlimited. Status rows are never counted and are not billable data rows.",
      "editor": "number",
      "maximum": 1000000,
      "minimum": 0,
      "prefill": 20,
      "title": "Max total rows",
      "type": "integer"
    },
    "maxVideosPerSource": {
      "default": 20,
      "description": "Discovery cap for channel, playlist and search sources. 0 = every video the source exposes.",
      "editor": "number",
      "maximum": 5000,
      "minimum": 0,
      "title": "Max videos per channel/playlist/search",
      "type": "integer"
    },
    "minLikes": {
      "default": 0,
      "description": "Drop comments and replies below this like count. Applied after fetching. YouTube only exposes rounded counts (K/M), so the comparison uses the rounded value.",
      "editor": "number",
      "maximum": 1000000000,
      "minimum": 0,
      "prefill": 0,
      "title": "Minimum likes",
      "type": "integer"
    },
    "onlyCreatorHearted": {
      "default": false,
      "description": "Keep only comments/replies that the video creator hearted. HeartedBy identifies the creator where YouTube exposes the tooltip.",
      "prefill": false,
      "title": "Only creator-hearted comments",
      "type": "boolean"
    },
    "outputMode": {
      "default": "flat",
      "description": "flat = one row per comment and reply (recommended for CSV/Excel/SQL); tree = parent rows with embedded replies[] (backward compatible); both = flat rows plus parent rows carrying replies[].",
      "editor": "select",
      "enum": [
        "flat",
        "tree",
        "both"
      ],
      "enumTitles": [
        "Flat rows",
        "Tree with embedded replies",
        "Both"
      ],
      "prefill": "flat",
      "title": "Output mode",
      "type": "string"
    },
    "playlistUrls": {
      "default": [],
      "description": "Playlist sources: youtube.com/playlist?list=... or a bare playlist ID. Scrapes up to Max videos per source.",
      "editor": "stringList",
      "items": {
        "type": "string"
      },
      "prefill": [],
      "title": "Playlist URLs or IDs",
      "type": "array"
    },
    "searchQueries": {
      "default": [],
      "description": "YouTube search terms or search-result URLs. Only video results are used, deduplicated, up to Max videos per source.",
      "editor": "stringList",
      "items": {
        "type": "string"
      },
      "prefill": [],
      "title": "Search queries",
      "type": "array"
    },
    "sortBy": {
      "default": "top",
      "description": "top = YouTube relevance order; newest = chronological, recommended for date cutoffs and complete windows. A date cutoff forces newest automatically.",
      "editor": "select",
      "enum": [
        "top",
        "newest"
      ],
      "enumTitles": [
        "Top comments",
        "Newest first"
      ],
      "prefill": "top",
      "title": "Sort comments by",
      "type": "string"
    },
    "videoUrls": {
      "default": [],
      "description": "Watch, youtu.be, Shorts, embed, live, v/ links, or bare 11-character IDs. Every video is normalized to a watch URL internally.",
      "editor": "stringList",
      "items": {
        "type": "string"
      },
      "prefill": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
      ],
      "title": "Video URLs or IDs",
      "type": "array"
    }
  },
  "schemaVersion": 1,
  "title": "YouTube Comments & Reply Thread Scraper Input",
  "type": "object"
}

Output schema

Published result links and storage locations. This schema is not an exhaustive contract for every field in a dataset record.

FieldTypeRequiredDescription
commentsstringoptionalDataset containing one row per comment or reply (plus optional status rows when emitStatusRows is enabled).

Download the published output schema

View complete JSON
{
  "actorOutputSchemaVersion": 1,
  "description": "Flat comment and reply rows with author identity, rounded engagement counts, honest approximate dates, thread fields, video statistics, and optional status rows.",
  "properties": {
    "comments": {
      "description": "Dataset containing one row per comment or reply (plus optional status rows when emitStatusRows is enabled).",
      "template": "{{links.apiDefaultDatasetUrl}}/items",
      "title": "YouTube comments, replies and statuses",
      "type": "string"
    }
  },
  "title": "YouTube Video Comment & Reply Thread Scraper Output"
}

Dataset schema

Published dataset views and any declared field definitions. A view can select only a subset of the returned record fields.

Download the published dataset schema

View complete JSON
{
  "actorSpecification": 1,
  "views": {
    "overview": {
      "display": {
        "component": "table",
        "properties": {
          "authorName": {
            "format": "text",
            "label": "Author"
          },
          "commentId": {
            "format": "text",
            "label": "Comment ID"
          },
          "isHearted": {
            "format": "boolean",
            "label": "Hearted"
          },
          "likeCount": {
            "format": "number",
            "label": "Likes"
          },
          "recordType": {
            "format": "text",
            "label": "Type"
          },
          "replyCount": {
            "format": "number",
            "label": "Replies"
          },
          "text": {
            "format": "text",
            "label": "Text"
          },
          "videoTitle": {
            "format": "text",
            "label": "Video"
          }
        }
      },
      "title": "Comments and replies",
      "transformation": {
        "fields": [
          "recordType",
          "commentId",
          "authorName",
          "text",
          "likeCount",
          "replyCount",
          "isHearted",
          "videoTitle"
        ]
      }
    },
    "thread": {
      "display": {
        "component": "table",
        "properties": {
          "authorHandle": {
            "format": "text",
            "label": "Handle"
          },
          "commentId": {
            "format": "text",
            "label": "Comment ID"
          },
          "creatorReplied": {
            "format": "boolean",
            "label": "Creator replied"
          },
          "heartedBy": {
            "format": "text",
            "label": "Hearted by"
          },
          "isPinned": {
            "format": "boolean",
            "label": "Pinned"
          },
          "likeCountText": {
            "format": "text",
            "label": "Likes (text)"
          },
          "parentCommentId": {
            "format": "text",
            "label": "Parent ID"
          },
          "publishedAtApprox": {
            "format": "text",
            "label": "Approx. published"
          },
          "publishedAtPrecision": {
            "format": "text",
            "label": "Date precision"
          },
          "publishedTimeText": {
            "format": "text",
            "label": "Published"
          },
          "recordType": {
            "format": "text",
            "label": "Type"
          },
          "replyCountText": {
            "format": "text",
            "label": "Replies (text)"
          },
          "replyLevel": {
            "format": "number",
            "label": "Level"
          },
          "rootCommentId": {
            "format": "text",
            "label": "Root ID"
          },
          "sourceInput": {
            "format": "text",
            "label": "Source input"
          },
          "sourceScope": {
            "format": "text",
            "label": "Source scope"
          },
          "threadPosition": {
            "format": "number",
            "label": "Position"
          },
          "videoId": {
            "format": "text",
            "label": "Video ID"
          }
        }
      },
      "title": "Thread fields",
      "transformation": {
        "fields": [
          "recordType",
          "commentId",
          "parentCommentId",
          "rootCommentId",
          "replyLevel",
          "threadPosition",
          "authorHandle",
          "likeCountText",
          "replyCountText",
          "isPinned",
          "heartedBy",
          "creatorReplied",
          "publishedTimeText",
          "publishedAtApprox",
          "publishedAtPrecision",
          "videoId",
          "sourceInput",
          "sourceScope"
        ]
      }
    }
  }
}

Key-value schema

Published key-value store definitions, when provided by this build.

This definition is not present in the default published build. See the published README for available documentation.

Published README

Read the full published documentation

YouTube Video Comment & Reply Thread Scraper

Scrape YouTube comments and reply threads as analysis-ready rows. Give it video links, channel video tabs, playlists, or search queries, and get one row per comment or reply with author identity, engagement signals, thread position, and video context. Works on public videos without user cookies, login, or an API key.

What this actor does

  • Four source types: individual videos (watch, Shorts, live, embed, youtu.be, bare ID), channel video tabs, playlists, and video search results
  • Flat rows by default: every comment and reply is its own row with parentCommentId, rootCommentId, replyLevel, and threadPosition, so CSV, Excel, SQL, and sentiment pipelines can use it directly
  • Full reply threads: fetch every page of a thread, cap per comment, or skip replies entirely; maxRepliesPerComment: 0 means all
  • Real filters: Top/Newest sort, date cutoff, multiple keywords (ANY match), minimum likes, and only creator-hearted comments
  • Honest engagement data: rounded like counts with the original text, advertised reply counts, pinned/hearted/verified flags, and approximate dates with a precision and range
  • Video context on every row: title, channel, views, likes, comment count, duration, publish date, and description
  • Empty fields are omitted — records only contain values that are actually populated.

Output per comment or reply

Each dataset row is one comment (recordType: "comment", replyLevel: 0) or one reply (recordType: "reply", replyLevel: 1).

Field Type Description
recordType string comment, reply, or status
commentId string YouTube comment identifier
parentCommentId string Root comment ID on replies; omitted on top-level comments
rootCommentId string Thread root ID (own ID on top-level comments)
replyLevel integer 0 for comments, 1 for direct replies
threadPosition integer 1-based order in the emitted feed or reply thread
isReply boolean Backward-compatible alias for replyLevel > 0
replyDepth integer Backward-compatible alias for replyLevel
text string Comment text, with unicode and newlines preserved
authorName string Display name
authorHandle string @handle when YouTube exposes it
authorChannelId string UC... channel ID
authorChannelUrl string https://www.youtube.com/@handle or /channel/UC...
authorAvatarUrl string Avatar image URL
authorIsVerified boolean Author channel is verified
authorIsCreator boolean Author is the video's channel owner
authorIsArtist boolean Author is marked as a YouTube Music artist
likeCount integer Rounded like count (69K becomes 69000)
likeCountText string Original formatted count (69K)
replyCount integer YouTube's advertised reply count; may exceed visible replies
replyCountText string Original reply-count text when YouTube exposes it (963 replies)
repliesFetched integer Reply rows actually attached/emitted for this comment (present when replies were fetched)
isPinned boolean Comment is pinned
pinnedByText string Pinned-by tooltip (Pinned by @channel)
isHearted boolean Creator hearted the comment
heartedBy string Creator handle from the heart tooltip
heartedByText string Normalized creator label for the heart
creatorReplied boolean Whether any emitted reply is authored by the video creator (present when replies were fetched)
creatorReplyCount integer Number of emitted replies authored by the creator (present when replies were fetched)
isEdited boolean Comment was edited, derived from the (edited) label
publishedTimeText string Relative publish text exactly as YouTube shows it
publishedAtApprox string Midpoint of the inferred date range (ISO-8601)
publishedAtFrom string Earliest possible publish time
publishedAtTo string Latest possible publish time
publishedAtPrecision string second, minute, hour, day, week, month, or year
ageDaysMin number Lower bound of comment age in days
ageDaysMax number Upper bound of comment age in days
ageDaysApprox number Midpoint of the age range
commentUrl string Direct link to the comment (...&lc=COMMENT_ID)
videoId string Video ID
videoUrl string Canonical watch URL
videoTitle string Video title
videoChannelId string Video channel ID
videoChannelHandle string Video channel path (/@handle)
videoChannelName string Video channel display name
videoViewCount integer View count
videoLikeCount integer Video like count (YouTube may round it)
videoCommentCount integer Approximate total comments shown by YouTube
videoPublishDate string Raw publish date text (Oct 24, 2009)
videoPublishedAt string Parsed publish date (2009-10-24)
videoDurationSeconds integer Video length in seconds
videoDescription string Video description
sourceInput string The original URL, handle, or query that produced this video
sourceScope string video, channel, playlist, or search
sourceVideoId string Video selected from the source
scrapedAt string ISO-8601 UTC timestamp of the run
replies array Embedded reply rows, only with outputMode: tree or both

Status rows

When emitStatusRows is enabled, blocked/empty/unavailable sources and partial or budget-limited runs add one row with recordType: "status", status, inputUrl, optional video fields, error, and rowsEmitted when known. Status rows are never counted toward maxTotalRows and do not consume the data-row budget. Status values: no_comments, comments_off, unavailable, private, age_restricted, members_only, region_blocked, live_chat_only, blocked, stale_token, transient_empty, parse_empty, partial, budget_reached, input_invalid. Successful comment runs do not add an ok status row.

Dates and counts are approximate

YouTube's public comment payload does not expose exact timestamps. Every date field is derived from the relative label (6 years ago) and comes with a precision plus earliest/latest bounds, so no exact timestamp is invented. Like counts above 999 are rounded by YouTube itself; likeCountText preserves the original value. replyCount is YouTube's advertised count and can be higher than the number of replies the public thread returns.

Input

Field Type Default Description
videoUrls array [] Video URLs or bare IDs (watch, youtu.be, Shorts, embed, live, v/)
channelUrls array [] Channel video tabs: /@handle, /channel/UC..., /c/name, /user/name, @handle, or UC...
playlistUrls array [] Playlist URLs or bare playlist IDs
searchQueries array [] Search terms or YouTube search-result URLs; video results only
maxVideosPerSource integer 20 Video discovery cap per channel/playlist/search; 0 = all
maxComments integer 100 Top-level comments to examine per video; 0 = all. Filtered-out comments still count toward this scan cap
includeReplies boolean true Fetch reply threads
maxRepliesPerComment integer 50 Replies per thread; 0 = all until the token is exhausted
maxReplyDepth integer 1 0 = top-level only, 1 = direct replies
maxTotalRows integer 5000 Global comment + reply budget; 0 = unlimited
sortBy string top top or newest
commentsSince string – ISO date/datetime or relative duration (7d, 24h); forces newest
keywords array [] Case-insensitive ANY-match filter on comment/reply text
minLikes integer 0 Drop rows below this rounded like count
onlyCreatorHearted boolean false Keep only creator-hearted rows
outputMode string flat flat, tree, or both
emitStatusRows boolean true Emit recordType: "status" rows for blocked/empty/invalid sources

At least one of videoUrls, channelUrls, playlistUrls, or searchQueries is required.

Example: one video with replies

{
  "videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
  "maxComments": 50,
  "includeReplies": true,
  "maxRepliesPerComment": 20,
  "sortBy": "top",
  "maxTotalRows": 500
}

Example: recent comments from a channel

{
  "channelUrls": ["https://www.youtube.com/@RickAstleyYT"],
  "maxVideosPerSource": 10,
  "sortBy": "newest",
  "commentsSince": "7d",
  "maxComments": 100,
  "minLikes": 5,
  "maxTotalRows": 1000
}

Example: search with creator-hearted comments

{
  "searchQueries": ["product launch review"],
  "maxVideosPerSource": 5,
  "onlyCreatorHearted": true,
  "maxRepliesPerComment": 0,
  "maxTotalRows": 2000
}

Example: backward-compatible tree output

{
  "videoUrls": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"],
  "maxComments": 20,
  "includeReplies": true,
  "maxRepliesPerComment": 50,
  "outputMode": "tree"
}

Use cases

  • Sentiment and topic analysis — export flat comment and reply rows directly into a notebook or BI tool
  • Creator engagement audits — measure which comments the creator pinned, hearted, or replied to
  • Audience research — mine questions and recurring themes across a channel's recent uploads or a playlist
  • Moderation datasets — collect long reply threads with parent/root IDs for context-aware classifiers
  • Campaign monitoring — pull the last few days of comments from several videos with a date cutoff and minimum-like floor
  • Competitive research — compare comment volume and engagement on the same topic across search results

FAQ

Do I need a YouTube API key or cookies?

No. The actor reads publicly available comment data without login, user cookies, or an API key. It sends YouTube's standard consent cookie so the EU consent page does not replace the video page.

Why are some comments missing?

YouTube's "Top comments" feed exposes a relevance-ordered subset (roughly 800–1,200 comments on very large videos). Use sortBy: "newest" for complete date windows and deeper coverage. The actor also continues with the newest feed when the top feed is exhausted, and maxComments, maxTotalRows, and your filters can all stop a run earlier than the source total.

Are replies complete?

The actor follows every reply continuation until the thread token is exhausted when maxRepliesPerComment: 0. YouTube can hide deleted, spam, or moderated replies, so the advertised replyCount may be higher than the rows returned. repliesFetched tells you exactly how many were emitted for a comment.

Why are timestamps approximate?

The public comment payload only contains relative labels such as "2 days ago". The actor converts each label into a date range with a precision (hour, day, week, month, year) and exposes the earliest and latest possible time. It never claims an exact timestamp.

Why do like counts end in round numbers?

YouTube exposes formatted counts (1.2K, 69K, 4.8M). likeCount is the rounded integer and likeCountText preserves the original string.

What does a date cutoff do?

commentsSince forces the newest-first feed, keeps pinned comments, and stops paginating at the first non-pinned comment older than the cutoff. Because dates are approximate, a comment whose range overlaps the cutoff is kept.

What happens if a video is private, age-restricted, or has comments off?

The actor classifies the page and, when emitStatusRows is enabled, writes one status row explaining the outcome. It does not try to bypass login, age, or region restrictions.

What is the difference between flat, tree, and both?

flat emits one row per comment and reply. tree emits parent comment rows with a replies[] array (useful for older integrations). both emits flat reply rows and also attaches replies[] to each parent.

Do I need to configure a proxy?

No. The actor chooses a working connection to YouTube automatically. Just fill in the input and run it.

Is this affiliated with YouTube?

No. This is an independent tool and is not affiliated with, endorsed by, or sponsored by YouTube or Google.

Limitations

  • Exact comment timestamps and exact like counts are not available in the public payload.
  • Nested replies beyond direct level 1 are not exposed in the observed public payload; maxReplyDepth above 1 is reserved for future support.
  • The top-comments feed is capped by YouTube; use newest-first plus a date cutoff for exhaustive windows.
  • Age-restricted, private, members-only, and region-blocked videos are reported as statuses, not bypassed.
  • replyCount is advertised, not guaranteed; use repliesFetched for the delivered count.

Python quickstart

Use a schema-derived input and the Python standard library to start a run. An Apify account and token are required for execution.

Open the step-by-step Python guide →

Source build 0a5GXomczgZPh2iFX. Documentation is regenerated from the default published build; private drafts are not included.