dsc category¶
List, pull, push, and copy categories, plus sync category definitions.
Two distinct kinds of sync live under dsc category:
- Topic content —
pull/pushmove the Markdown topics inside a category. - Category definitions —
def pull/def pushandshow/get/setmanage the category objects themselves (name, colour, permissions, description, topic template, tag rules, ordering). This is the config-as-code counterpart todsc tag pull/pushanddsc setting pull/push.
dsc category list¶
Lists all categories with their IDs and names.
Flags:
--tree— print categories in a hierarchy, with subcategories indented under parents.-v,--verbose— include additional fields where supported.
dsc category pull¶
dsc category pull <discourse> <category-id-or-slug> [<local-path>] [--convert-admonitions <style>] [--rewrite-links]
Pulls the category into a directory of Markdown files. If <local-path> is omitted, writes to a new folder in the current directory (named from the category slug/name). Files are named from topic titles.
Each file gets YAML front matter binding it to its remote topic:
---
title: Dependency management
topic_id: 412
url: https://forum.example.com/t/dependency-management/412
pulled_at: 2026-06-22T09:19:00Z
---
[the topic's first post follows here as Markdown]
category push reads topic_id from this block to route updates by ID, so renaming the file or editing the title no longer risks creating a duplicate topic. The front matter is local-only metadata: dsc strips it before sending content to Discourse, so the --- block never reaches the published post. Files without front matter (e.g. ones you author by hand) still work - they fall back to slug/title matching.
--rewrite-links— rewrite same-forum links to pulled category topics as localfilename.mdpaths. The category export is flat, so it cannot reproduce an original parent-directory layout.
dsc category push¶
Pushes local Markdown files up to the category, updating existing topics and (by default) creating new ones for files with no remote match. Files are matched by topic_id front matter first, falling back to slug/title matching.
The push prints a plan with a sigil per file: ~ (update), + (create), = (unchanged, skipped). Bodies byte-identical to the remote post (ignoring trailing whitespace) are reported = and not re-sent.
Flags:
-n,--dry-run— print the plan without writing anything. Run this first and review before pushing for real.--updates-only— only update existing topics; error with a hint instead of silently creating a new topic when a file has no remote match. Use for curated categories where accidental topic creation must be impossible.--no-bump— update posts without bumping their topics to the top of the category activity feed (sendspost[no_bump]=true). Use for silent bulk maintenance edits.--skip-revision— update posts without recording an edit-history revision (sendspost[skip_revision]=true). Suppresses the online audit trail; use sparingly.-a,--convert-admonitions <quote-callouts|plain-blockquote>— convert MkDocs/Zensical admonitions while pushing, or the selected generated form back while pulling. Omit it to preserve raw Markdown.--rewrite-links— rewrite relative.mdlinks such as../versioning.mdto matching same-category forum topic URLs. Unresolved local targets are left intact and reported as warnings on stderr.
Admonition conversion¶
Use quote-callouts when the Quote Callouts theme component is installed and attached to the forum's active theme:
dsc category push forum 34 forum-export/ --convert-admonitions=quote-callouts
dsc category pull forum 34 forum-export/ --convert-admonitions=quote-callouts
It converts !!! warning "Title" to > [!warning] Title (and reverses it), preserves supported/custom callout types, and carries MkDocs ??? / ???+ folding to Quote Callouts - / +. It does not transform fenced code examples or ordinary blockquotes.
Choose plain-blockquote for a component-free target:
This generates a readable bold emoji lead-in inside a normal quote. It is the safer choice for email-heavy forums: Quote Callouts is browser-side theme styling, so email notifications contain the underlying quote rather than a styled callout. dsc does not detect component installation; selecting quote-callouts is an explicit deployment prerequisite.
Link rewriting¶
Use --rewrite-links to switch between a flat local category export and its same-forum topic URLs:
dsc category pull forum 34 forum-export/ --rewrite-links
dsc category push forum 34 forum-export/ --rewrite-links
On pull, https://forum.example.com/t/versioning/42#details becomes versioning.md#details when topic 42 is in the pulled category. Links to another forum or to a topic outside the category stay as full URLs.
On push, any relative .md path is resolved by its final filename stem, so ../versioning.md and versioning.md both resolve to the local versioning.md file's topic_id front matter and then to the current remote topic slug. Links with no matching category file are preserved and reported as warnings. Fenced code examples and image destinations are not rewritten.
Recommended workflow¶
dsc category pull forum 34 forum-export/ # snapshot (front matter embedded)
# edit files in forum-export/, commit to git for an offline audit trail
dsc category push -n forum 34 forum-export/ # review the plan
dsc category push forum 34 forum-export/ # apply
dsc category copy¶
Copies the specified category. If --target is omitted, copies within the same Discourse.
- The copied category name is set to
Copy of <original category name>. - The copied category slug is suffixed with
-copy(e.g.,staff->staff-copy). - All other fields match the source, except the ID which is assigned by Discourse.
<category-id-or-slug> can be found using dsc category list.
Category definitions¶
Version-control a forum's category structure the way tag pull/push handles the tag taxonomy.
dsc category def pull <discourse> [categories.yaml] # snapshot every definition to a file
dsc category def push <discourse> <categories.yaml> # apply the file (upsert; never deletes)
def pullwrites onecategories.yaml(or.jsonby extension) holding every category's definition - name, slug, colour, position, parent,read_restricted, description, topic template, permissions, tag rules, and display knobs. Usage counts and other volatile fields are dropped so re-pulls diff cleanly.def pushreconciles the server toward the file: it creates missing categories and updates changed ones, matching byid(stable), thenslug, thenname. It never deletes.--dry-runprints the plan with+(create),~(update),=(unchanged) sigils. A file entry with noidthat matches nothing is flagged loudly - it would create a new category, so if you meant to rename an existing one, keep itsidin the file to preserve its topics, or usedsc category renamebelow.- The push is idempotent: a pull followed by a push with no edits reports every category
= unchanged.
Single-field access¶
For a quick one-field read or edit without rewriting the whole file - mirrors dsc setting get/set and dsc theme show:
dsc category show <discourse> <category> # all definition fields
dsc category get <discourse> <category> <field> # one field
dsc category set <discourse> <category> <field> <value>
<category>resolves byid,slug, orname.<field>is one of:name,slug,color,text_color,position,parent,read_restricted,description,topic_template,permissions,allowed_tags,allowed_tag_groups,minimum_required_tags,sort_order,default_view,subcategory_list_style,num_featured_topics,show_subcategory_list.- List fields (
allowed_tags,allowed_tag_groups) take a comma-separated value; an empty value clears the list. permissionstakesgroup:level,...where level isfull,create_post, orreadonly(e.g.staff:full). Granting any group other thaneveryonealso setsread_restricted=true, matching the admin UI.show/gethonour--format text|json|yaml;sethonours the global--dry-run.
Notes:
descriptionis read from the plain-text form; on write, Discourse re-cooks it as the category's "About" topic excerpt (settles a moment after a create).- When
def pushcreates a category whoseparentis itself brand-new in the same file, run the push twice (or create the parent first) - a parent is resolved against categories that already exist on the server.
dsc category diff¶
Compare the stable, editable definition fields of two explicitly selected live categories:
dsc category diff forum-a general forum-b announcements
dsc category diff forum-a 12 forum-b 37 --format json
Each category resolves independently by id, slug, or name, so the categories may have different names or slugs. The diff reports only changed definition fields from the same surface as category show/get/set; it excludes server-specific IDs and volatile usage counts. --format json|yaml preserves booleans, numbers, arrays, and permission maps as native values rather than display strings.
Aliases: df.
dsc category rename¶
Rename a category, preserving its topics. Uses a safe PUT /categories/{id}.json by the resolved category id - the recommended path when a categories.yaml entry has no id, since def push can't tell a rename from a delete+create in that case (see the note above).
dsc category rename myforum old-name new-name # rename
dsc -n category rename myforum old-name new-name # dry-run
<category> resolves by id, slug, or name, same as show/get/set.
Aliases: rn.
Refuses to run when:
- the category does not resolve (bad id/slug/name),
- a different category already has the new name,
- old and new names are identical after trimming,
- the new name is empty after trimming.