Reference for the tables that back BuddyNext’s moderation, trust, and audit subsystem: bn_reports, bn_mod_log, bn_user_strikes, bn_user_suspensions, bn_appeals, bn_invites, bn_activity_log, and the safeguard counter bn_rate_limits. All eight are created by BuddyNext\Core\Installer via dbDelta() and live in the site table prefix (shown below as wp_). This page is for developers reading, extending, or writing against these tables directly.
Note: Every table in this page was verified against includes/Core/Installer.php. All seven exist. bn_invites is documented here because it carries a per-row trust token and shares the audit/lifecycle shape of the moderation tables, even though it lives under the installer’s “Onboarding + Invites” section.
The moderation surface is split into three concerns:
Intake - members file reports into bn_reports. One reporter can file at most one report per object.
Action + audit - moderators act, and every action is recorded. bn_mod_log is the append-only audit trail; bn_user_strikes and bn_user_suspensions are the durable state of trust actions against a user; bn_activity_log is the broader (non-moderation-specific) action log.
Recourse - a suspended or struck user can file an appeal into bn_appeals, reviewed by a moderator.
Two structural rules shaped the schema:
Reversibility over deletion. Strikes, suspensions, and appeals carry reversal / lift / review columns (is_reversed, lifted_at, reviewed_by) rather than being hard-deleted, so trust history and audit are preserved.
Space-scoped where relevant.bn_reports and bn_mod_log carry a nullable space_id so the same tables serve both site-level moderation and per-space moderation.
Member-filed reports against a piece of content or a user. The queue read path is by status and recency; the per-object read path checks whether a given object already has reports.
Column
Type
Notes
id
BIGINT UNSIGNED AUTO_INCREMENT
Primary key.
reporter_id
BIGINT UNSIGNED NOT NULL
The user who filed the report.
object_type
VARCHAR(32) NOT NULL
The kind of object reported, e.g. post, comment, user.
object_id
BIGINT UNSIGNED NOT NULL
The id of the reported object.
reason
ENUM('spam','harassment','misinformation','inappropriate','fake','impersonation','other') NOT NULL DEFAULT 'other'
The selected report reason.
notes
TEXT NULL
Optional free-text detail from the reporter.
status
ENUM('pending','dismissed','escalated','resolved') NOT NULL DEFAULT 'pending'
Queue state.
resolved_by
BIGINT UNSIGNED NULL
Moderator who closed the report.
resolved_at
DATETIME NULL
When it was closed.
created_at
DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
When it was filed; the queue sort key.
space_id
BIGINT UNSIGNED NULL
The space the report belongs to, if it is space-scoped. Null for site-level reports.
Durable record of account suspensions. A suspension is active while it is not yet lifted and not yet past its expiry; it is lifted (not deleted) when a moderator ends it early.
Column
Type
Notes
id
BIGINT UNSIGNED AUTO_INCREMENT
Primary key.
user_id
BIGINT UNSIGNED NOT NULL
The suspended user.
suspended_by
BIGINT UNSIGNED NOT NULL
The moderator who imposed the suspension.
reason
TEXT NULL
Why the user was suspended.
duration_days
INT UNSIGNED NULL
Length in days. Null implies an open-ended suspension (no automatic expiry).
hide_posts
TINYINT(1) NOT NULL DEFAULT 0
Whether the user’s content is hidden for the duration.
expires_at
DATETIME NULL
When the suspension auto-expires. Null for open-ended.
lifted_at
DATETIME NULL
When a moderator lifted it early. Null means it was never manually lifted.
A generic, key-based counter that backs the safeguard rate limits (post and comment throttles, sign-ups per hour, duplicate-post window). Each row is one bucket, keyed by an opaque rl_key the caller composes (for example an action name plus the actor id or IP), holding a hit count and an expiry after which the bucket is considered empty. There are no foreign keys — it is a self-expiring tally, not audit state.
Column
Type
Notes
rl_key
VARCHAR(191)
Primary key. The caller-composed bucket identifier.
hits
INT UNSIGNED
Count of hits recorded in the current window. Default 0.
expires_at
DATETIME
When the bucket resets. Indexed (KEY expires_at) so expired rows can be swept.
one_per_reporter is enforced at the DB. A second report from the same reporter on the same object is rejected by the unique key, not by application logic. Handle the duplicate-insert case rather than pre-checking.
Active state is computed, not stored. A suspension is “active” when lifted_at IS NULL AND (expires_at IS NULL OR expires_at > now); that is what active_check indexes. There is no single boolean column for it. The same reversibility pattern applies to strikes via is_reversed.
bn_mod_log and bn_activity_log are append-only. Treat them as audit trails: insert, never update. bn_mod_log is moderation-specific (carries actor_id / target_user_id / space_id); bn_activity_log is the broader per-user action log.
Site vs space scope is the nullable space_id.bn_reports and bn_mod_log both serve site-level and per-space moderation off the same table; a null space_id is the site-level case.