experience_id scopes forum content, so each forum tile in a community sidebar is a separate experience.
Initialize the SDK
import { WhopClient } from "@whop/sdk";
const client = new WhopClient({
token: process.env.WHOP_API_KEY,
});
import os
from whop_sdk import Whop
client = Whop(
token=os.environ["WHOP_API_KEY"],
)
use whop_sdk::prelude::*;
let config = ClientConfig {
token: Some(std::env::var("WHOP_API_KEY").unwrap()),
..Default::default()
};
let client = Whop::new(config).expect("Failed to build client");
import (
"os"
"github.com/whopio/whopsdk-go/client"
"github.com/whopio/whopsdk-go/option"
)
client := client.NewWhop(option.WithToken(os.Getenv("WHOP_API_KEY")))
Create a post
Posts support Markdown. Optional fields let you pin the post, paywall it, or mark it as a mention.const post = await client.forumPosts.create({
experience_id: "exp_xxxxxxxxxxxxx",
content: "This is the post body in **Markdown**.",
title: "Optional title for paywalled posts",
pinned: false,
is_mention: false,
paywall_amount: 0, // cents; 500 = $5.00
});
post = client.forum_posts.create(
experience_id="exp_xxxxxxxxxxxxx",
content="This is the post body in **Markdown**.",
title="Optional title for paywalled posts",
pinned=False,
is_mention=False,
paywall_amount=0,
)
let post = client
.forum_posts
.create(
&CreateForumPostsRequest {
experience_id: "exp_xxxxxxxxxxxxx".to_string(),
content: Some("This is the post body in **Markdown**.".to_string()),
title: Some("Optional title for paywalled posts".to_string()),
pinned: Some(false),
is_mention: Some(false),
paywall_amount: Some(0.0), // cents; 500 = $5.00
..Default::default()
},
None,
)
.await?;
post, err := client.ForumPosts.Create(context.TODO(), &whopsdk.CreateForumPostsRequest{
ExperienceID: "exp_xxxxxxxxxxxxx",
Content: whopsdk.String("This is the post body in **Markdown**."),
Title: whopsdk.String("Optional title for paywalled posts"),
Pinned: whopsdk.Bool(false),
IsMention: whopsdk.Bool(false),
PaywallAmount: whopsdk.Float64(0), // cents; 500 = $5.00
})
if err != nil {
log.Fatal(err)
}
_ = post
Read posts
List operations paginate automatically.const posts = await client.forumPosts.list({
experience_id: "exp_xxxxxxxxxxxxx",
first: 10,
});
for await (const item of posts) {
console.log(item);
}
const post = await client.forumPosts.retrieve({ id: "post_xxxxxxxxxxxxx" });
for item in client.forum_posts.list(
experience_id="exp_xxxxxxxxxxxxx",
first=10,
):
print(item)
post = client.forum_posts.retrieve("post_xxxxxxxxxxxxx")
let posts = client
.forum_posts
.list(
&ForumPostsListQueryRequest {
experience_id: "exp_xxxxxxxxxxxxx".to_string(),
first: Some(10),
..Default::default()
},
None,
)
.await?;
for item in posts.data {
println!("{item:?}");
}
let post = client
.forum_posts
.retrieve(&"post_xxxxxxxxxxxxx".to_string(), None)
.await?;
page, err := client.ForumPosts.List(context.TODO(), &whopsdk.ListForumPostsRequest{
ExperienceID: "exp_xxxxxxxxxxxxx",
First: whopsdk.Int(10),
})
if err != nil {
log.Fatal(err)
}
iter := page.Iterator()
for iter.Next(context.TODO()) {
fmt.Println(iter.Current())
}
if err := iter.Err(); err != nil {
log.Fatal(err)
}
post, err := client.ForumPosts.Retrieve(context.TODO(), &whopsdk.RetrieveForumPostsRequest{
ID: "post_xxxxxxxxxxxxx",
})
if err != nil {
log.Fatal(err)
}
_ = post
Comment on a post
Comments are posts with aparent_id. Fetch them by passing the parent’s ID to list.
const comment = await client.forumPosts.create({
experience_id: "exp_xxxxxxxxxxxxx",
content: "Great post!",
parent_id: "post_xxxxxxxxxxxxx",
});
const comments = await client.forumPosts.list({
experience_id: "exp_xxxxxxxxxxxxx",
parent_id: "post_xxxxxxxxxxxxx",
first: 10,
});
for await (const item of comments) {
console.log(item);
}
comment = client.forum_posts.create(
experience_id="exp_xxxxxxxxxxxxx",
content="Great post!",
parent_id="post_xxxxxxxxxxxxx",
)
for item in client.forum_posts.list(
experience_id="exp_xxxxxxxxxxxxx",
parent_id="post_xxxxxxxxxxxxx",
first=10,
):
print(item)
let comment = client
.forum_posts
.create(
&CreateForumPostsRequest {
experience_id: "exp_xxxxxxxxxxxxx".to_string(),
content: Some("Great post!".to_string()),
parent_id: Some("post_xxxxxxxxxxxxx".to_string()),
..Default::default()
},
None,
)
.await?;
let comments = client
.forum_posts
.list(
&ForumPostsListQueryRequest {
experience_id: "exp_xxxxxxxxxxxxx".to_string(),
parent_id: Some("post_xxxxxxxxxxxxx".to_string()),
first: Some(10),
..Default::default()
},
None,
)
.await?;
for item in comments.data {
println!("{item:?}");
}
comment, err := client.ForumPosts.Create(context.TODO(), &whopsdk.CreateForumPostsRequest{
ExperienceID: "exp_xxxxxxxxxxxxx",
Content: whopsdk.String("Great post!"),
ParentID: whopsdk.String("post_xxxxxxxxxxxxx"),
})
if err != nil {
log.Fatal(err)
}
_ = comment
page, err := client.ForumPosts.List(context.TODO(), &whopsdk.ListForumPostsRequest{
ExperienceID: "exp_xxxxxxxxxxxxx",
ParentID: whopsdk.String("post_xxxxxxxxxxxxx"),
First: whopsdk.Int(10),
})
if err != nil {
log.Fatal(err)
}
iter := page.Iterator()
for iter.Next(context.TODO()) {
fmt.Println(iter.Current())
}
if err := iter.Err(); err != nil {
log.Fatal(err)
}
Like a post
Forum reactions are always
:heart:. Chat messages accept any emoji, but forum reactions ignore other values.await client.reactions.create({
resource_id: "post_xxxxxxxxxxxxx",
emoji: ":heart:",
});
client.reactions.create(
resource_id="post_xxxxxxxxxxxxx",
emoji=":heart:",
)
client
.reactions
.create(
&CreateReactionsRequest {
resource_id: "post_xxxxxxxxxxxxx".to_string(),
emoji: Some(":heart:".to_string()),
..Default::default()
},
None,
)
.await?;
_, err := client.Reactions.Create(context.TODO(), &whopsdk.CreateReactionsRequest{
ResourceID: "post_xxxxxxxxxxxxx",
Emoji: whopsdk.String(":heart:"),
})
if err != nil {
log.Fatal(err)
}
Advanced features
Pinned posts
Pinned posts appear at the top of the feed. Usepinned: true when creating a post.
await client.forumPosts.create({
experience_id: "exp_xxxxxxxxxxxxx",
content: "Important announcement!",
pinned: true,
});
client.forum_posts.create(
experience_id="exp_xxxxxxxxxxxxx",
content="Important announcement!",
pinned=True,
)
client
.forum_posts
.create(
&CreateForumPostsRequest {
experience_id: "exp_xxxxxxxxxxxxx".to_string(),
content: Some("Important announcement!".to_string()),
pinned: Some(true),
..Default::default()
},
None,
)
.await?;
_, err := client.ForumPosts.Create(context.TODO(), &whopsdk.CreateForumPostsRequest{
ExperienceID: "exp_xxxxxxxxxxxxx",
Content: whopsdk.String("Important announcement!"),
Pinned: whopsdk.Bool(true),
})
if err != nil {
log.Fatal(err)
}
Mention users
Use<@username> inline. Mentioned users get notified.
await client.forumPosts.create({
experience_id: "exp_xxxxxxxxxxxxx",
content: "Hey <@username> check this out!",
});
client.forum_posts.create(
experience_id="exp_xxxxxxxxxxxxx",
content="Hey <@username> check this out!",
)
client
.forum_posts
.create(
&CreateForumPostsRequest {
experience_id: "exp_xxxxxxxxxxxxx".to_string(),
content: Some("Hey <@username> check this out!".to_string()),
..Default::default()
},
None,
)
.await?;
_, err := client.ForumPosts.Create(context.TODO(), &whopsdk.CreateForumPostsRequest{
ExperienceID: "exp_xxxxxxxxxxxxx",
Content: whopsdk.String("Hey <@username> check this out!"),
})
if err != nil {
log.Fatal(err)
}
Paywalled posts
paywall_amount is in cents. Readers must pay to see the full content.
await client.forumPosts.create({
experience_id: "exp_xxxxxxxxxxxxx",
title: "Premium post",
content: "Exclusive content here",
paywall_amount: 500, // $5.00
});
client.forum_posts.create(
experience_id="exp_xxxxxxxxxxxxx",
title="Premium post",
content="Exclusive content here",
paywall_amount=500,
)
client
.forum_posts
.create(
&CreateForumPostsRequest {
experience_id: "exp_xxxxxxxxxxxxx".to_string(),
title: Some("Premium post".to_string()),
content: Some("Exclusive content here".to_string()),
paywall_amount: Some(500.0), // $5.00
..Default::default()
},
None,
)
.await?;
_, err := client.ForumPosts.Create(context.TODO(), &whopsdk.CreateForumPostsRequest{
ExperienceID: "exp_xxxxxxxxxxxxx",
Title: whopsdk.String("Premium post"),
Content: whopsdk.String("Exclusive content here"),
PaywallAmount: whopsdk.Float64(500), // $5.00
})
if err != nil {
log.Fatal(err)
}
Pagination with cursors
Beyond automatic pagination, you can step through pages manually using theend_cursor on the response’s page_info.
const firstPage = await client.forumPosts.list({
experience_id: "exp_xxxxxxxxxxxxx",
first: 10,
});
const next = await client.forumPosts.list({
experience_id: "exp_xxxxxxxxxxxxx",
first: 10,
after: firstPage.response.page_info.end_cursor ?? undefined,
});
first = client.forum_posts.list(
experience_id="exp_xxxxxxxxxxxxx",
first=10,
)
next_page = client.forum_posts.list(
experience_id="exp_xxxxxxxxxxxxx",
first=10,
after=first.response.page_info.end_cursor,
)
let first_page = client
.forum_posts
.list(
&ForumPostsListQueryRequest {
experience_id: "exp_xxxxxxxxxxxxx".to_string(),
first: Some(10),
..Default::default()
},
None,
)
.await?;
let next = client
.forum_posts
.list(
&ForumPostsListQueryRequest {
experience_id: "exp_xxxxxxxxxxxxx".to_string(),
first: Some(10),
after: first_page.page_info.end_cursor.clone(),
..Default::default()
},
None,
)
.await?;
for item in next.data {
println!("{item:?}");
}
firstPage, err := client.ForumPosts.List(context.TODO(), &whopsdk.ListForumPostsRequest{
ExperienceID: "exp_xxxxxxxxxxxxx",
First: whopsdk.Int(10),
})
if err != nil {
log.Fatal(err)
}
// Step to the next page manually
nextPage, err := firstPage.GetNextPage(context.TODO())
if err != nil {
log.Fatal(err)
}
_ = nextPage
Required permissions
Add these to your app’s permission list from the Permissions guide before publishing.| Permission | Needed for |
|---|---|
forum:read | Reading posts and comments |
forum:post:create | Creating posts and comments |
chat:read | Creating and reading reactions |
Post object shape
Post object shape
{
id: string;
comment_count: number;
content: string | null;
is_edited: boolean;
is_pinned: boolean;
is_poster_admin: boolean;
like_count: number | null;
parent_id: string | null; // null = top-level, set = comment
title: string | null;
view_count: number | null;
user: { id: string; name: string | null; username: string };
}
Next steps
Build a chatbot
Pair forum posts with live chat messages in the same community.
Listen to events with webhooks
React to forum posts and comments in realtime on your server.
Send notifications
Push users back to your forum when there’s something new.
Upload files
Attach images and videos to posts.

