---
name: forum-section
description: Add, rename, regroup or retire a section on the GCC Forum or Help Center — the he_type row, the forum_section mapping, categories, read-only archives and the access implications. Use when changing what sections exist or where they appear.
---

# Adding or changing a forum section

A section is an `he_type` row. Where it *appears* is a `forum_section` row. The
two are deliberately separate: `he_type` is shared with the legacy `hef.cfm` and
is the source of truth for access, `forum_section` is presentation only.

## Checklist

1. **Does the `he_type` row exist?** Sections are shared with the legacy board.
   Reuse the id if it does; only create one if the section is genuinely new.
2. **Add the `forum_section` row** — `src`, `type_id`, `category_id`, title,
   glyph, blurb, sortorder, `listed`, `postable`.
3. **Pick or add a `forum_category`.** Ids 1–5 are the Forum, 10–15 the Help
   Center.
4. **Never put an access level in `forum_section`.** It lives on `he_type`.
5. Apply the migration — `forum-migration`.
6. **Restart the app.** `he_type` is cached in application scope by
   `s_loadsystem.cfm`; a new section will not appear until it reloads.
7. Verify — `forum-verify`.

## The two rows

```sql
USE `gcc`;

-- Only if the section is genuinely new. id < 100 for a Help Center ticket
-- section; 100-199 for a Forum board. s_loadsystem.cfm only adds ids < 100 to
-- application.s_hetypelist, which is what fills the legacy dropdowns.
INSERT INTO `he_type` (`id`,`short`,`name`,`detail`,`publicflag`,`access`,`orderby`,`emailflag`)
VALUES (96,'Recruitment','Staff > Recruitment','<u>Staff &gt; Recruitment</u><br><br>…',0,3,13,0)
ON DUPLICATE KEY UPDATE `name`=VALUES(`name`), `detail`=VALUES(`detail`);

-- Where it appears.
INSERT INTO `forum_section`
  (`src`,`type_id`,`category_id`,`title`,`glyph`,`blurb`,`sortorder`,`listed`,`postable`)
VALUES ('he',96,14,'Recruitment','&#9733;','What we are looking for right now.',40,1,1)
ON DUPLICATE KEY UPDATE
  `category_id`=VALUES(`category_id`), `title`=VALUES(`title`), `glyph`=VALUES(`glyph`),
  `blurb`=VALUES(`blurb`), `sortorder`=VALUES(`sortorder`),
  `listed`=VALUES(`listed`), `postable`=VALUES(`postable`);
```

### Why the PK is `(src, type_id)`

The two boards share the `he_type` id space but not its meaning. **Type 1 is
"Question" in the Help Center and the pre-category catch-all in the Forum.**
Different sections, different rows, same id.

## `listed` and `postable`

| | Effect |
|---|---|
| `listed = 0` | Hidden from the index and the sidebar. Threads still readable by direct link. |
| `postable = 0` | Read-only. `canPostIn()` refuses with "This section is an archive." |

`postable = 0` is how **The Vault** works — the older Forum threads,
browsable and searchable but frozen, because new conversation belongs in a live
section.

## Access is on `he_type`, always

| Column | Means |
|---|---|
| `access` | minimum `adminflag` to **browse**. Types 200+ ignore it — they are public, and the number is who may *post*. |
| `publicflag` | `0` = threads are private to their author and staff by default |

**Who may CREATE in a section can be its own capability.** Events (106) browses
at 0 and creates at `post.events` (default 3), because raising `he_type.access`
to keep players out of the composer would also hide the section from them. If a
new section wants that shape, register a capability rather than moving `access`
— see [`forum-acl-section`](../forum-acl-section/SKILL.md).

Changing either is an access change: read
[`forum-access-rule`](../forum-access-rule/SKILL.md) first. In particular, a
high `access` does **not** stop players filing tickets in a Help Center
section — that is deliberate, and how suspension appeals work.

## Glyphs

HTML entities, stored raw and printed raw. `forum_section.glyph` is
`varchar(16)` — wide enough for a five-digit entity like `&#128161;`. It was
`varchar(8)` originally and seeding the Help Center failed with *"Data too long
for column 'glyph'"*; `forum_glyph_widen.sql` exists for installs created before
the fix.

## Retiring a section

**Do not delete rows.** Threads are keyed on `type`; removing the section
orphans them exactly the way `hef` type 1 was orphaned for two decades.

- To hide it: `listed = 0`.
- To freeze it: `postable = 0`.
- To move its threads first: `Mod.move()` per thread, or an `UPDATE hef SET
  type = …` migration — then hide the empty section.

## After you add one

**Check nothing became invisible.** The Vault exists because `f_he.cfm` listed
the Forum with `type>=100 and type<=199` and 1,341 threads sat outside that
range with no listing that could reach them.

```sql
-- Any thread whose type has no forum_section row for its board?
SELECT h.type, COUNT(*) FROM hef h
LEFT JOIN forum_section s ON s.src='hef' AND s.type_id=h.type
WHERE s.type_id IS NULL GROUP BY h.type;
```

Run the same for `he`. A non-empty result is threads nobody can find.

## Verify

```
?p=index                     the section appears in its category
?b=he&p=index                …on the right board
?p=section&s=<id>            it lists, and the count matches the table
?p=compose                   it appears in the dropdown iff canPostIn allows
```

Then as a signed-out viewer and at each staff level — `forum-verify`.
