Files
modrinth/packages/moderation
755825b09a project disclosures frontend (#6955)
* begin project disclosures

* new project settings header

* begin disclosure settings, edit content rules

* togglecards

* update phrasing of the rules, prepr

* more disclosure settings structuring

* add functionality toggle

* implement functionality with staging api

* improve project settings head titles

* feat(labrinth): project disclosures model

* feat(labrinth): project disclosures database model

* feat(labrinth): project disclosures get endpoint

* feat(labrinth): censor user ids if set by moderator

* feat(labrinth): wrap disclosures in struct

* feat(labrinth): edit project disclosures endpoint

* style(labrinth): cargo fmt

* style(labrinth): fix typo

* feat(labrinth): add fields for ai content disclosure

* fix(labrinth): field typo

* chore(labrinth): update query cache

* feat(labrinth): index disclosures in search

* refactor(labrinth): use enum instead of bools

* feat(labrinth): trigger incremental index on disclosure change

* feat(labrinth): change archived status to disclosure

* feat(labrinth): use disclosure for archival status

* fix(labrinth): error type for deserialization

* fix(labrinth): migration timestamp

* fix(labrinth): add disclosures to elasticsearch schema

* disclosures in search

* update photosensitivity warning copy

* update archiving, move general project settings to v3 (start redesign there), use project id lookups for stable cache keys, advanced in server search

* blue archive banner

* display AI use types

* improve labels

* add modrinth to link

* add placeholder missing disclosure report type

* add AI metadata checking to block image uploads with notice

* update copy

* update section 4 of rules

* Update rule 6 layout

* fix(labrinth): don't remove archival disclosure when changing status via v3 api

* feat(labrinth): derive str for ai usages and telemtry consent

* feat(labrinth): include ai usages and telemetry consent in disclosure types

* Update moderation checklist (#7056)

* fix: missing delphi severity (#7054)

* Utils nav, new disclosures stage, ai button in rules

* add prefix to collect

* Finish up new disclosures stage

* update r4 messages

* new r4 msg, update showcase clarity message

* fix: version upload failing to detect mrpack loader (#7063)

* fix: mrpack exporting with zip64 (#7064)

* fix: action bar max width (#7048)

* fix: action bar max width

* fix: width

* changelog

* Rule placeholders with subsections & anchors, update messages.

* update new messages to account for new rule and placeholder layout

* Add nag for content disclosures

---------

Co-authored-by: ThatGravyBoat <gravy@thatgravyboat.tech>
Co-authored-by: chyzman <chyzalt@gmail.com>
Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>

* prepr

* refactor(labrinth): move disclosure parsing to document creation

* fix(labrinth): prevent removing moderator added archival disclosures

* fix(labrinth): dedupe ai usages

* fix(labrinth): auth logic on archived disclosure removal

* feat(labrinth): add interactions field

* style(labrinth): cargo fmt

* feat(labrinth): granular disclosure locking

* add 5.8, move 5.9, update messages and placeholders accordingly.

* move disclosures stage earlier

* include consent model info in telemetry disclosure msgs

* prepr + update date

* typo

* qa pass

* search suboptions

* add empty state

* checkboxes in column

* persistent advanced filters

* blog draft

* prepr

* support soft deletion

* reload instead of optimistically updating

* update blog

* TOS update + minor verbiage adjustment

* 45 day grace

---------

Co-authored-by: sychic <47618543+Sychic@users.noreply.github.com>
Co-authored-by: coolbot <76798835+coolbot100s@users.noreply.github.com>
Co-authored-by: ThatGravyBoat <gravy@thatgravyboat.tech>
Co-authored-by: chyzman <chyzalt@gmail.com>
Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>
2026-08-13 09:01:18 -07:00
..
2026-08-13 09:01:18 -07:00
2025-07-11 16:09:04 +00:00
2026-05-19 18:11:48 +00:00

@modrinth/moderation

This package contains both the moderation checklist system used by moderators for reviewing projects on Modrinth, and the publishing checklist (nag system) that provides automated feedback to project authors during the submission process.

Structure

The package is organized as follows:

/packages/moderation/
├── data/
│   ├── checklist.ts        # Main moderation checklist definition - imports and exports all stages
│   ├── messages/           # Markdown files containing message templates for moderation
│   │   ├── title/          # Messages for the title stage
│   │   ├── description/    # Messages for the description stage
│   │   └── ...             # One directory per stage
│   ├── stages/             # Moderation stage definition files
│   │   ├── title.ts        # Title stage definition
│   │   ├── description.ts  # Description stage definition
│   │   └── ...             # One file per stage
│   └── nags/               # Publishing checklist (nag system) files
│       ├── core.ts         # Core nags (required fields, basic validation)
│       └── ...
└── types/                  # Type definitions
    ├── actions.ts          # Action-related types (moderation)
    ├── messages.ts         # Message-related types (moderation)
    ├── stage.ts            # Stage-related types (moderation)
    └── nags.ts             # Nag-related types (publishing checklist)

Moderation Checklist System

The moderation checklist provides a structured and transparent way to define moderation stages, actions, and messages that are displayed to moderators during the review process.

Stages

A stage represents a discrete step in the moderation process, like checking a project's title, description, or links. Each stage has:

  • A title displayed to moderators
  • A link to guidance documentation
  • An optional navigation path to direct moderators to the relevant part of the project page
  • A list of actions that moderators can take

Stages are defined in individual files in the data/stages directory and are assembled into the complete checklist in data/checklist.ts.

Actions

Actions represent decisions moderators can make for each stage. They can be buttons, dropdowns, toggles, etc. Actions can have:

  • Labels displayed to the moderator
  • Messages that are included in the final moderation decision
  • Suggested moderation status and severity
  • Optional text inputs for additional information
  • Conditional behavior based on other selected actions

Each action requires a unique id field that is used for conditional logic and action relationships. The suggestedStatus and severity fields help determine the overall moderation outcome.

Messages

Messages are the actual text that will be included in communications to project authors. To promote maintainability and reuse, messages are stored as Markdown files in the data/messages directory, organized by stage.

Variable replacement

You can use variables in your messages that will be replaced with user input:

  1. Define a variable in the relevantExtraInput array of an action:
relevantExtraInput: [
  {
    label: 'Explanation for the user',
    variable: 'MESSAGE',
    required: true,
  },
],
  1. Use the variable in your message with %VARIABLE% syntax:
# Your Message Title

Here is some explanation about the issue.

%MESSAGE%

More text after the variable.

The %MESSAGE% placeholder will be replaced with the text entered by the moderator.

Conditional logic

The moderation system supports conditional behavior that changes based on the selection of other actions.

Conditional messages

You can define different messages for an action based on other selected actions:

{
  id: 'my_action',
  type: 'button',
  label: 'My Action',
  weight: 100,
  message: async () => (await import('../messages/default-message.md?raw')).default,
  conditionalMessages: [
    {
      conditions: {
        requiredActions: ['other_action_id'],
        excludedActions: ['another_action_id']
      },
      message: async () => (await import('../messages/conditional-message.md?raw')).default,
    }
  ]
}

Enabling and disabling actions

Actions can enable or disable other actions when selected:

{
  id: 'parent_action',
  type: 'button',
  label: 'Parent Action',
  // This will show these actions when parent_action is selected
  enablesActions: [
    {
      id: 'child_action',
      type: 'button',
      label: 'Child Action',
      // ...other properties
    }
  ],
  // This will hide actions with these IDs when parent_action is selected
  disablesActions: ['incompatible_action_id']
}

Conditional text inputs

Text inputs can be conditionally shown based on selected actions:

relevantExtraInput: [
	{
		label: 'Additional Information',
		variable: 'INFO',
		showWhen: {
			requiredActions: ['specific_action_id'],
			excludedActions: ['incompatible_action_id'],
		},
	},
]

Publishing Checklist (Nag System)

The nag system provides automated feedback to project authors during the submission process, helping them improve their projects before they reach moderation. It analyzes project data and provides suggestions, warnings, and requirements.

Nags

A nag represents a specific issue or suggestion for improvement. Each nag has:

  • A unique id for identification
  • A title and description displayed to the user
  • A status indicating severity: 'required', 'warning', or 'suggestion'
  • A shouldShow function that determines when the nag should be displayed
  • An optional link to help users address the issue

Internationalization

Use vintl's defineMessage syntax.

If you want to use context in the messages, you can do so like this:

description: (context: NagContext) => {
  const { formatMessage } = useVIntl()

  return formatMessage(defineMessage(...), {
    length: context.project.body?.length || 0,
    minChars: MIN_DESCRIPTION_CHARS,
  })
}

Nag Context

The NagContext type provides access to:

  • project: Current project data
  • versions: Project versions
  • tags: Frontend "tags" (generated state)
  • currentRoute: Current page route
  • and other data...

Adding New Nags

To add a new nag:

  1. Add the nag definition to the appropriate category file (or make a new category file and add it to data/nags.ts)
  2. Add corresponding i18n messages to the .i18n.ts file
  3. Implement the shouldShow logic based on project state
  4. Add appropriate links to help users resolve the issue
  5. Run pnpm run fix to fix lint issues & generate the root locale index.json file.

Example:

// In description.ts
{
  id: 'new-nag',
  title: messages.newNagTitle,
  description: messages.newNagDescription,
  status: 'warning',
  shouldShow: (context: NagContext) => {
    // Your validation logic here
    return someCondition
  },
  link: {
    path: 'settings/description',
    title: messages.editDescriptionTitle,
    shouldShow: (context: NagContext) => context.currentRoute !== 'type-project-settings-description',
  },
}
// In description.i18n.ts
newNagTitle: {
  id: 'nags.new-nag.title',
  defaultMessage: 'New Nag Title',
},
newNagDescription: {
  id: 'nags.new-nag.description',
  defaultMessage: 'Description of the new nag issue.',