TikTok Caption Writer
Write a TikTok caption under 2,200 chars (hashtags included), pick a tasteful 3 to 5 hashtag set with mixed reach, and set the platformSettings.tiktok flags (viewer setting, comments, duet, stitch, commercial and branded content).
How to use it
- Hit Copy the whole skill.
- Claude: ⋯ → Download .md, then Customize → Skills → Add → Upload skill.
ChatGPT: make a Project and paste it into Instructions.
Neither? Paste it at the top of a new chat — it works for that chat. - Describe your job in plain words. The AI follows the skill from there.
npx degit sergebulaev/tiktok-skills/.codex-marketplace/tiktok-skills/skills/tt-caption-writer#main ~/.claude/skills/tt-caption-writerFor one project only, change the path to .claude/skills/tt-caption-writer.
Not working?
- Check which app you pasted it into — the steps above name the right one.
- Some skills need the paid tier of Claude or ChatGPT.
Paste into Claude, ChatGPT or Cursor.
Show the full text135 lines
TikTok Caption Writer
Write the caption that sits under the video: a tight caption inside the 2,200
char API limit, a mixed-reach hashtag set, and the platformSettings.tiktok
flags that control reach and interaction. On TikTok the caption supports the
video, it does not carry it. The hook is in the first 1-3 seconds of the clip
(see tt-hook-scripter); the caption's job is to add context and earn a comment.
When to use
- User has a video (or a script) and needs the caption, hashtags, and settings
- User is about to upload and wants the posting flags right
- User wants Claude Code or Codex to schedule a rendered .mp4 via Publora
What this skill produces
- Caption (<= 2,200 chars on the API; aim much shorter), first line front- loaded with a reason to read or comment
- Hashtag set (3 to 5, mixed reach), placed at the end
- platformSettings.tiktok flags with a plain-English summary
- On approval, the publish call (draft -> upload -> schedule) when a video file is supplied
The TikTok settings (platformSettings.tiktok)
Build these with lib.tiktok_settings(...). Defaults match Publora's.
| Setting | Values / default | What it does |
|---|---|---|
viewerSetting |
PUBLIC_TO_EVERYONE (default), MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY | who can view. Effectively required; an empty value is rejected |
allowComments |
true (default) | viewers can comment |
allowDuet |
false (default) | viewers can Duet |
allowStitch |
false (default) | viewers can Stitch |
commercialContent |
false (default) | the video is commercial |
brandOrganic |
false (default) | promoting your own brand |
brandedContent |
false (default) | paid partnership / sponsored |
Two gotchas you must surface to the user
- Boolean inversion bug. Publora currently maps
allowComments,allowDuet,allowStitchto TikTok'sdisable_*flags, so the booleans can land inverted (sendingallowDuet: truemay disable duets). The status cannot be confirmed from the API alone. Test with aSELF_ONLYdraft before trusting these values. Tell the user this whenever they change a flag. - Commercial disclosure. If
commercialContentis true, at least one ofbrandOrganic(your own brand) orbrandedContent(paid partnership) must also be true, or Publora rejects it.lib.tiktok_settingsenforces this.
Posting reality
- Unaudited apps post PRIVATE only. Until the publishing app passes TikTok's
review, posts are forced to
SELF_ONLYregardless ofviewerSetting. Warn the user if a "public" post lands private. - For reach, use
PUBLIC_TO_EVERYONE. For an end-to-end test, useSELF_ONLY.
Steps
Voice profile first (all drafts). If ../../references/voice-profile.md has filled: yes, load it and match the user's voice fingerprint, hard rules, and CTA/link style throughout. If it is not filled, mention once that tt-humanizer --mode profile can learn their voice from a few posts, then proceed with the generic voice rules.
- Gather inputs, goal first. Ask (or infer) what the caption should earn, then shape for it: comments -> end on one specific question tied to the video; saves -> promise reference value ("the 3 settings are in the caption"); shares -> a claim people will want to show someone; profile taps -> an open loop the pinned video pays off. Then gather the video topic/script, the niche, whether it is commercial, and whether a rendered .mp4 path exists.
- Draft the caption. Front-load the first visible line with a reason to read or a specific question to comment on. Keep it tight. Em dashes under the cap (about one per 100 words; a short caption rarely needs one), no AI vocab cluster. Optionally restate the hook's open loop as a comment prompt.
- Pick hashtags. 3 to 5, mixed reach: one broad, one to two niche-defining, one to two specific to the video. Put them at the end. Never stuff.
- Set the flags. Default to
PUBLIC_TO_EVERYONE, comments on, duet/stitch off, no commercial flags. Adjust per the user, and surface the two gotchas. Build withlib.tiktok_settings(...). - Char check. Confirm caption + hashtags <= 2,200. If over, tighten.
- Humanizer pass. Scrub 2026 AI vocab by density, cap em dashes (about
one per 100 words, never swap one for a period), break stacked triads,
generic openers, reveal bridges and sincerity openers ("not gonna lie").
Leave the rhythm alone; never chop a line for punch. Canonical rules:
tt-humanizerV3. - Approval card. Show: caption, char count, hashtag set, settings summary,
and the resolved
viewerSetting(flag if it may post private). - On approval. Call
lib.publish("video", caption, target_url=<upload or profile URL>, video_path=<path or None>, platform_settings=<settings>, scheduled_time=<iso or None>, platforms=[<TIKTOK_PLATFORM_ID>]). With a video path and Publora configured, it runs draft -> upload -> schedule. Without one, it returns the caption and settings to upload in-app.
Hard rules
Global voice rules: see root SKILL.md Voice rules. Additional skill-specific
rules:
- Caption + hashtags must fit 2,200 chars (API), hashtags included. Aim far under.
- 3 to 5 hashtags, mixed reach, at the end. Never 10+, never mid-sentence.
- The caption never does the hook's job. The hook is in the video.
- Always set
viewerSetting. Never ship an empty value. - Surface the boolean inversion warning whenever an interaction flag changes.
Anti-patterns (skill will refuse)
- Hashtag walls (10+), or hashtags jammed into the sentence.
- Em dashes above the cap (more than about one per 100 words), or an em dash swapped for a period.
- "The result?" reveals and "No X. No Y. Just Z." staccato stacks added for punch.
- "Follow for part 2 / like and subscribe / don't forget to share" stacks.
- A caption that repeats the spoken hook word for word.
commercialContent: truewith neither brand flag set.- Claiming a post will be public when the app is unaudited (it will be private).
Resources
../../references/voice-rules.md- caption and hashtag rules../../references/algorithm-heuristics.md- caption, hashtag, and settings heuristicsreferences/settings-matrix.md- every platformSettings.tiktok value, defaults, and the known bugslib/publora_client.py-tiktok_settings(...),publish_video(...),create_draft(...)
Optional illustration
Offer a generated image when a visual would lift reach. Draft a prompt and call
lib.illustrate(prompt, kind="story"), pulling brand handle/color from Voice &
Brand Profile section 6 for a pixel-exact overlay. Show the returned url + cost,
for a TikTok photo post, attach the image via media_urls=[url] (TikTok photo carousel); for a video, TikTok's API has no cover-image field, so set the cover in the TikTok app. Full workflow (incl. quote-cards):
../tt-humanizer/sub-skills/illustration.md. No Pixfaro key -> it drafts the prompt for you to generate manually.
Related skills
tt-hook-scripter- the in-video hook (the caption does not replace it)tt-humanizer- scrub the caption before publishingtt-content-planner- schedule captions across a posting week
| 1 | |
| 2 | name tt-caption-writer |
| 3 | description "Write a TikTok caption under 2,200 chars (hashtags included), pick a tasteful 3 to 5 hashtag set with mixed reach, and set the platformSettings.tiktok flags (viewer setting, comments, duet, stitch, commercial and branded content). Front-loads a reason to comment, runs the humanizer pass, and publishes the rendered video via Publora on approval. Use to caption a finished video. Not for the in-video hook (use tt-hook-scripter) or scrubbing a script (use tt-humanizer)." |
| 4 | |
| 5 | |
| 6 | # TikTok Caption Writer |
| 7 | |
| 8 | Write the caption that sits under the video: a tight caption inside the 2,200 |
| 9 | char API limit, a mixed-reach hashtag set, and the `platformSettings.tiktok` |
| 10 | flags that control reach and interaction. On TikTok the caption supports the |
| 11 | video, it does not carry it. The hook is in the first 1-3 seconds of the clip |
| 12 | (see `tt-hook-scripter`); the caption's job is to add context and earn a comment. |
| 13 | |
| 14 | ## When to use |
| 15 | |
| 16 | User has a video (or a script) and needs the caption, hashtags, and settings |
| 17 | User is about to upload and wants the posting flags right |
| 18 | User wants Claude Code or Codex to schedule a rendered .mp4 via Publora |
| 19 | |
| 20 | ## What this skill produces |
| 21 | |
| 22 | **Caption** (<= 2,200 chars on the API; aim much shorter), first line front- |
| 23 | loaded with a reason to read or comment |
| 24 | **Hashtag set** (3 to 5, mixed reach), placed at the end |
| 25 | **platformSettings.tiktok** flags with a plain-English summary |
| 26 | On approval, the publish call (draft -> upload -> schedule) when a video file |
| 27 | is supplied |
| 28 | |
| 29 | ## The TikTok settings (platformSettings.tiktok) |
| 30 | |
| 31 | Build these with `lib.tiktok_settings(...)`. Defaults match Publora's. |
| 32 | |
| 33 | | Setting | Values / default | What it does | |
| 34 | |---|---|---| |
| 35 | | `viewerSetting` | PUBLIC_TO_EVERYONE (default), MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY | who can view. Effectively required; an empty value is rejected | |
| 36 | | `allowComments` | true (default) | viewers can comment | |
| 37 | | `allowDuet` | false (default) | viewers can Duet | |
| 38 | | `allowStitch` | false (default) | viewers can Stitch | |
| 39 | | `commercialContent` | false (default) | the video is commercial | |
| 40 | | `brandOrganic` | false (default) | promoting your own brand | |
| 41 | | `brandedContent` | false (default) | paid partnership / sponsored | |
| 42 | |
| 43 | ### Two gotchas you must surface to the user |
| 44 | |
| 45 | **Boolean inversion bug.** Publora currently maps `allowComments`, |
| 46 | `allowDuet`, `allowStitch` to TikTok's `disable_*` flags, so the booleans can |
| 47 | land inverted (sending `allowDuet: true` may disable duets). The status cannot |
| 48 | be confirmed from the API alone. **Test with a `SELF_ONLY` draft before |
| 49 | trusting these values.** Tell the user this whenever they change a flag. |
| 50 | **Commercial disclosure.** If `commercialContent` is true, at least one of |
| 51 | `brandOrganic` (your own brand) or `brandedContent` (paid partnership) must |
| 52 | also be true, or Publora rejects it. `lib.tiktok_settings` enforces this. |
| 53 | |
| 54 | ### Posting reality |
| 55 | |
| 56 | **Unaudited apps post PRIVATE only.** Until the publishing app passes TikTok's |
| 57 | review, posts are forced to `SELF_ONLY` regardless of `viewerSetting`. Warn the |
| 58 | user if a "public" post lands private. |
| 59 | For reach, use `PUBLIC_TO_EVERYONE`. For an end-to-end test, use `SELF_ONLY`. |
| 60 | |
| 61 | ## Steps |
| 62 | |
| 63 | **Voice profile first (all drafts).** If `../../references/voice-profile.md` has `filled: yes`, load it and match the user's voice fingerprint, hard rules, and CTA/link style throughout. If it is not filled, mention once that `tt-humanizer --mode profile` can learn their voice from a few posts, then proceed with the generic voice rules. |
| 64 | |
| 65 | **Gather inputs, goal first.** Ask (or infer) what the caption should earn, |
| 66 | then shape for it: comments -> end on one specific question tied to the video; |
| 67 | saves -> promise reference value ("the 3 settings are in the caption"); |
| 68 | shares -> a claim people will want to show someone; profile taps -> an open |
| 69 | loop the pinned video pays off. Then gather the video topic/script, the |
| 70 | niche, whether it is commercial, and whether a rendered |
| 71 | .mp4 path exists. |
| 72 | **Draft the caption.** Front-load the first visible line with a reason to read |
| 73 | or a specific question to comment on. Keep it tight. Em dashes under the cap |
| 74 | (about one per 100 words; a short caption rarely needs one), no AI vocab |
| 75 | cluster. Optionally restate the hook's open loop as a comment prompt. |
| 76 | **Pick hashtags.** 3 to 5, mixed reach: one broad, one to two niche-defining, |
| 77 | one to two specific to the video. Put them at the end. Never stuff. |
| 78 | **Set the flags.** Default to `PUBLIC_TO_EVERYONE`, comments on, duet/stitch |
| 79 | off, no commercial flags. Adjust per the user, and surface the two gotchas. |
| 80 | Build with `lib.tiktok_settings(...)`. |
| 81 | **Char check.** Confirm caption + hashtags <= 2,200. If over, tighten. |
| 82 | **Humanizer pass.** Scrub 2026 AI vocab by density, cap em dashes (about |
| 83 | one per 100 words, never swap one for a period), break stacked triads, |
| 84 | generic openers, reveal bridges and sincerity openers ("not gonna lie"). |
| 85 | Leave the rhythm alone; never chop a line for punch. Canonical rules: |
| 86 | `tt-humanizer` V3. |
| 87 | **Approval card.** Show: caption, char count, hashtag set, settings summary, |
| 88 | and the resolved `viewerSetting` (flag if it may post private). |
| 89 | **On approval.** Call `lib.publish("video", caption, target_url=<upload or |
| 90 | profile URL>, video_path=<path or None>, platform_settings=<settings>, |
| 91 | scheduled_time=<iso or None>, platforms=[<TIKTOK_PLATFORM_ID>])`. With a video |
| 92 | path and Publora configured, it runs draft -> upload -> schedule. Without one, |
| 93 | it returns the caption and settings to upload in-app. |
| 94 | |
| 95 | ## Hard rules |
| 96 | |
| 97 | Global voice rules: see root `SKILL.md` Voice rules. Additional skill-specific |
| 98 | rules: |
| 99 | |
| 100 | Caption + hashtags must fit 2,200 chars (API), hashtags included. Aim far under. |
| 101 | 3 to 5 hashtags, mixed reach, at the end. Never 10+, never mid-sentence. |
| 102 | The caption never does the hook's job. The hook is in the video. |
| 103 | Always set `viewerSetting`. Never ship an empty value. |
| 104 | Surface the boolean inversion warning whenever an interaction flag changes. |
| 105 | |
| 106 | ## Anti-patterns (skill will refuse) |
| 107 | |
| 108 | Hashtag walls (10+), or hashtags jammed into the sentence. |
| 109 | Em dashes above the cap (more than about one per 100 words), or an em dash swapped for a period. |
| 110 | "The result?" reveals and "No X. No Y. Just Z." staccato stacks added for punch. |
| 111 | "Follow for part 2 / like and subscribe / don't forget to share" stacks. |
| 112 | A caption that repeats the spoken hook word for word. |
| 113 | `commercialContent: true` with neither brand flag set. |
| 114 | Claiming a post will be public when the app is unaudited (it will be private). |
| 115 | |
| 116 | ## Resources |
| 117 | |
| 118 | `../../references/voice-rules.md` - caption and hashtag rules |
| 119 | `../../references/algorithm-heuristics.md` - caption, hashtag, and settings heuristics |
| 120 | `references/settings-matrix.md` - every platformSettings.tiktok value, defaults, and the known bugs |
| 121 | `lib/publora_client.py` - `tiktok_settings(...)`, `publish_video(...)`, `create_draft(...)` |
| 122 | |
| 123 | ## Optional illustration |
| 124 | |
| 125 | Offer a generated image when a visual would lift reach. Draft a prompt and call |
| 126 | `lib.illustrate(prompt, kind="story")`, pulling brand handle/color from Voice & |
| 127 | Brand Profile section 6 for a pixel-exact overlay. Show the returned `url` + `cost`, |
| 128 | for a TikTok photo post, attach the image via `media_urls=[url]` (TikTok photo carousel); for a video, TikTok's API has no cover-image field, so set the cover in the TikTok app. Full workflow (incl. quote-cards): |
| 129 | `../tt-humanizer/sub-skills/illustration.md`. No Pixfaro key -> it drafts the prompt for you to generate manually. |
| 130 | ## Related skills |
| 131 | |
| 132 | `tt-hook-scripter` - the in-video hook (the caption does not replace it) |
| 133 | `tt-humanizer` - scrub the caption before publishing |
| 134 | `tt-content-planner` - schedule captions across a posting week |
| 135 |