Documentation Index
Fetch the complete documentation index at: https://docs.signalrooms.xyz/llms.txt
Use this file to discover all available pages before exploring further.
Captions
Status: Current operator how-to. Caption sources are configured on the template’s Set Description toggle and apply to posting runs (Run Mode = Upload) and onboarding runs (Run Mode = Seeding).
A caption is the text TikTok posts alongside a video or carousel. The template picks one of four sources: Inline text, TXT file, JSON file, or Sidecar files. This page covers when to pick each, the exact file formats, and how variants get selected.
Turning it on
The Set Description toggle on the template:
- Off → the TikTok caption field is left empty.
- On → pick one of the four sources below.
Each template has a “Download example” link next to the field that points at a working example file.
Cheat sheet: which source to pick
┌─ One-or-two variants for all posts → Inline text
│
same captions ├─ 10+ simple captions, no titles → Text file (.txt)
across all clips ───┤
└─ Need titles, need Sequential → JSON file
unique caption ──→ Sidecar (video: <name>.json,
per file carousel: content.json in subfolder)
Option 1. Inline text: multiple captions separated by ;
When to use
A handful of short captions and you don’t want a separate file.
What to set
- Source → Text.
- Descriptions field → all variants on one line, separated by
;.
Example
First caption #tag1; Second caption with emoji 🎬; Third caption @mention #hot
How variants are picked
Random without repeats: the runner cycles variants randomly, marking each as used. Once every variant is consumed, the cycle restarts.
Pitfalls
- A literal
; in a caption is treated as a separator, move that caption to a .txt or sidecar instead.
- Empty blocks between
;; are ignored.
Option 2. Text file: .txt, one caption per line
When to use
10+ descriptions and you’d rather keep them in an external file editable in any text editor.
What to set
- Source → TXT file.
- Descriptions file (.txt) → absolute path to a
.txt (extension must be .txt; .text, .md, .csv are ignored).
- Encoding: UTF-8 (BOM supported).
- Line endings: LF or CRLF (normalized).
- One line = one caption.
- Empty lines are ignored.
- Hashtags and emoji go inline in the caption text, there are no separate fields.
Example
Put your caption here, replace this line with your own text
Add hashtags inline like this #example #replace_me
Emojis work too 🎬, drop them anywhere in the caption
Each line in this file becomes one TikTok caption
Empty lines are ignored, max 2200 characters per caption
How variants are picked
Random without repeats, same as inline text.
Pitfalls
- Don’t use
# for comments, a line starting with # will be posted as a caption with a hashtag.
- Only
.txt is parsed. Renaming a .md to .txt works; pointing at a .md does not.
Option 3. JSON file: {title, description} per post
When to use
- Carousels need separate titles (TikTok’s Title field).
- You need sequential ordering (not just random).
- Content is generated by a pipeline that prefers structured storage.
What to set
- Source → JSON file.
- Title+Description file (.json) → absolute path.
- Selection order:
- Sequential: items used in order: first, second, third…
- Random: random without repeats.
The root is an array; each element is an object with two fields:
[
{ "title": "...", "description": "..." },
{ "title": "...", "description": "..." }
]
| Field | Required? | Notes |
|---|
description | Yes | Caption text, ≤ 2200 characters. |
title | Optional | Ignored for videos (TikTok has no video title). Filled in for carousels if non-empty; empty/missing leaves the carousel’s Title field untouched. |
Any other keys (e.g. _comment) are ignored by the parser, keep inline comments in your file if you like.
Example
[
{
"_comment": "This key is ignored by the parser",
"title": "Put your title here",
"description": "Put your description here. Replace this with your TikTok caption #hashtag"
},
{
"title": "",
"description": "Leave title empty if you don't want one. Only the description will be set"
},
{
"title": "Add hashtags inline",
"description": "Hashtags go inside the description, not a separate field 🎯 #example #replace"
}
]
Pitfalls
- JSON must be valid. Any syntax error and the source falls back to the legacy
.txt / inline text, if defined.
- Empty array
[] → no descriptions → fallback fires.
- Encoding must be UTF-8.
When to use
- Each video or carousel has its own unique caption, and a strict “this caption → this file” binding matters.
- Content is prepared by an external pipeline that drops a caption next to each clip.
- In onboarding runs, to avoid mixing up which caption belongs to which clip.
What to set
- Source → Sidecar files.
- If sidecar is missing → what to do when a file has no sidecar:
- Skip description (default), leave the TikTok caption empty.
- Use inline text: fall back to the global Descriptions field.
- Use global .txt: fall back to the global
.txt.
- Use global .json: fall back to the global
.json.
Recommended object form:
{
"title": "Put your title here",
"description": "Put your description here. Replace this with your TikTok caption #hashtag"
}
An array form is also accepted for backwards compatibility, the first element is used:
[
{ "title": "...", "description": "..." }
]
Where to put the sidecar
Video
A <video_filename>.json in the same folder next to the video:
videos/
├── clip_1.mp4
├── clip_1.json ← caption for clip_1.mp4
├── clip_2.mov
├── clip_2.json ← caption for clip_2.mov
└── clip_3.mp4 ← no sidecar, fallback kicks in
For videos, title is always ignored: TikTok doesn’t display a title for videos.
Carousel: subfolder mode (supported)
The sidecar lives inside the carousel subfolder. Filename is picked by priority (case-insensitive):
content.json
description.json
caption.json
- Any first
*.json (natural sort).
carousels/
├── 01_summer/
│ ├── 1.jpg
│ ├── 2.jpg
│ ├── 3.jpg
│ └── content.json ← priority 1
├── 02_promo/
│ ├── photo_a.png
│ ├── photo_b.png
│ └── description.json ← priority 2
└── 03_lifestyle/
├── img1.png
└── caption.json ← priority 3
If a subfolder contains multiple .json files and none match the priority list, the first by natural sort wins and a WARNING is logged. Don’t introduce ambiguity, name your file using one of the priority names.
Carousel: flat mode (not supported)
Sidecars in flat carousel mode don’t work, there’s no unambiguous way to bind photos to a group. If you need per-carousel captions, switch to subfolder mode.
What happens to the sidecar after publishing
- Video:
<name>.json moves to used/ (or is deleted) together with the video, in sync.
- Carousel in subfolders: the entire subfolder moves to
used/ as a unit; the sidecar inside goes along.
| What | Where | Notes |
|---|
| Hashtags | Inside the description text | No separate field: "My caption #travel #2026" |
Mentions @user | Inside the description text | The runner catches the suggestion popup while typing |
| Title | JSON / sidecar, title field | Ignored for videos. Filled in for carousels if non-empty. |
| Music | Separate Music card on the template | Not part of the description, see Posting runs. |
| Multi-language captions | Inside the text | Any Unicode is supported. |
Limits and normalization
- Maximum 2200 characters per caption (TikTok’s hard cap).
- Longer captions are truncated to 2200 and a WARNING is logged.
- A BOM (
) at the start of a file is stripped automatically.
- Line endings are normalized (
\r\n → \n, \r → \n).
- Leading and trailing whitespace is trimmed.
Troubleshooting
| Symptom | Cause | Fix |
|---|
| Empty caption on TikTok | Sidecar missing, fallback = Skip | Add a sidecar, or switch fallback to inline / .txt / .json |
| Same text in a loop | Few inline variants, cycle restarts | Add variants via ; or switch to .txt / .json |
| Title didn’t appear on a carousel | title empty or missing | Add a non-empty "title" |
| Title didn’t appear on a video | Expected, videos have no Title field | Move text into description |
| WARNING “Multiple non-priority sidecars” | Several .json in a carousel subfolder, none priority | Rename the right one to content.json |
| Caption truncated | > 2200 characters | Shorten the caption |
| JSON not parsing | Broken syntax or non-UTF-8 | Validate JSON; check encoding |
| Sidecar not picked up mid-run | Cache populates at thread start | Restart the thread |