Posts Module
The Posts module enables social media-style content publishing in your community platform. Members can create posts with rich media, comment on posts, and interact with content through customizable feeds. The module includes built-in moderation tools and flexible visibility controls, including optional ActivityPub federation for connecting with the wider fediverse.
Each post category can define custom fields that determine what information users provide when creating posts. This allows you to create specialized post types like event announcements, music sharing, photo galleries, or any other structured content your community needs.
Quick Start
Section titled “Quick Start”Adding the Posts module
Section titled “Adding the Posts module”If you didn’t include the Posts module during initial project setup, you can add it at any time:
cohera module add postsThis command will:
- Install the Posts module package
- Update your
config/cohera.config.jsto enable the module - Create a default configuration file at
config/modules/posts.config.js - Run database migrations to set up posts and comments storage
Creating your first post
Section titled “Creating your first post”Once the module is installed, posts can be created through your platform’s UI. The Posts module provides:
- Rich text editing - Format post content with markdown or rich text
- Media attachments - Embed images, videos, and other media
- Categories - Organize posts using custom categories
- Comments - Enable discussions with configurable nesting
Accessing posts
Section titled “Accessing posts”Posts are automatically accessible at /posts/{id} where {id} is the auto-generated post identifier. This routing pattern is convention-based and requires no configuration.
Here’s what a post looks like:
Configuration
Section titled “Configuration”The Posts module is configured via config/modules/posts.config.js. Here’s an example configuration:
export default { // Define custom categories for organizing posts categories: [ { id: "announcements", name: "Announcements", description: "Important platform updates and news", }, { id: "general", name: "General Discussion", description: "General community conversations", }, { id: "help", name: "Help & Support", description: "Ask questions and get help from the community", }, ],
// Configure comment nesting depth // 0 = no comments, 1 = comments only (no replies), 2+ = nested replies comments: { maxNestingDepth: 1, },
// Feed display and sorting options feed: { // Default sort order for post feeds // Options: 'newest', 'popular', 'trending' defaultSort: "newest",
// Allow users to change sort order in the UI allowUserSort: true, },
// Moderation settings moderation: { enableReporting: true, roles: [], // Uses default role-based permissions },
// Visibility and federation settings visibility: { defaultLevel: "public", allowUserChoice: true, enableFederation: false, },};Configuration Reference
Section titled “Configuration Reference”categories
Section titled “categories”An array of category objects that define how posts can be organized.
Type: Array<Category>
Category object:
id(string, required) - Unique identifier for the category. This ID is used as the database table name for posts in this category.name(string, required) - Display name shown in the UIdescription(string, optional) - Description of what belongs in this categoryreactions(array, optional) - Array of emoji strings for reaction buttons. Omit or use empty array to disable reactions for this category.fields(array, optional) - Array of field definitions that posts in this category must/can include
Example:
categories: [ { id: "announcements", name: "Announcements", description: "Important platform updates", fields: [ { name: "Title", type: "shortText", required: true, }, { name: "Content", type: "richText", required: true, }, ], },];comments.maxNestingDepth
Section titled “comments.maxNestingDepth”Controls how deeply comments can be nested in reply threads.
Type: number | null
Values:
0- Comments disabled1- Comments enabled, no replies (flat structure)2+- Nested replies up to specified depthnull- Unlimited nesting depth (no limit)
Default: 1
Example:
comments: { maxNestingDepth: 2, // Allow comments with one level of replies}Visual examples of nesting depths:
With maxNestingDepth: 1 (flat comments only):
With maxNestingDepth: 2 (one level of replies):
With maxNestingDepth: 3 (deeper nesting):
feed.defaultSort
Section titled “feed.defaultSort”The default sort order for post feeds.
Type: 'newest' | 'popular' | 'trending'
Default: 'newest'
Options:
'newest'- Most recent posts first'popular'- Posts with most engagement (likes, comments)'trending'- Posts gaining engagement quickly
Example:
feed: { defaultSort: 'popular',}feed.allowUserSort
Section titled “feed.allowUserSort”Whether users can change the feed sort order in the UI.
Type: boolean
Default: true
Example:
feed: { allowUserSort: false, // Lock feed to defaultSort}moderation
Section titled “moderation”Configure moderation features and permissions for managing post content.
Type: object
Properties:
enableReporting(boolean, default: true) - Allow users to report posts for moderator reviewroles(array, optional) - Define custom roles with moderation permissions
Default:
moderation: { enableReporting: true, roles: [], // Uses default role-based permissions}Example with custom moderation roles:
moderation: { enableReporting: true, roles: [ { name: "moderator", permissions: ["remove", "hide", "review_reports"], }, { name: "admin", permissions: ["remove", "hide", "review_reports", "manage_categories"], }, ],}Available permissions:
remove- Permanently delete postshide- Hide posts from public view (soft delete)review_reports- Access and act on user-reported contentmanage_categories- Modify category configurations
Content Reporting:
When enableReporting is true, users can report posts that violate community guidelines. Reported posts appear in the moderation queue for users with the review_reports permission. Moderators can then choose to:
- Dismiss the report (no action)
- Hide the post (temporarily remove from view)
- Remove the post (permanently delete)
Role-based Permissions:
The Posts module integrates with Cohera’s role system. By default, these permissions are checked:
- Post authors can always edit or delete their own posts
- Users with the
moderatorrole can hide and review reports - Users with the
adminrole have full moderation capabilities
You can customize these permissions using the roles configuration option.
visibility
Section titled “visibility”Configure post visibility levels to control who can see posts across your instance and federated network.
Type: object
Properties:
defaultLevel(string, default: ‘public’) - Default visibility level for new postsallowUserChoice(boolean, default: true) - Allow users to choose visibility when creating postsenableFederation(boolean, default: false) - Enable ActivityPub federation for public posts
Default:
visibility: { defaultLevel: 'public', allowUserChoice: true, enableFederation: false,}Visibility Levels:
The Posts module supports three visibility levels that control post access:
-
Public & Federated - Post is visible to everyone and shared across federated instances via ActivityPub
- Visible to all users on your instance
- Shared with other instances in the federation network
- Appears in public feeds across the fediverse
- Requires
enableFederation: truein configuration
-
Public (Instance-only) - Post is visible to everyone on your instance only
- Visible to all users on your instance (logged in or not)
- Not shared with federated instances
- Appears in your instance’s public feeds
- Default when
enableFederation: false
-
Group Private - Post is visible only to members of specific groups
- Only visible to users in designated groups
- Not visible in public feeds
- Not federated
- Requires groups module integration
Example configuration:
visibility: { defaultLevel: 'public', // Start posts as public to instance allowUserChoice: true, // Let users choose visibility enableFederation: true, // Enable cross-instance federation}Federation:
When enableFederation is enabled, posts marked as “Public & Federated” are shared using the ActivityPub protocol. This means:
- Posts appear on other Cohera instances and compatible platforms (Mastodon, Pleroma, etc.)
- Users from other instances can interact with your posts (reactions, comments)
- Your instance must have ActivityPub configured in the main Cohera settings
Group Private Posts:
Group private posts require the Groups module. When creating a post, users can select specific groups that should have access. Only members of those groups will see the post in their feeds or be able to access it directly.
User Choice:
When allowUserChoice is true, users see visibility options when creating posts:
- “Public & Federated” (if federation is enabled)
- “Public (Instance-only)” (always available)
- “Private to groups…” (if groups module is installed)
When allowUserChoice is false, all posts use the defaultLevel visibility.
Custom Fields for Categories
Section titled “Custom Fields for Categories”Categories can define custom fields that determine what information users provide when creating posts. This allows you to create structured post types tailored to your community’s needs.
Field Definition
Section titled “Field Definition”Each field in the fields array is an object with the following properties:
Field object:
name(string, required) - Display label for the field in the UItype(string, required) - The field type (see Field Types below)description(string, optional) - Help text explaining what the field is forrequired(boolean, optional, default: false) - Whether the field must be filledplaceholder(string, optional) - Placeholder text for input fieldsdefaultValue(any, optional) - Default value for the fieldoptions(array, for select/multiSelect) - Array of strings defining available choicestarget(string, for reference/references) - Module and type to reference (format:module/type)
Field Types
Section titled “Field Types”Text Fields:
shortText- Single-line text input (for titles, names, short answers)longText- Multi-line plain text input (for descriptions, longer content). Supports @mentions for referencing users.richText- Rich text editor with markdown/HTML formatting. Supports @mentions for referencing users.
Media Fields:
image- Single image uploadimages- Multiple image uploadsvideo- Video file uploadfile- Generic file upload
Structured Data:
date- Date picker (date only)dateTime- Date and time picker (timestamp)number- Numeric input with validationurl- URL input with link validationselect- Dropdown with single choice from predefined optionsmultiSelect- Multiple choice selection from predefined options
References:
reference- Link to a single item from another module (requirestarget)references- Link to multiple items from another module (requirestarget)
Field Type Examples
Section titled “Field Type Examples”Text fields:
{ name: 'Title', type: 'shortText', required: true, placeholder: 'Enter a title...',}Select field:
{ name: 'Priority', type: 'select', options: ['Low', 'Medium', 'High', 'Urgent'], defaultValue: 'Medium',}Reference field:
{ name: 'Related Event', type: 'reference', target: 'calendar/event', description: 'Link to an event from the calendar',}Multiple references:
{ name: 'Related Posts', type: 'references', target: 'posts/post', description: 'Link to other related posts',}Moderation & Visibility Usage
Section titled “Moderation & Visibility Usage”This section shows practical examples of using moderation and visibility features in your application code.
Creating Posts with Specific Visibility
Section titled “Creating Posts with Specific Visibility”When creating a post programmatically, you can set the visibility level:
import { posts } from "@cohera/posts";
// Create a public post (instance-only)const publicPost = await posts.create({ categoryId: "general", title: "Welcome to our community!", content: "Excited to be here and meet everyone.", visibility: "public",});
// Create a federated post (shared across instances)const federatedPost = await posts.create({ categoryId: "announcements", title: "Big news!", content: "Our community is now part of the fediverse!", visibility: "federated",});
// Create a group-private postconst privatePost = await posts.create({ categoryId: "general", title: "Team discussion", content: "Let's plan our next meetup.", visibility: "private", groups: ["team-leads", "organizers"],});Reporting Posts
Section titled “Reporting Posts”Users can report posts that violate community guidelines:
import { posts } from "@cohera/posts";
// Report a postawait posts.report({ postId: "post-123", reason: "spam", details: "This post is advertising unrelated products",});Moderator Actions
Section titled “Moderator Actions”Users with moderation permissions can perform these actions:
import { posts } from "@cohera/posts";
// Get the moderation queue (requires 'review_reports' permission)const reports = await posts.moderation.getReports({ status: "pending", limit: 20,});
// Review a specific reportconst report = await posts.moderation.getReport("report-456");
// Dismiss a report (no action needed)await posts.moderation.dismissReport("report-456", { reason: "Not a violation",});
// Hide a post (soft delete - requires 'hide' permission)await posts.moderation.hide("post-123", { reason: "Inappropriate content", reportId: "report-456", // Optional: link to report});
// Remove a post permanently (requires 'remove' permission)await posts.moderation.remove("post-123", { reason: "Spam", reportId: "report-456",});
// Restore a hidden postawait posts.moderation.restore("post-123");Checking User Permissions
Section titled “Checking User Permissions”Before showing moderation UI elements, check if the user has permissions:
import { posts } from "@cohera/posts";import { auth } from "@cohera/auth";
const currentUser = await auth.getCurrentUser();
// Check if user can moderate postsconst canModerate = await posts.moderation.canModerate(currentUser.id);
// Check specific permissionsconst permissions = await posts.moderation.getPermissions(currentUser.id);
if (permissions.includes("review_reports")) { // Show moderation queue link}
if (permissions.includes("hide")) { // Show hide post button}
if (permissions.includes("remove")) { // Show remove post button}Filtering Posts by Visibility
Section titled “Filtering Posts by Visibility”When querying posts, the API automatically filters based on the current user’s permissions:
import { posts } from "@cohera/posts";
// Get all posts visible to the current user// This automatically includes:// - All public and federated posts// - Group-private posts where user is a memberconst visiblePosts = await posts.list({ categoryId: "general", limit: 20,});
// As an admin, get all posts regardless of visibilityconst allPosts = await posts.list({ categoryId: "general", includePrivate: true, // Requires admin permission limit: 20,});Examples
Section titled “Examples”Music sharing
Section titled “Music sharing”A community where members share what they’re listening to:
export default { categories: [ { id: "music", name: "Currently Listening", description: "Share what you're listening to right now", reactions: ["🔥", "🎵", "💯", "❤️"], fields: [ { name: "Track Title", type: "shortText", required: true, placeholder: "Song name and artist", }, { name: "Link", type: "url", placeholder: "Spotify, Apple Music, YouTube, etc.", }, { name: "Thoughts", type: "longText", placeholder: "What do you think about this track?", }, { name: "Genre", type: "select", options: [ "Rock", "Pop", "Hip Hop", "Electronic", "Jazz", "Classical", "Other", ], }, ], }, ], comments: { maxNestingDepth: 1, }, feed: { defaultSort: "newest", allowUserSort: true, },};Community discussion:
Event announcements with calendar integration
Section titled “Event announcements with calendar integration”Configuration that links posts to events from the calendar module:
export default { categories: [ { id: "event-announcements", name: "Event Announcements", description: "Announce and discuss upcoming community events", reactions: ["🎉", "👀", "❤️", "🔥"], fields: [ { name: "Announcement", type: "richText", required: true, placeholder: "Tell the community about this event...", }, { name: "Related Event", type: "reference", target: "calendar/event", required: true, description: "Link to the event in the calendar", }, { name: "Cover Image", type: "image", }, { name: "RSVP Required", type: "select", options: ["Yes", "No", "Optional"], defaultValue: "Optional", }, ], }, { id: "general", name: "General Discussion", description: "Everything else", fields: [ { name: "Content", type: "richText", required: true, }, ], }, ], comments: { maxNestingDepth: 2, }, feed: { defaultSort: "newest", allowUserSort: true, },};Community discussion:
Photo sharing platform
Section titled “Photo sharing platform”Configuration for a community focused on photo sharing:
export default { categories: [ { id: "photos", name: "Photo Posts", description: "Share your photography with the community", reactions: ["❤️", "📸", "✨", "🔥"], fields: [ { name: "Title", type: "shortText", required: true, placeholder: "Give your photo a title", }, { name: "Photos", type: "images", required: true, }, { name: "Caption", type: "longText", placeholder: "Tell us about these photos...", }, { name: "Category", type: "select", options: [ "Nature", "Architecture", "People", "Street", "Events", "Other", ], required: true, }, { name: "Camera/Phone", type: "shortText", placeholder: "What did you shoot with?", }, { name: "Location", type: "shortText", placeholder: "Where was this taken?", }, ], }, ], comments: { maxNestingDepth: 1, // Flat comments like social media }, feed: { defaultSort: "popular", allowUserSort: true, },};Multi-purpose community forum
Section titled “Multi-purpose community forum”A versatile setup with different post types for different needs:
export default { categories: [ { id: "announcements", name: "Announcements", description: "Official announcements from the team", reactions: ["📢", "👍", "❤️", "🎉"], fields: [ { name: "Title", type: "shortText", required: true, }, { name: "Content", type: "richText", required: true, }, { name: "Priority", type: "select", options: ["Low", "Medium", "High", "Urgent"], defaultValue: "Medium", }, ], }, { id: "discussions", name: "General Discussion", description: "Community conversations", reactions: ["👍", "💡", "❤️", "🤔"], fields: [ { name: "Topic", type: "shortText", required: true, }, { name: "Content", type: "richText", required: true, }, { name: "Tags", type: "multiSelect", options: ["Question", "Discussion", "Feedback", "Idea", "Other"], }, ], }, { id: "showcase", name: "Showcase", description: "Share your projects and creations", reactions: ["🚀", "🔥", "👏", "💯"], fields: [ { name: "Project Name", type: "shortText", required: true, }, { name: "Description", type: "richText", required: true, }, { name: "Screenshots", type: "images", }, { name: "Project URL", type: "url", placeholder: "Link to your project", }, { name: "Tech Stack", type: "multiSelect", options: [ "JavaScript", "TypeScript", "Python", "Go", "Rust", "Other", ], }, ], }, ], comments: { maxNestingDepth: 3, // Allow deeper discussions }, feed: { defaultSort: "trending", allowUserSort: true, },};