Use the API to create restricted categories for external groups

If you have a community site that also (but not exclusively) uses Discourse, you may want to provide a restricted subforum for each of your groups, for example as a workspace. Here’s how, in Ruby:

  1. Ensure that your API calls authenticate as a user with Admin privileges.

  2. Create a forum group including your group’s leaders and regular users

params = { 
  "group": { 
    "name": "<group_name_with_underscores>", 
    "full_name": "<group name>", 
    "title": "Member of <group name>", # optional: forum flair for group members
    "mentionable_level": 99, # optional: who can @ mention this group?
    "messageable_level": 99, # optional: who can message the whole group?
    "owner_usernames": "<comma-separated forum usernames of your group leaders>",
    "usernames": "<comma-separated forum usernames of the other group members>"
r = Discourse.make_request("/admin/groups.json", params, "POST", true)

If successful, find the ‘id’ field in the response and remember it; that’s your group’s ID.

  1. Keep the external group and forum group in sync
# new simple members
r = Discourse.make_request("/groups/#{group_id}/members.json", {"usernames": "<new_members as comma-separated string>"}, "PUT", true)
# new leaders
r = Discourse.make_request("/admin/groups/#{group_id}/owners.json", {"group": { "usernames": "<new leader forum usernames as comma-separated string>"}}, "PUT", true)
# remove members who left
r = Discourse.make_request("/groups/#{group_id}/members.json", {"usernames": "<leaving_members as comma-separated string>"}, "DELETE", true)
  1. Create a subcategory (aka subforum) that is only accessible to this group. I recommend manually creating a subforum “Workspaces” or “Restricted” or whatever, which will be the parent category for all of these, and placing it at the bottom of your categories. Note that this container-forum must have “Everyone can see” (otherwise Discourse will give an error) and probably shouldn’t allow anyone to write. Then, you can programmatically create subforums for each new group.
params = { 
  "name": "<subforum name, e.g. same as group full name>", 
  "color": "<whatever>", # optional
  "text_color": "<whatever>", # optional
  "permissions": { 
    "<your group name with underscores>": "1", 
    "admins": "1", # if you don't have this, you may be unable to delete your subforum except through API
    "moderators": "1" 
  "parent_category_id": "<see note above>", 
  "allow_badges": true,  # optional
  "suppress_from_latest": true, # optional
r = Discourse.make_request("/categories.json", params, "POST", true)

If successful, remember the category ID that you get in return.

  1. You may want to pre-populate this subforum. I will give my group a pinned wiki post for knowledge-sharing and planning.
# make a regular post in this subforum
post_msg = "Welcome! This post is editable by all members of the group and can be used as a knowledge repository as well as to track common projects.\n\n**The following is a template you can decide to use or not.**\n\n**Useful links for group members:**\n\n- Link 1\n\n- Link 2\n\n- Link 3\n\n**These are the projects we are currently working on:**\n\n- Project 1 - *started*. Involved: Member 1, 2 and 3\n\n- Project 2 - *planning phase*. To be discussed at our next meeting on Monday."
params = { "raw": post_msg, title: "Knowledge Wiki for #{group_name}", "category": category_id, "archetype": "regular"}
r = Discourse.make_request("/posts", params, "POST", true)
if r and r.status == 200
  my_post = JSON.parse(r.body)
  post_id = my_post["id"]
  topic_id = my_post["topic_id"]
  # turn it into a wiki
    r = Discourse.make_request("/posts/#{post_id}/wiki", {"wiki": true}, "PUT", false)
    rescue JSON::ParserError  # Discourse API will give bad JSON response, but fortunately we don't have to parse this
  # pin this
  r = Discourse.make_request("/t/topic/#{topic_id}/status", { 
    "status": "pinned", 
    "enabled": "true", # careful: must be a string, otherwise unpins the topic
    "until": "3020-01-01 08:00+1:00" # forever
  }, "PUT", false)

Have fun!