This is the developer contract for the BricksMembers Community module. Community is a custom-table Structure → Container → Item engine with a visual Builder, service-owned writes, access-filtered reads, Bricks rendering adapters, AJAX and REST transports, safe domain events, and rebuildable projections. It is not Member Chat, BRM Groups, or a WordPress post-type implementation.
Start with the Feature Maps
the Community feature map in the plugin repo, entry points, bootstrap, canonical write/read owners, events, integrations, and extension points.the Community read/write architecture map in the plugin repo, allowed mutation and read paths.ADR 0006 Community engine architecture, core custom-table architecture.ADR 0007 Community integration boundaries, cross-module and future-module boundaries.
Ownership and Bootstrap
| Concern | Canonical owner |
|---|---|
| Module registration | CommunityFeatureRegistrar |
| Runtime orchestration | CommunityRuntime |
| Schema lifecycle | CommunitySchemaService and cohesive Community*SchemaDdl definitions |
| Builder graph read | CommunityBuilderReadService::build_graph() |
| Builder transport | CommunityBuilderActions; it validates and delegates |
| Builder configuration transfer | CommunityBuilderTransferService; CommunityBuilderTransferReferenceService describes and resolves site-bound references |
| First-run recipe catalog and commit | CommunityBuilderSetupCatalog and CommunitySetupService |
| Structure writes | CommunityStructureService |
| Section/collection writes | CommunityContainerCollectionService |
| Container/Space writes and membership | CommunityContainerService |
| Item, tree, accepted-child, repost, and quote writes | CommunityItemService |
| Access decisions | CommunityAccessService and CommunityTargetAccessResolver |
| Community attachment associations and subscriptions | CommunityAttachmentService and CommunitySubscriptionService |
| Protected binary lifecycle, upload, access, and delivery | ProtectedMediaObjectService, ProtectedMediaUploadService, ProtectedMediaAccessService, and ProtectedMediaDeliveryService |
| Reaction/bookmark state | InteractionService::set_state(); CommunityReactionService and CommunityBookmarkService are permission/rate-limit compatibility adapters |
| Realtime invalidation and Community change cursors | RealtimeOutboxService, RealtimePublisherService, and CommunityChangeService |
| Reports and lifecycle moderation | CommunityReportService and CommunityModerationService |
| Search | CommunitySearchService |
| Notifications and preferences | CommunityNotificationService, CommunityNotificationFanoutService, CommunityNotificationPreferenceService, and CommunityNotificationDeliveryService |
| Read/unread state | CommunityUserStateService |
| Neutral activity projections | CommunityUserStatsService |
| Personal-data export/erasure | CommunityPersonalDataService through the shared user-data services |
| Bricks context, actions, and conditions | CommunityRenderContext, CommunityBricksIntegration, and Community element classes |
| Bricks Saved Feed choices for the atomic Destination Field | CommunityBricksFeedCatalog expands active backend Feeds while CommunityAccessService authorizes each destination |
| Bricks nestable collections and linked-content context | CommunityBricksCollectionService, CommunityBricksAttachedTargetContextBuilder, and CommunityBricksLinkedPostContext |
| Headless transport | HeadlessRestCommunityController; it is an adapter, not a domain owner |
Internal code must call these owners directly. It must not route through public brm_* wrappers, AJAX handlers, REST callbacks, templates, or Bricks elements.
Canonical Storage
brm_community_items is the canonical authored conversation store. Optional wp_post_id projection metadata can support direct URLs, but WordPress posts are never the source of truth. Community also owns normalized tables for Structures, role/depth rules, container collections, containers, audience grants, target connections, connection rules and indexes, saved Feeds and sources, members, attachments, reactions, bookmarks, subscriptions, terms and relationships, search, reports, moderation actions, notifications, preferences and deliveries, read state, rate limits, diagnostics, and neutral user stats.
Denormalized counts, search rows, ancestor paths, projection metadata, notification deliveries, and user stats are derived data. Only their owning service may update them. Diagnostic and repair paths must remain targeted, dry-run by default, bounded, and compact.
Builder and Admin Contract
- The production tabs are Builder, People, Moderation, Settings, and Health & tools. Overview, Quick Start, Build, and the former standalone Tools surface are legacy/developer-only surfaces when legacy admin rendering is enabled.
- The Builder stages graph operations client-side. Save changes flushes them through
CommunityBuilderActionsto canonical services. - A valid connected-content draft persists across Save/reload before a Space destination is connected. An incomplete Add form stays open with field validation and is not converted into a stored draft.
export_settings,plan_import, andrun_importare capability/nonce-checking AJAX adapters.CommunityBuilderTransferServiceowns the versioned, configuration-only 1 MB package and the additive write. Export includes active Sections/Spaces, behavior Structures, Audience grants, active/draft connection rules, Saved Feeds, portable frontend mappings, and layout; it excludes Items, replies, people, memberships, media, notifications, moderation, analytics, and secrets.- Import never trusts source-site numeric IDs.
CommunityBuilderTransferReferenceServiceresolves roles, Levels, Groups, posts, terms, Content Structures, pages, and templates from stable descriptions, while unresolved references produce omissions and warnings. The reviewed plan is single-use, actor-bound, graph-revision-bound, expires after 600 seconds, and runs additively under the Community Builder transfer lock; it never changes, deletes, or adopts existing objects. reset_space_behavioris deliberately not a staged canvas operation. After confirmation it immediately delegates toCommunityContainerService::reset_builder_space_behavior(), resets the dedicated Structure throughCommunityStructureService, bumps the Builder revision, and reloads the graph. The Space, URL key, grants, writing profile, and moderation policy are preserved.- The publication-moderation UI emits only
standard,first_posts, ormanual_review. A retained legacynonevalue is presented asstandard; permission to moderate remains an independent grant/capability decision. - When
feed_visibility_policy=selected_roles, the Space inspector exposes the Structure’s semantic roles and submitsfeed_visible_role_keys.CommunityStructureServicesanitizes those keys, always retains the depth-zero role, and persists each child role’s canonicalappears_in_feedconfiguration. - The Space inspector’s Frontend display fields submit
frontend_modeplus a page or template ID.CommunityContainerService::save_builder_space()delegates mutually exclusive page/template mappings toCommunityBricksStarterPageService. A template-backed Space resolves through/community-space/{id}/, while{brm_community:container_url}returns the active public page or virtual template route. - The same inspector exposes the complete Item type editor. It submits the existing
depth_rule_*arrays for labels, depth ranges, and role capabilities throughCommunityContainerService::save_builder_space();CommunityStructureService::update_structure()remains the canonical validator and write owner. Generated role keys stay internal and stable after the first save. - Successful graph mutations return
builder_revision. Layout and per-user viewport revisions are separate. - Node keys include
content:,rule:,space:,feed:, and multipartaudience:type:id:key. - First-run Social, Forum, Support, Learning, Custom, and Blank recipes work only against an empty graph and create no pages or Bricks templates.
CommunitySetupService::run_starter_setup()andCommunityBricksStarterPageServiceare legacy compatibility paths, not the current Builder recipe contract.
Bricks Rendering Contract
CommunityRenderContext carries the active Structure, Container, Item, discussion target, access target, and notification target. Community custom-table rows do not depend on global $post. A repeated Items, Replies, or Collection row establishes its own context; nesting a second Context inside that row is normally incorrect.
| Surface | Contract |
|---|---|
brm-community-context | Resolves inherited, specific, or no Space context and gates child rendering. Space Source is inherit, specific, or none only. |
brm-community-link | show_content exposes the authorized linked WordPress post for an active Item or Reply. show_discussion resolves attached-target context for lesson, Content Bank, or manual host posts. |
brm-community-collection | Typed nestable lists for Sections, Spaces, Ancestors, Member Activity, and current-member Interactions through CommunityBricksCollectionService. |
brm-community-composer | AJAX adapter for root creation, child creation, and active/specific Item editing. Root mode uses the current Area Space or one explicit Space. Multi-Space choice is supplied only by an authored brm-community-destination-field. |
brm-community-destination-field | Optional atomic Saved Feed destination selector connected to one Composer Connection. Removing it leaves the Composer’s current Area Space as the destination. |
brm-community-items | Access-filtered Structure Policy, explicit Space, all-accessible, or backend Saved Feed read. |
brm-community-replies | Loads one bounded descendant branch, orders it depth-first, pushes each reply into render context, and repeats one direct replies_reply Block as the atomic reply card. Visual depth is capped without requiring nested Replies elements. |
brm-community-search | Service-backed visible search with optional custom result children. |
brm-community-notifications | Current-user, visibility-filtered notifications and unread state. |
brm-composer-relations | Nestable authorized mention/reference context; its native children repeat with {brm_relation:*} data. |
BRM_Element_Community_Destination_Field calls CommunityBricksFeedCatalog to expand the selected Feed’s Section and Space sources, deduplicate active Spaces, and retain only destinations where CommunityAccessService::can_create_root_item() succeeds. It connects to one Composer through the Area and Composer Connection values. The selected ID is copied into the ordinary root-item payload; CommunityItemService::create_root_item() remains the canonical writer and revalidates the exact destination. The field is therefore a removable UX filter, never an authorization boundary. Without it, the root Composer writes to its current Area Space.
Community custom Bricks query types were removed in 1.4.0. Feeds stay on brm-community-items; reply branches stay on brm-community-replies; directory, ancestry, profile-activity, and liked/bookmarked lists stay on brm-community-collection; linked WordPress content and attached host-post discussions stay on brm-community-link. Mention and reference layouts use the access-filtered nestable brm-composer-relations element. CommunityBricksQueryIntegration was deleted with the query-type surface; do not reintroduce aliases.
The native Button action catalog is: Submit Composer, Reset Composer, Focus Composer, Composer Tool, Open Attachment Picker, Clear Community Search, Join Container, Request Container Access, Leave Container, Mark Item Read, Mark Container Read, Toggle Reaction, Toggle Bookmark, Toggle Item Subscription, Toggle Container Subscription, Report Item, Moderate Item, Accept Child Item, Clear Accepted Child, Repost Item, Submit Quote Post, Mark Notification Read, Open Linked Chat, Create or Open Linked Chat, and Reference Chat Message.
Data-driven Collection, Items, Replies, Search, and Notifications elements use CommunityBricksPreviewData only during Bricks Builder requests when live rows or required context are unavailable and Show Example Content is enabled. The sample row is passed through the normal CommunityRenderContext and native child renderer, but it is not persisted, does not call a write owner, and is never used for frontend output.
BRM Community Link uses a different Builder-only preview path because linked discussions require a real post-to-Space attachment. Its Example Post control passes the selected post ID to CommunityBricksAttachedTargetContextBuilder only during Builder requests, so nested Items, Replies, Composer, and dynamic tags resolve real seeded Community data. If Show linked discussion has no attached Space, Builder rendering keeps the missing-context notice and renders the authored child tree so it remains editable; frontend rendering emits only the notice. Frontend requests always retain their actual current-post context.
BRM Community Area accepts a Builder-only Example Conversation ID for direct-item templates. It resolves the selected published item through CommunityItemService, derives its Space through the normal Area context path, and exposes that real context to nested children. The override is ignored outside Builder requests.
Native Div/Block layout roles include portable Composer Toolbar, Editor, Status, Media, and Actions regions; Community Title plus Community-specific external slots; Context No Access/Error and list state roles; and the explicit repeat roles items_item, replies_reply, collection_item, search_result, notifications_item, and relations_item. A list repeats one direct atomic Block; its nested native children inherit the current row. Two direct Blocks with the same repeat role render the same record twice, so visual variants belong inside the one repeat Block and use Bricks conditions. Boolean tags are data, not labels: render human text such as Solved behind brm_community_is_solved = yes instead of printing {brm_community:is_accepted_child}. Canonical Item cards link both their title and an explicit Open Conversation Text Link to {brm_community:permalink}. CommunityBricksContextCatalog gives the BRM Community Area provider a named Area Name while consumers default to Nearest Area. CommunityRenderContext resolves that target to the closest active Area or repeated Community row before render attributes and signed pagination state are created. Named Areas remain an advanced cross-Area override, not a Feed, template, or persisted backend object. Composer Connection continues to isolate one composer, its slots, fields, and actions.
A Space can use either a dedicated WordPress page or a shared published Bricks Content template. CommunityBricksStarterPageService owns both mutually exclusive mappings. Template-backed Spaces use /community-space/{container_id}/; CommunitySpaceRequestController prepares an empty virtual WordPress document, enforces CommunityAccessService::can_view_container(), selects the mapped Content template through bricks/active_templates, and seeds the current Space and Structure through CommunityRenderContext. The table-backed Space never becomes a WordPress post or custom post type.
The Notifications frontend uses the same page-or-template model without pretending a member notification is a WordPress post. CommunityNotificationsFrontendService owns the mutually exclusive destination. In template mode, CommunityNotificationsRequestController renders the selected Bricks Content template at /community-notifications/ for logged-in users only. BRM Community Notifications resolves rows for the current member; no Space or Item URL context is required. Use {brm_community:notifications_url} for links that must follow either destination mode.
Base and structural styling belongs in Bricks controls, hidden Structural Controls, saved settings, global classes, utility classes, or Template Assistant JSON. Community elements must not enqueue frontend CSS for their default appearance.
Dynamic Data and Conditions
CommunityDynamicTags registers {brm_community:*}. The family includes identity, hierarchy, Structure/Section/Space data, target/access/notification context, content and author fields, role behavior, permalinks, counters, attachments, engagement, access booleans, read state, accepted-child state, relation/source fields, privacy redaction, and neutral member activity projections.
CommunityBricksIntegration registers the BRM: Community condition group. Boolean conditions include context, container access/membership, Item access/actions, feed/context-page behavior, attachments, engagement, accepted-child, repost, quote, solved/open, unread, and privacy-redacted state. Scalar conditions cover Item role/depth, Structure ID, Container ID, and membership status. Conditions are server-side render gates; canonical services still authorize every mutation.
Frontend AJAX Family
CommunityActions registers authenticated actions for create/update Item, reaction, bookmark, subscription, report, lifecycle moderation, accepted-child, attachment, membership, Item/Container read state, notification read state, notification preference save/delete, mention search, and emoji search. brm_community_load_items is also available to logged-out readers with a frontend nonce, signed saved-element token, signed cursor, access recheck, and bounded rate bucket.
All AJAX handlers validate method, nonce, capability/login state, input, and request throttle before delegating. Domain services then apply exact Container/Item access, Structure policy, member restrictions, spam checks, action-specific rate buckets, sanitization, and projection/event side effects. Do not treat an AJAX nonce as authorization.
Headless REST Route Family
The namespace is /wp-json/bricksmembers/v1. The Community family is registered only when both the Headless REST integration and Community module are active.
| Method | Route | Purpose |
|---|---|---|
| GET | /community/structures | Active labels, roles, depth rules, and allowlisted behavior. |
| GET | /community/container-collections | Collections represented by at least one accessible active Space. |
| GET | /community/containers | Accessible active Spaces. |
| GET | /community/containers/{container_id} | One visible Space. |
| GET | /community/containers/{container_id}/items | Visible Space feed with Structure Policy, target scope, and keyset pagination. |
| GET | /community/feed | Current viewer’s cross-Space feed. |
| GET | /community/search | Visible canonical Item search. |
| GET | /community/items/{item_id} | One visible Item. |
| GET | /community/items/{item_id}/context | Selected Item, bounded ancestors, and direct-child window. |
| GET | /community/items/{item_id}/replies | Direct visible replies. |
| GET | /community/items/{item_id}/attachments | Visible normalized attachments. |
| GET | /community/terms and /community/items/{item_id}/terms | Active term discovery and visibility-filtered Item terms. |
| GET | /community/users/{user_id}/activity | Viewer-visible member activity. |
| GET | /community/items/engagement-state, /community/items/read-state | Current-user batched Item state after visibility filtering. |
| GET | /community/containers/subscription-state, /community/containers/read-state | Current-user batched Space state after visibility filtering. |
| GET | /community/notifications | Logged-in current-user notifications only. |
| POST | /community/items | Create a root or child Item. |
| PATCH | /community/items/{item_id} | Update editable content. |
| POST | /community/items/{item_id}/reactions, /bookmark | Toggle reaction or bookmark. |
| POST | /community/subscriptions | Toggle Item or Space subscription. |
| POST | /community/items/{item_id}/reports, /moderation | Report or perform authorized lifecycle moderation. |
| POST | /community/items/{item_id}/accepted-child | Set or clear an accepted direct child. |
| POST | /community/items/{item_id}/attachments | Upload or attach policy-compliant media. |
| POST | /community/containers/{container_id}/membership | Join, request access, or leave. |
| POST | /community/items/{item_id}/read, /community/containers/{container_id}/read, /community/notifications/read | Current-user read state. |
| GET/POST | /community/moderation/reports, /community/moderation/reports/{report_id} | Permission-scoped report queue and resolution. |
| GET | /community/access-debugger, /community/diagnostics | Manager-only access reasons and targeted diagnostics. |
| POST | /community/repairs | Manager-only targeted repair, dry-run by default. |
Public reads still pass the Community read throttle and exact visibility checks. Current-user state requires login. Writes require login, the REST write throttle, canonical permission checks, and action-specific buckets. Manager/debug/repair/report routes have additional capability or manageable-Container gates.
Events and Automations
Canonical services dispatch versioned Community events after successful state changes. The automation trigger catalog is registered by CommunityAutomationTriggerPack.
community_builder_setup_completedcommunity_item_createdcommunity_item_updatedcommunity_item_moderatedcommunity_reaction_toggledcommunity_bookmark_toggledcommunity_subscription_toggledcommunity_accepted_child_updatedcommunity_attachment_addedcommunity_attachment_quarantinedcommunity_chat_room_linkedcommunity_chat_reference_createdcommunity_report_createdcommunity_container_membership_changed
Trigger filters use IDs, Structure/Space scope, Item roles and statuses, reaction type, boolean state, member role/status/source, report reason, or media kind as appropriate. Event and analytics payloads intentionally omit Item bodies, report notes, attachment URLs, filenames, and arbitrary metadata. Consumers must re-read authorized data through a canonical read owner when they need more context.
Notification and Email Flow
CommunityNotificationService records notification events without synchronous broad fanout. CommunityNotificationFanoutService pages recipients in resumable batches and rechecks current visibility and preference before inserting normalized notification rows. CommunityNotificationPreferenceService resolves global → Space → Item preferences. CommunityNotificationDeliveryService schedules delivery records only for an existing materialized notification. Email delivery then passes through the Emails module queue/template boundary. Community events alone must never become a bypass around recipient access or preferences.
Connected Content and Content Bank
A connected discussion keeps three concepts distinct: discussion target, access target, and notification target. current_post, bank_source, bank_proxy, and keyed manual targets are resolved through CommunityTargetResolverService and CommunityContainerTargetService. For shared Content Bank discussions, the source can remain the shared target while the visible proxy remains the access target. Never collapse these fields or trust submitted target metadata without resolving it back to the attached Container.
ComposerRelationContextService may resolve a pending Community Item so the submitting author can finish writing its mention/reference relations before publication. can_write_source() allows that pending source only when the authenticated user is its exact author; other pending-item writes remain denied. Published and hidden Items continue through the canonical edit decision. This exception preserves pending content composition without making pending Items generally visible or writable.
Starter Site 1.1.1 Community Fixtures
AtomikMembers 1.4.3 ships Starter Site pack 1.1.1. StarterSiteCommunityWriteService remains an adapter over canonical Community services: it creates the LMS graph, resolves semantic refs, binds Community Home Items and the optional Destination Field to the Community Activity Saved Feed, keeps the Area/root Composer default on the Community Feed Space, writes ordinary fixture Items through CommunityItemService, seeds the Pro learner’s Like and Bookmark through the canonical InteractionService::set_state() writer, creates one true pending Item by temporarily applying and then restoring the authored moderation policy, and creates one open report through CommunityReportService. Repair is idempotent and records owned object IDs in the installer ledger.
Installer recovery is owned by StarterSiteInstallStateService and StarterSiteInstallService. Legacy state repair strips rejected password-shaped fields while retaining ownership records. Payment snapshots contain only the checkout-preview, gifting, and checkout-link sections the pack changes; Bricks snapshots contain only pack-owned account-page assignments and the query-filter flag. Install, repair, reset, and removal merge those scopes so provider credentials and unrelated Bricks settings are never restored from a broad stale snapshot.
Security and Privacy Rules
- Validate method, nonce/authentication, capability, input shape, and bounded collection sizes at each transport.
- Authorize again in the canonical service against exact Item visibility, not Container visibility alone.
- Use
RichContentSanitizer,CommunitySpamSignalService, mention policy, attachment MIME/size/count/ownership rules, and action rate buckets. - Apply final
CommunityAccessService::can_view_item()filtering after candidate queries and before serialization. - Never expose another member’s notifications, read state, preferences, report notes, or inaccessible source Item.
- Use
CommunityPersonalDataServicefor export and erasure. Privacy redaction keeps identifiers and thread shape but removes authored title/body and marksprivacy_redacted_at. - Render redacted placeholders from
{brm_community:is_privacy_redacted}orbrm_community_is_privacy_redacted; do not infer redaction from an empty body.
Extension Contract for Future Modules
Community is prepared for separate interaction, gamification, achievement, reputation, and leaderboard modules without owning those domains. Extensions should subscribe to safe versioned events, maintain idempotency/event cursors, and store their own ledgers and projections. They may read canonical Items and neutral stats through service owners, but must not add scoring columns to Community tables or make Community wait synchronously for ranking work.
brm_community_user_stats contains rebuildable activity counts and last activity only. It is not canonical activity history and has no score field. A Leaderboards module must own score rules, ledger, cursor, score projection, rank read model, rebuild behavior, retention, and privacy handling. The separate Interactions module adapts Community Items through CommunityItemInteractionTargetAdapter; InteractionService::set_state() is the canonical interaction writer. CommunityReactionService and CommunityBookmarkService are compatibility adapters that add Community permission/rate-limit checks and legacy events before delegating the state mutation.
Diagnostics and Repair
CommunityDiagnosticsService inspects targeted Item counters, reaction counts, accepted-child pointers, search projections, tree metadata, optional CPT projection metadata, Container counters, targets, member counts, user-stat scopes, and bounded orphaned dependent rows. CommunityRepairService owns repairs. Manager routes and the Settings Data & maintenance UI default to dry run, and CommunityDiagnosticsLogService records compact summaries without raw member content.
AI, MCP, and Template Assistant
bricksmembers/community-builder-catalog-getreads recipe and graph setup options.bricksmembers/community-setup-planproduces an empty-graph dry run.bricksmembers/setup-plan-commitcommits the validated setup token.- Later graph editing remains in the Builder and canonical service-backed AJAX path.
- Community & Groups Template Assistant assets are schema-2 Bricks JSON. Assistant metadata selects live Feeds, Sections, Spaces, editor profiles, and pages, then is stripped before paste.
Regression Expectations
New behavior needs a named behavioral regression test at the cheapest suitable layer. Update feature maps when ownership, bootstrap, events, or extension boundaries change. Update the AI/MCP catalogs, Bricks agent reference and skill, public endpoint catalog, REST/AJAX references, database schema documentation, and user/developer posts when their public contract changes. Browser-visible Builder or Bricks changes require browser evidence; storage changes require migration-safety and architecture review.
Related References
modules/communityapi/rest-endpointsapi/ajax-handlerssecurity/public-endpoint-catalogintegrations/bricks-elementsintegrations/bricks-conditionsdynamic-tagsai-agent/bricks-builder-referenceuser-posts/65-community-builder-for-membership-conversations
Protected Media, Realtime, and Member Chat Owners
ProtectedMediaObjectService, ProtectedMediaUploadService, ProtectedMediaAccessService, ProtectedMediaDeliveryService, and ProtectedMediaMaintenanceService own the shared binary lifecycle, serialized upload finalization, live authorization, delivery, abandoned-upload expiry, and quota reconciliation. Community keeps only brm_community_attachments associations. Delivery is bound to a user/session/object/association/fingerprint/intent and reruns CommunityAccessService::can_view_item() for every GET, HEAD, or range request.
RealtimeOutboxService and RealtimePublisherService own content-free invalidations and retries. CommunityChangeService owns monotonic reconciliation cursors. Domain mutations do not depend on successful Pusher/Ably calls, email, automation, broad fanout, scanning, or provider processing.
CommunityChatIntegrationService owns mapping policy but delegates room creation to ChatRoomService. ComposerRelationWriteService stores ID-only source/target relations and ComposerRelationReadService performs fresh source and target authorization on reads. Events community_attachment_quarantined and community_chat_room_linked contain IDs/state only.
Large global feeds join valid brm_community_user_container_access projections and batch-load attachments/references. Broad notifications use CommunityNotificationFanoutService in resumable batches. Future modules consume CommunityProjectionSourceService::CONTRACT_VERSION and list_change_facts_after(); Community does not add score, points, rank, badge, streak, or leaderboard ownership.