Replyke - APIs for user content, social graphs, and moderation tools | Product Hunt

Everything you need to build in-app social features

Replyke provides the full social layer your app is missing - comments, feeds, notifications, profiles, and more - all open-source and ready to integrate.

Tell me what's your product, and I'll show you how Replyke can boost it!

Click to start asking

Flexible Architecture

Build From Scratch or Integrate Seamlessly

Whether you're starting a new project or enhancing an existing application, Replyke adapts to your needs. Use it as a complete social infrastructure or add individual features—without vendor lock-in, migrations, or architectural rewrites.

Flexible Foundation

Start with what you need, scale to what you want. Build a complete social platform or add a single feature—Replyke grows with your vision.

Zero Migration Required

Existing app? Keep your data models, auth systems, and architecture intact. New project? Get a production-ready foundation without complex setup.

Progressive Integration

Choose your level of adoption. Use Replyke as your entire backend, just the social features, or anywhere in between—without all-or-nothing commitments.

Complete Control

Export your data anytime. Remove Replyke later if needed—your project stays functional and fully yours. No lock-in, ever.

Works Your Way

Available for React, React Native, vanilla JavaScript, and Node.js. Use our packages for seamless integration or connect via our REST API for maximum flexibility.

Production-Ready, Risk-Free

Test features without permanent commitments. For new projects, get battle-tested infrastructure. For existing apps, enhance without disrupting what works.

flexiblecomplete or modularzero migrationfull control

Build a complete social platform from the ground up, or add powerful features to your existing application—all while maintaining complete control over your data, architecture, and future decisions.

Features

Build Social Features, Fast

Production-ready React hooks and components. No backend complexity, no infrastructure headaches. Just clean APIs that work out of the box.

Zero-Config Authentication

Wrap once with ReplykeProvider, then call useAuth() for instant sign-up / sign-in / sign-out logic with email and password.

Replyke also supports easy integration with your existing user system or any external authentication systems you wish to integrate.

email authentication
external integrations
user state
Authentication.tsx
1import { ReplykeProvider, useAuth, useUser } from "@replyke/react-js";
2
3 function App() {
4 return (
5 <ReplykeProvider projectId="your-project-id">
6 <AuthenticatedApp />
7 </ReplykeProvider>
8 );
9 }
10
11 function AuthenticatedApp() {
12 const { signUpWithEmailAndPassword, signInWithEmailAndPassword, signOut } = useAuth();
13 const { user } = useUser();
14 const [email, setEmail] = useState("");
15 const [password, setPassword] = useState("");
16
17 const handleSignUp = async () => {
18 try {
19 await signUpWithEmailAndPassword({
20 email,
21 password,
22 username: email.split("@")[0], // Generate username from email
23 name: "Demo User"
24 });
25 } catch (error) {
26 console.error("Sign up failed:", error);
27 }
28 };
29
30 const handleSignIn = async () => {
31 try {
32 await signInWithEmailAndPassword({ email, password });
33 } catch (error) {
34 console.error("Sign in failed:", error);
35 }
36 };
37
38 return user ? (
39 <div>
40 <p>Welcome, {user.username}!</p>
41 <button onClick={signOut}>Sign Out</button>
42 </div>
43 ) : (
44 <div>
45 <input
46 type="email"
47 placeholder="Email"
48 value={email}
49 onChange={(e) => setEmail(e.target.value)}
50 />
51 <input
52 type="password"
53 placeholder="Password"
54 value={password}
55 onChange={(e) => setPassword(e.target.value)}
56 />
57 <button onClick={handleSignUp}>Sign Up</button>
58 <button onClick={handleSignIn}>Sign In</button>
59 </div>
60 );
61 }

Dynamic Content Feeds

useEntityList() streams content with built-in pagination, real-time filtering, and sorting. Redux-powered state management handles infinite scroll, content filters, and optimistic updates automatically.

Already have exisiting content data? No problem - you can leverage Replyke's content streaming capabilities to score, filter and stream your existing data.

infinite scroll
content filtering
Redux state
ContentFeed.tsx
1import { useEntityList, EntityProvider } from "@replyke/react-js";
2
3 function ContentFeed() {
4 const {
5 entities,
6 loading,
7 hasMore,
8 loadMore,
9 fetchEntities
10 } = useEntityList({
11 listId: "home-feed"
12 });
13
14 // Initialize the feed with basic configuration
15 useEffect(() => {
16 fetchEntities({
17 sortBy: "hot"
18 }, {
19 sourceId: "user-posts",
20 limit: 10
21 });
22 }, []);
23
24 const handleFilterByContent = (keyword: string) => {
25 fetchEntities({
26 sortBy: "hot",
27 keywordsFilters: { includes: [keyword] }
28 }, { resetUnspecified: true });
29 };
30
31 const handleFilterByTime = (timeFrame: string) => {
32 fetchEntities({
33 sortBy: "hot",
34 timeFrame: timeFrame
35 }, { resetUnspecified: true });
36 };
37
38 return (
39 <div>
40 <div className="filters">
41 <button onClick={() => handleFilterByContent("javascript")}>
42 JavaScript Posts
43 </button>
44 <button onClick={() => handleFilterByContent("react")}>
45 React Posts
46 </button>
47 <button onClick={() => handleFilterByTime("week")}>
48 This Week
49 </button>
50 <button onClick={() => handleFilterByTime("month")}>
51 This Month
52 </button>
53 </div>
54
55 <div className="feed">
56 {entities.map(entity => (
57 <EntityProvider key={entity.id} entity={entity}>
58 <PostCard />
59 </EntityProvider>
60 ))}
61 </div>
62
63 {hasMore && (
64 <button onClick={loadMore} disabled={loading}>
65 {loading ? "Loading..." : "Load More"}
66 </button>
67 )}
68 </div>
69 );
70 }

Dual-Style Comments

Choose between SocialCommentSection and ThreadedCommentSection for different UX styles.

Both include nested replies, likes/votes, @mentions, voting and GIF support out-of-the-box.

Intrested in a custom comments UI? No problem - all Replyke comments components are built with composability in mind and can be customized to fit your exact needs.

dual styles
@mentions
nested replies
Comments.tsx
1import {
2 SocialCommentSection,
3 ThreadedCommentSection
4 } from "@replyke/comments-social-react-js";
5
6 function CommentsDemo({ entity }: { entity: Entity }) {
7 const [style, setStyle] = useState<"social" | "threaded">("social");
8
9 return (
10 <div>
11 <div className="style-selector">
12 <button
13 onClick={() => setStyle("social")}
14 className={style === "social" ? "active" : ""}
15 >
16 Social Style
17 </button>
18 <button
19 onClick={() => setStyle("threaded")}
20 className={style === "threaded" ? "active" : ""}
21 >
22 Threaded Style
23 </button>
24 </div>
25
26 {style === "social" ? (
27 <SocialCommentSection
28 entity={entity}
29 callbacks={{
30 loginRequiredCallback: () => showLoginModal(),
31 }}
32 />
33 ) : (
34 <ThreadedCommentSection
35 entity={entity}
36 highlightedCommentId={urlParams.commentId}
37 callbacks={{
38 loginRequiredCallback: () => showLoginModal()
39 }}
40 />
41 )}
42 </div>
43 );
44 }

Optimistic Voting

EntityProvider + useEntity() gives instant UI feedback with vote states, counts, and user interaction flags.

Replyke handles optimistic updates and server sync automatically, so you can focus on the user-experience knowing that the data is taken care of.

optimistic UI
vote states
instant feedback
VotingSystem.tsx
1import { EntityProvider, useEntity } from "@replyke/react-js";
2
3 function PostWithVoting({ entity }: { entity: Entity }) {
4 return (
5 <EntityProvider entity={entity}>
6 <PostCard />
7 </EntityProvider>
8 );
9 }
10
11 function PostCard() {
12 const {
13 entity,
14 userUpvotedEntity,
15 userDownvotedEntity,
16 upvoteEntity,
17 removeEntityUpvote,
18 downvoteEntity,
19 removeEntityDownvote
20 } = useEntity();
21
22 const handleUpvote = () => {
23 if (userUpvotedEntity) {
24 removeEntityUpvote();
25 } else {
26 upvoteEntity();
27 }
28 };
29
30 const handleDownvote = () => {
31 if (userDownvotedEntity) {
32 removeEntityDownvote();
33 } else {
34 downvoteEntity();
35 }
36 };
37
38 return (
39 <div className="post-card">
40 <h3>{entity?.title}</h3>
41 <p>{entity?.content}</p>
42
43 <div className="voting-controls">
44 <button
45 onClick={handleUpvote}
46 className={userUpvotedEntity ? "active" : ""}
47 >
48 ▲ {entity?.upvotes.length}
49 </button>
50 <button
51 onClick={handleDownvote}
52 className={userDownvotedEntity ? "active" : ""}
53 >
54 ▼ {entity?.downvotes.length}
55 </button>
56 </div>
57 </div>
58 );
59 }

User Profiles & Social Graph

View user profiles with bio, birthdate, and social actions — all with optimistic updates.

useFollowManager() handles one-sided follows/unfollows.

useConnectionManager() manages two-sided connections (requests and approvals, like friendships on Facebook or LinkedIn).

profile viewing
social graph
follow system
UserProfile.tsx
1import { useFollowManager, useFetchUser, useUser } from "@replyke/react-js";
2
3 function UserProfile({ userId }: { userId: string }) {
4 const { user: currentUser } = useUser();
5 const { isFollowing, isLoading, toggleFollow } = useFollowManager({ userId });
6 const [profileUser, setProfileUser] = useState(null);
7 const [loading, setLoading] = useState(true);
8
9 const fetchUser = useFetchUser();
10
11 useEffect(() => {
12 const loadProfile = async () => {
13 try {
14 setLoading(true);
15 const user = await fetchUser({ userId });
16 setProfileUser(user);
17 } catch (error) {
18 console.error("Failed to fetch user profile:", error);
19 } finally {
20 setLoading(false);
21 }
22 };
23
24 if (userId) {
25 loadProfile();
26 }
27 }, [userId, fetchUser]);
28
29 if (loading) {
30 return <div className="loading">Loading profile...</div>;
31 }
32
33 if (!profileUser) {
34 return <div className="error">Profile not found</div>;
35 }
36
37 return (
38 <div className="profile">
39 <div className="profile-header">
40 <img
41 src={profileUser.avatar || "/default-avatar.png"}
42 alt={profileUser.username + " avatar"}
43 className="avatar"
44 />
45 <div className="profile-info">
46 <h2>@{profileUser.username}</h2>
47 {profileUser.name && <p className="name">{profileUser.name}</p>}
48 </div>
49
50 {currentUser && currentUser.id !== userId && (
51 <button
52 onClick={toggleFollow}
53 disabled={isLoading}
54 className={isFollowing ? "btn-following" : "btn-follow"}
55 >
56 {isLoading ? "..." : isFollowing ? "Following" : "Follow"}
57 </button>
58 )}
59 </div>
60
61 <div className="profile-details">
62 {profileUser.bio && (
63 <div className="bio">
64 <h3>Bio</h3>
65 <p>{profileUser.bio}</p>
66 </div>
67 )}
68
69 {profileUser.birthdate && (
70 <div className="birthdate">
71 <h3>Born</h3>
72 <p>{new Date(profileUser.birthdate).toLocaleDateString("en-US", {
73 month: "long",
74 day: "numeric",
75 year: "numeric"
76 })}</p>
77 </div>
78 )}
79
80 <div className="join-date">
81 <h3>Joined</h3>
82 <p>{new Date(profileUser.createdAt).toLocaleDateString("en-US", {
83 month: "long",
84 year: "numeric"
85 })}</p>
86 </div>
87 </div>
88 </div>
89 );
90 }

Smart Collections

useLists() enables hierarchical collections with navigation, creation, and management. useIsEntitySaved() provides bookmark states. Perfect for organizing content with Redux-powered persistence.

With these hooks, users can create nested collections, save items, and navigate their organizational structure seamlessly - increasing engagement and retention.

hierarchical navigation
bookmark states
collection management
Collections.tsx
1import { useLists, useIsEntitySaved, useEntity } from "@replyke/react-js";
2
3 function CollectionsManager() {
4 const { entity } = useEntity();
5 const {
6 currentList,
7 subLists,
8 loading,
9 createList,
10 deleteList,
11 updateList,
12 addToList,
13 removeFromList,
14 isEntityInList,
15 openList,
16 goBack
17 } = useLists();
18
19 const { checkIfEntityIsSaved } = useIsEntitySaved();
20 const [newListName, setNewListName] = useState("");
21 const [editingList, setEditingList] = useState(null);
22
23 const handleCreateList = async () => {
24 if (!newListName.trim()) return;
25 await createList({ listName: newListName.trim() });
26 setNewListName("");
27 };
28
29 const handleSaveToCollection = async () => {
30 if (!entity) return;
31
32 if (isEntityInList(entity.id)) {
33 await removeFromList({ entityId: entity.id });
34 } else {
35 await addToList({ entityId: entity.id });
36 }
37 };
38
39 return (
40 <div className="collections-manager">
41 {currentList && (
42 <div className="navigation">
43 <button onClick={goBack}>← Back to {currentList.parentName}</button>
44 <h3>{currentList.name}</h3>
45 </div>
46 )}
47
48 <div className="current-entity-actions">
49 {entity && (
50 <button
51 onClick={handleSaveToCollection}
52 className={isEntityInList(entity.id) ? "saved" : ""}
53 >
54 {isEntityInList(entity.id) ? "✓ Saved" : "+ Save to Collection"}
55 </button>
56 )}
57 </div>
58
59 <div className="collections-list">
60 {subLists.map(list => (
61 <div key={list.id} className="collection-item">
62 <div onClick={() => openList(list)} className="collection-info">
63 📁 {list.name} ({list.entityIds.length} items)
64 </div>
65 <button onClick={() => setEditingList(list.id)}>Edit</button>
66 <button onClick={() => deleteList({ list })}>Delete</button>
67 </div>
68 ))}
69 </div>
70
71 <div className="create-collection">
72 <input
73 value={newListName}
74 onChange={(e) => setNewListName(e.target.value)}
75 placeholder="New collection name"
76 onKeyDown={(e) => e.key === "Enter" && handleCreateList()}
77 />
78 <button onClick={handleCreateList} disabled={!newListName.trim()}>
79 Create Collection
80 </button>
81 </div>
82 </div>
83 );
84 }

In-app Notifications

useAppNotifications() provides notificatitons data, unread notification counts and mark-as-read functionality.

Interested in customizing the noitfications text? Simply pass a notification templates object with your custom formats.

By notifying users of important events related to them, your product will experience increased engagement and retention.

real-time updates
notification templates
unread tracking
Notifications.tsx
1import { useAppNotifications, AppNotification } from "@replyke/react-js";
2
3 function NotificationCenter() {
4 const {
5 appNotifications,
6 unreadAppNotificationsCount,
7 loading,
8 hasMore,
9 loadMore,
10 markNotificationAsRead,
11 markAllNotificationsAsRead
12 } = useAppNotifications({
13 limit: 15,
14 notificationTemplates: {
15 entityComment: {
16 title: "New comment on your post",
17 content: "$initiatorName commented: '$commentContent'"
18 },
19 commentReply: {
20 title: "Reply to your comment",
21 content: "$initiatorName replied to your comment"
22 },
23 entityUpvote: {
24 title: "Your post was liked",
25 content: "$initiatorName liked your post '$entityTitle'"
26 },
27 newFollow: {
28 title: "New follower",
29 content: "$initiatorName started following you"
30 },
31 entityMention: {
32 title: "You were mentioned",
33 content: "$initiatorName mentioned you in a post"
34 }
35 }
36 });
37
38 const handleNotificationClick = (notification: AppNotification.UnifiedAppNotification) => {
39 if (!notification.isRead) {
40 markNotificationAsRead(notification.id);
41 }
42
43 // Navigate based on notification type
44 switch (notification.type) {
45 case "entity-comment":
46 navigate("/posts/" + notification.metadata.entityShortId);
47 break;
48 case "new-follow":
49 navigate("/users/" + notification.metadata.initiatorId);
50 break;
51 case "entity-upvote":
52 navigate("/posts/" + notification.metadata.entityShortId);
53 break;
54 }
55 };
56
57 return (
58 <div className="notification-center">
59 <div className="notification-header">
60 <h3>
61 Notifications
62 {unreadAppNotificationsCount > 0 && (
63 <span className="unread-badge">{unreadAppNotificationsCount}</span>
64 )}
65 </h3>
66 {unreadAppNotificationsCount > 0 && (
67 <button onClick={markAllNotificationsAsRead}>
68 Mark All Read
69 </button>
70 )}
71 </div>
72
73 <div className="notifications-list">
74 {appNotifications.map(notification => (
75 <div
76 key={notification.id}
77 onClick={() => handleNotificationClick(notification)}
78 className={"notification-item" + !notification.isRead ? "unread" : ""}
79 >
80 <div className="notification-content">
81 <h4>{notification.title}</h4>
82 <p>{notification.content}</p>
83 <span className="timestamp">
84 {formatTimestamp(notification.createdAt)}
85 </span>
86 </div>
87 {!notification.isRead && <div className="unread-indicator" />}
88 </div>
89 ))}
90 </div>
91
92 {hasMore && (
93 <button onClick={loadMore} disabled={loading}>
94 {loading ? "Loading..." : "Load More"}
95 </button>
96 )}
97 </div>
98 );
99 }
Safety & Moderation

Built-in Moderation & Safety Tools

Building user-generated content platforms means dealing with moderation. Replyke handles the complex safety infrastructure so you can focus on building great experiences.

User Reporting System

Built-in reporting functionality allows users to flag inappropriate content instantly.

Developer Hooks

Easy-to-use hooks let you implement custom reporting on any entities in your app.

Centralized Dashboard

Manage all reports, remove content, and moderate users from one unified interface.

User Management

Ban users for flexible timeframes or indefinitely with comprehensive user controls.

Content Oversight

Review, approve, or remove user-generated content to maintain community standards.

Safe Communities

Build trust and safety into your platform from day one with proven moderation tools.

ReportPostDialog.tsx
1import { useCreateReport } from "@replyke/react-js";
2
3function ReportPostDialog({ entityId }: { entityId: string }) {
4 const createReport = useCreateReport({ type: "entity" });
5
6 const handleSubmitReport = async (reason: string) => {
7 await createReport({
8 targetId: entityId,
9 reason
10 });
11 };
12
13 return (
14 <select onChange={(e) => handleSubmitReport(e.target.value)}>
15 <option value="">Report content...</option>
16 <option value="spam">Spam</option>
17 <option value="harassment">Harassment</option>
18 <option value="inappropriate">Inappropriate</option>
19 </select>
20 );
21}
reporting systemuser managementcontent removalsafe communities

Focus on building your platform while Replyke ensures your community stays safe, engaged, and free from harmful content.

Authentication Made Easy

Securely add email and password authentication or integrate third-party login systems to get users onboard effortlessly.

Dynamic Comments

Add threaded comments with upvotes, downvotes, and mentions, creating engaging discussions across your content.

Customizable Feeds

Deliver personalized feeds with filtering, sorting, and time-based options to keep users engaged and informed.

Built-In Voting System

Boost engagement with upvotes and downvotes while automatically scoring posts to deliver smarter, more engaging feeds.

In-App Notifications

Notify users instantly about votes, mentions, replies and more, driving real-time interaction and engagement.

Collections

Empower users to organize content with lists and sub-lists tailored to their needs.

Rich User Profiles

Create personalized user experiences with customizable profiles that showcase avatars, bios, and more.

Robust Reporting

Empower users to report inappropriate content while you manage reports from a centralized dashboard.

Pricing & Plans

Choose the plan that's right for you or start free and upgrade later. No hidden fees, no surprises.

Free

$0

/month
Hobby

$15

/month
Pro

$25

/month
Growth

$75

/month

Dashboard Seats

1
1
2
5

Monthly Active Users (MAU)

500
5k
25k
100k

Records stored (entities, comments, notifications, lists)

50k
500k
5M
25M

API Calls

250k/month
2M/month
10M/month
40M/month

Egress (Data Transfer Out)

1GB
20GB
80GB
320GB

Storage

DB 1GB + Files 1GB
DB 3GB + Files 5GB
DB 10GB + Files 25GB
DB 30GB + Files 100GB

Back-office & Moderation Dashboard

Need more than our Growth plan?

We offer custom pricing for high-volume applications and enterprise customers. Let's discuss a plan that fits your specific requirements.

Frequently Asked Questions