Posts
Forum and feedback boards with voted posts, roadmap statuses, merged duplicates, replies and moderation, on the messaging core.
The posts block keeps boards of posts per organization: a forum board
for questions and discussion, or a feedback board where members post ideas
and vote on them. Posts take up and down votes through
reactions, replies through
comments on the messaging
core, and reports, hiding and bans through
moderation. Moderators set a roadmap status, pin,
lock and merge duplicates.
better-supabase add posts # adds tenant, access, subjects, messaging, comments, reactions and moderation| Permission | Lets a member | Default roles |
|---|---|---|
posts.read | read boards and posts, and vote | member, admin |
posts.write | write posts and edit or delete their own | member, admin |
posts.moderate | set statuses, pin, lock, merge and edit any post | admin (posts.*) |
posts.manage | create, rename and archive boards | admin (posts.*) |
The example app's post board recipe shows a feedback board's posts by votes, with an upvote toggle and a New post dialog.
Boards and posts
import { createPosts, rpcTransport } from "better-supabase/blocks/posts";
const posts = createPosts({
transport: rpcTransport(supabase),
tenant: organizationId,
});
const ideas = await posts
.saveBoard({ key: "ideas", name: "Ideas", kind: "feedback" })
.orThrow();
const post = await posts
.create(ideas.id, {
title: "Dark mode for reports",
body: "The exported PDFs ignore the theme.",
})
.orThrow();saveBoard creates a board, or with id changes it; the key can't change
once set and archived: true hides it from boards(). create writes a
post that shows at once, or with the moderation: "pre" option waits for a
moderator. A banned member can't post. update(postId, input) changes the
caller's own post while it isn't locked, or any post for a moderator, and
delete(postId) removes a post with its votes. postInputSchema and
postBoardInputSchema validate the inputs.
list({ boardId, status, authorId, sort }) returns the posts the caller
reads. sort is active (pinned first, then the latest activity, the
default), new or top (most votes). Posts merged into others stay out
unless you pass merged: true. Each post carries its votes tally with the
caller's own vote, its replyCount and whether the caller wrote it
(mine).
Votes and replies
const tally = await posts.vote(post.id, 1).orThrow();
// { score: 4, up: 4, down: 0, vote: 1 }vote(postId, value) sets the caller's vote: 1 up, -1 down, 0 clears
it. Replies are comments on the subject post, so the comments block's
client and recipes thread them:
await comments
.create({ type: "post", id: post.id }, { body: "Agreed" })
.orThrow();A reply on a locked, merged or hidden post fails unless a moderator writes it.
Moderate
await posts
.setState(post.id, { status: "planned", pinned: true, locked: false })
.orThrow();
const kept = await posts.merge(duplicate.id, post.id).orThrow();setState sets the status (open, under_review, planned,
in_progress, done or closed), the pin and the lock. merge closes a
duplicate, points it at the post it kept and moves its votes there, unless a
voter already voted on that post; replies stay on the duplicate.
Every post has a moderation item. A report through the moderation block
queues it, and hiding or removing it there sets the post's moderation to
hidden or removed, which keeps it out of members' lists and stops votes
and replies. Under moderation: "pre", a new post and an author's edit
start as pending until a moderator approves them.
Extra columns
options.extraColumns adds columns to the posts table, such as a category or
an effort estimate. create and update write them from fields, and
every post carries them in fields:
blocks: {
posts: { options: { extraColumns: { category: "text", effort: "integer" } } },
},Pass a Standard Schema as fields to createPosts to type them: writes
validate them before the database and reads parse them.
Options
blocks: {
posts: { options: { moderation: "pre" } },
},| Option | Default | What it does |
|---|---|---|
moderation | "post" | post shows posts at once; pre holds new posts and edits for review |
maxBodyLength | 20000 | the longest post body, in characters |
extraColumns | none | extra columns on the posts table |
Events
The block writes post_board.saved, and per post post.created,
post.updated, post.status_changed, post.moderated, post.merged and
post.deleted, to the outbox. See
Events.
| Error code | When |
|---|---|
POST_FORBIDDEN | the caller lacks the permission, is banned or the post is locked |
POST_INVALID | a title, status, vote or body doesn't validate |
POST_NOT_FOUND | there is no such post, or the caller can't read it |
POST_BOARD_NOT_FOUND | there is no such board in the organization |
Last updated on