On This Page

Create and Manage Forum Channels

Recognize forum and media channels, create them with tags and post settings, change one tag at a time and read their posts

A forum channel holds posts instead of messages. Each post is a public thread with its own first message. A media channel is a forum channel that can also hide media download options

The examples use the default API. The Effect entry point exports the same names

Recognize a Forum Channel

Compare type with ChannelType.Forum or ChannelType.Media. Both shapes share the post settings below, and only a forum channel has defaultForumLayout

import { ChannelFlags, ChannelType } from "@neontechspace/fluxerly"

export function forumSummary(channel) {
    if (channel.type !== ChannelType.Forum && channel.type !== ChannelType.Media)
        return undefined
    return {
        guidelines: channel.topic,
        tags: (channel.availableTags ?? []).map((tag) => tag.name),
        requiresTag: ((channel.flags ?? 0) & ChannelFlags.RequireTag) !== 0,
    }
}
import { ChannelFlags, ChannelType, type GuildChannel } from "@neontechspace/fluxerly"

export function forumSummary(channel: GuildChannel) {
    if (channel.type !== ChannelType.Forum && channel.type !== ChannelType.Media) return undefined
    return {
        guidelines: channel.topic,
        tags: (channel.availableTags ?? []).map((tag) => tag.name),
        requiresTag: ((channel.flags ?? 0) & ChannelFlags.RequireTag) !== 0,
    }
}

The channels.fetchAll method lists forum and media channels but never their posts. The channels.fetch method returns a post as a thread shape when the ID names one, and channels.get can return one when the cache holds it, so compare type or use isThreadChannel before reading fields that only some shapes have

Read Post Settings

Fluxer leaves the post settings out of some payloads, such as the guildChannelDelete event. Each setting is therefore optional, and its absence means that it is unavailable, not that it has its default

Flag bits, sort orders, layouts and tag settings that this SDK version does not name are kept as Fluxer sent them. Audit-log entries for forum channel changes record available_tags as ForumTag values and default_reaction_emoji as a ForumDefaultReaction

Read Posts

Each post is a GuildPublicThreadChannel whose parentId is the forum or media channel. The appliedTagIds field lists the post's tags, and ChannelFlags.Pinned in flags marks the channel's pinned post. Read threads covers the fields every thread shares

import { ChannelType } from "@neontechspace/fluxerly"

export function postTagNames(forum, post) {
    if (post.type !== ChannelType.PublicThread || post.parentId !== forum.id)
        return []
    const names = new Map((forum.availableTags ?? []).map((tag) => [tag.id, tag.name]))
    return (post.appliedTagIds ?? []).map((id) => names.get(id) ?? id)
}
import { ChannelType, type GuildForumChannel, type GuildThreadChannel } from "@neontechspace/fluxerly"

export function postTagNames(forum: GuildForumChannel, post: GuildThreadChannel) {
    if (post.type !== ChannelType.PublicThread || post.parentId !== forum.id) return []
    const names = new Map((forum.availableTags ?? []).map((tag) => [tag.id, tag.name]))
    return (post.appliedTagIds ?? []).map((id) => names.get(id) ?? id)
}

Create a Forum or Media Channel

A channels.create call takes ChannelType.Forum or ChannelType.Media together with the channel's tags and post settings. The bot needs ManageChannels, and Fluxer rejects forum and media channels in a community where threads are not active, which threadsActive on guilds.fetchPage reports

import { ChannelFlags, ChannelType, ForumLayout, ForumSortOrder } from "@neontechspace/fluxerly"

export async function createHelpForum(client, guildId) {
    const result = await client.channels.create(guildId, {
        type: ChannelType.Forum,
        name: "help",
        topic: "Ask one question per post and say what was already tried",
        availableTags: [
            { name: "bug", emojiName: "🐛" },
            { name: "question" },
            { name: "staff", moderated: true },
        ],
        defaultSortOrder: ForumSortOrder.CreationTime,
        defaultForumLayout: ForumLayout.List,
        flags: ChannelFlags.RequireTag,
    })
    return result.map((channel) => channel.id)
}
import { ChannelFlags, ChannelType, ForumLayout, ForumSortOrder, type Client } from "@neontechspace/fluxerly"

export async function createHelpForum(client: Client, guildId: string) {
    const result = await client.channels.create(guildId, {
        type: ChannelType.Forum,
        name: "help",
        topic: "Ask one question per post and say what was already tried",
        availableTags: [
            { name: "bug", emojiName: "🐛" },
            { name: "question" },
            { name: "staff", moderated: true },
        ],
        defaultSortOrder: ForumSortOrder.CreationTime,
        defaultForumLayout: ForumLayout.List,
        flags: ChannelFlags.RequireTag,
    })
    return result.map((channel) => channel.id)
}

These settings are specific to forum and media channels, and the SDK rejects a setting that the created channel type cannot have before it sends anything

Set Thread Defaults on Any Thread Parent

Text, announcement, forum and media channels accept defaultAutoArchiveMinutes, one of the ThreadAutoArchiveMinutes values, and defaultThreadRateLimitPerUser, a slowmode in seconds from 0 to 21,600. A new thread copies the slowmode when its creation sets none. Fluxer stores the archive period but does not apply it, so a new thread uses ThreadAutoArchiveMinutes.ThreeDays unless its creation sets another period. In a community where threads are not active, Fluxer ignores both settings on text and announcement channels. Pass null to clear a default

Change a Forum Channel

A channels.edit call accepts the same settings as creation, and it leaves every omitted setting unchanged. Passing null resets defaultReactionEmoji, defaultSortOrder, defaultForumLayout and defaultTagSetting. The flags value replaces all flags, so include ChannelFlags.RequireTag to keep it

The availableTags list replaces the whole tag list. An entry with an id keeps that tag and replaces its settings, an entry without one adds a tag, and a tag left out is deleted, after which posts stop showing it. A received ForumTag is valid input, so passing back forum.availableTags keeps every tag. An edit cannot see the channel's type, so Fluxer ignores a setting that does not belong to it, such as availableTags sent for a text channel

Change One Tag

Changing a single tag through a full list risks overwriting a concurrent change. The channels.createForumTag, channels.editForumTag and channels.deleteForumTag methods change one tag and return the updated channel. Each needs ManageChannels and accepts an auditReason

export async function addTag(client, forumId, name) {
    const updated = await client.channels.createForumTag(forumId, { name })
    return updated.map((channel) => channel.availableTags?.find((tag) => tag.name === name)?.id)
}

export async function moderateTag(client, forum, tagName) {
    const tag = forum.availableTags?.find((candidate) => candidate.name === tagName)
    if (tag === undefined)
        return undefined
    // Editing a tag replaces all of its settings, so the received tag supplies the ones that stay
    return await client.channels.editForumTag(forum.id, tag.id, { ...tag, moderated: true })
}

export async function removeTag(client, forumId, tagId) {
    return await client.channels.deleteForumTag(forumId, tagId)
}
import type { Client, GuildForumChannel } from "@neontechspace/fluxerly"

export async function addTag(client: Client, forumId: string, name: string) {
    const updated = await client.channels.createForumTag(forumId, { name })
    return updated.map((channel) => channel.availableTags?.find((tag) => tag.name === name)?.id)
}

export async function moderateTag(client: Client, forum: GuildForumChannel, tagName: string) {
    const tag = forum.availableTags?.find((candidate) => candidate.name === tagName)
    if (tag === undefined) return undefined
    // Editing a tag replaces all of its settings, so the received tag supplies the ones that stay
    return await client.channels.editForumTag(forum.id, tag.id, { ...tag, moderated: true })
}

export async function removeTag(client: Client, forumId: string, tagId: string) {
    return await client.channels.deleteForumTag(forumId, tagId)
}

Fluxer assigns the ID of a new tag, so read it from the returned channel by name. An edit without moderated or an emoji resets them to false or none. Deleting an unknown tag fails, which includes a second delete of the same tag. Fluxer also rejects a change that would leave a channel with ChannelFlags.RequireTag without a tag that is not moderated

Start Posts

Read threads covers posts started with the bot. A webhook that belongs to a forum or media channel can start a post with threadName, as the webhook guide shows