Installation
Install the FeedbackKit JavaScript SDK using your preferred package manager.
# Using npm
npm install feedbackkit-js
# Using yarn
yarn add feedbackkit-js
The SDK is written in TypeScript and ships with full type definitions. No additional @types packages are needed.
Quick Start
Initialize the client with your project API key and start collecting feedback in minutes.
import { FeedbackKit, FeedbackCategory } from 'feedbackkit-js';
// Initialize the client
const client = new FeedbackKit({
apiKey: 'your-api-key',
userId: 'user_12345'
});
// List feedback
const feedbacks = await client.feedback.list();
// Submit feedback
const newFeedback = await client.feedback.create({
title: 'Add dark mode',
description: 'Would love a dark mode option',
category: FeedbackCategory.FeatureRequest,
userId: 'user_12345'
});
Configuration
The FeedbackKitConfig interface accepts the following options when creating a new client instance.
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| apiKey | string | Yes | — | Your project API key |
| userId | string | No | — | Default user identifier |
| timeout | number | No | 30000 | Request timeout in ms |
Client Methods
const client = new FeedbackKit({
apiKey: 'sf_your_api_key',
userId: 'user_12345',
timeout: 10000
});
// Update user ID after login
client.setUserId('user_12345');
// Get current user ID
const userId = client.getUserId();
// Clear user ID on logout
client.setUserId(undefined);
API Reference
Feedback API
client.feedback
| Method | Description |
|---|---|
| list(options?: ListFeedbackOptions): Promise<Feedback[]> | List all feedback with optional filters |
| get(feedbackId: string): Promise<Feedback> | Get single feedback by ID |
| create(request: CreateFeedbackRequest): Promise<Feedback> | Submit new feedback |
// Get all feedback
const all = await client.feedback.list();
// Filter by status
const pending = await client.feedback.list({
status: FeedbackStatus.Pending
});
// Filter by category
const bugs = await client.feedback.list({
category: FeedbackCategory.BugReport
});
// Include merged items
const withMerged = await client.feedback.list({
includeMerged: true
});
// Get single feedback
const feedback = await client.feedback.get('feedback-uuid');
console.log(feedback.title, feedback.voteCount);
// Submit new feedback
const created = await client.feedback.create({
title: 'Add dark mode',
description: 'Please add a dark mode option for night time use.',
category: FeedbackCategory.FeatureRequest,
userId: 'user_12345',
userEmail: 'user@example.com' // optional
});
Votes API
client.votes
| Method | Description |
|---|---|
| vote(feedbackId: string, request: VoteRequest): Promise<VoteResponse> | Vote for feedback |
| unvote(feedbackId: string, request: UnvoteRequest): Promise<VoteResponse> | Remove vote |
// Simple vote
const result = await client.votes.vote('feedback-id', {
userId: 'user_12345'
});
// Vote with email notification opt-in
const result = await client.votes.vote('feedback-id', {
userId: 'user_12345',
email: 'user@example.com',
notifyStatusChange: true
});
// Remove a vote
const removed = await client.votes.unvote('feedback-id', {
userId: 'user_12345'
});
console.log(removed.hasVoted); // false
Comments API
client.comments
| Method | Description |
|---|---|
| list(feedbackId: string): Promise<Comment[]> | List comments for feedback |
| create(feedbackId: string, request: CreateCommentRequest): Promise<Comment> | Add comment |
// List all comments
const comments = await client.comments.list('feedback-id');
for (const comment of comments) {
console.log(`${comment.userId}: ${comment.content}`);
if (comment.isAdmin) console.log('(Admin response)');
}
// Add a user comment
const comment = await client.comments.create('feedback-id', {
content: 'This would be really helpful!',
userId: 'user_12345'
});
// Add an admin response
const adminComment = await client.comments.create('feedback-id', {
content: 'Thanks for the feedback! We are working on this.',
userId: 'admin_user',
isAdmin: true
});
Users API
client.users
| Method | Description |
|---|---|
| register(request: RegisterUserRequest): Promise<SDKUser> | Register SDK user |
// Register a free user
const user = await client.users.register({
userId: 'user_12345'
});
// Register a paying user with MRR
const payingUser = await client.users.register({
userId: 'user_67890',
mrr: 9.99
});
// Update MRR when subscription changes
const upgraded = await client.users.register({
userId: 'user_67890',
mrr: 19.99
});
Events API
client.events
| Method | Description |
|---|---|
| track(request: TrackEventRequest): Promise<TrackedEvent> | Track analytics event |
import { SDKEvents } from 'feedbackkit-js';
// Track a simple event
await client.events.track({
eventName: 'feedback_list',
userId: 'user_12345'
});
// Track with properties
await client.events.track({
eventName: 'feedback_list',
userId: 'user_12345',
properties: {
filter: 'feature_request',
sort: 'votes'
}
});
// Using predefined SDK events
await client.events.track({
eventName: SDKEvents.FeedbackList,
userId: 'user_12345'
});
// Track a custom event
await client.events.track({
eventName: 'onboarding_completed',
userId: 'user_12345',
properties: {
step_count: 5,
duration_seconds: 120
}
});
Predefined SDK Events:
SDKEvents.FeedbackList,
SDKEvents.FeedbackDetail,
SDKEvents.SubmitFeedback
Models & Types
Feedback
| Field | Type | Description |
|---|---|---|
| id | string | Unique identifier |
| title | string | Feedback title |
| description | string | Detailed description |
| status | FeedbackStatus | Current status |
| category | FeedbackCategory | Feedback category |
| userId | string | ID of the user who submitted |
| userEmail | string? | Email of the submitter (if provided) |
| voteCount | number | Total number of votes |
| hasVoted | boolean | Whether the current user has voted |
| commentCount | number | Total number of comments |
| totalMrr | number? | Combined MRR of all voters |
| createdAt | string | When the feedback was created |
| updatedAt | string | When the feedback was last updated |
| rejectionReason | string? | Explanation for rejection |
| mergedIntoId | string? | ID of feedback this was merged into |
| mergedAt | string? | When this feedback was merged |
| mergedFeedbackIds | string[]? | IDs of feedback items merged into this one |
Enums
FeedbackStatus
| Value | Raw |
|---|---|
| Pending | "pending" |
| Approved | "approved" |
| InProgress | "in_progress" |
| TestFlight | "testflight" |
| Completed | "completed" |
| Rejected | "rejected" |
FeedbackCategory
| Value | Raw |
|---|---|
| FeatureRequest | "feature_request" |
| BugReport | "bug_report" |
| Improvement | "improvement" |
| Other | "other" |
Comment
| Field | Type | Description |
|---|---|---|
| id | string | Unique identifier |
| content | string | Comment text |
| userId | string | ID of the commenting user |
| isAdmin | boolean | Whether this is an admin comment |
| createdAt | string | When the comment was created |
VoteResponse
| Field | Type | Description |
|---|---|---|
| feedbackId | string | ID of the feedback item |
| voteCount | number | Updated vote count |
| hasVoted | boolean | Whether the user has voted after this action |
Request Types
CreateFeedbackRequest
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | Brief title (1-200 chars) |
| description | string | Yes | Detailed description (1-5000 chars) |
| category | FeedbackCategory | Yes | Feedback category |
| userId | string | Yes | Unique identifier of the submitting user |
| userEmail | string | No | Optional email for status update notifications |
VoteRequest
| Field | Type | Required | Description |
|---|---|---|---|
| userId | string | Yes | Unique identifier of the voting user |
| string | No | Email for status change notifications | |
| notifyStatusChange | boolean | No | Opt-in to receive email notifications |
UnvoteRequest
| Field | Type | Required | Description |
|---|---|---|---|
| userId | string | Yes | Unique identifier of the user removing their vote |
CreateCommentRequest
| Field | Type | Required | Description |
|---|---|---|---|
| content | string | Yes | Comment text (1-2000 chars) |
| userId | string | Yes | Unique identifier of the commenting user |
| isAdmin | boolean | No | Whether this comment is from an admin/developer |
ListFeedbackOptions
| Field | Type | Required | Description |
|---|---|---|---|
| status | FeedbackStatus | No | Filter by status |
| category | FeedbackCategory | No | Filter by category |
| includeMerged | boolean | No | Include merged feedback items |
RegisterUserRequest
| Field | Type | Required | Description |
|---|---|---|---|
| userId | string | Yes | Unique identifier of the SDK user |
| mrr | number | No | Monthly Recurring Revenue |
TrackEventRequest
| Field | Type | Required | Description |
|---|---|---|---|
| eventName | string | Yes | Name of the event to track |
| userId | string | Yes | Unique identifier of the user |
| properties | Record<string, unknown> | No | Optional key-value properties for the event |
Error Handling
The SDK provides a typed error hierarchy so you can handle specific error cases. All errors extend the base FeedbackKitError class.
| Error Class | Status Code | Code | Description |
|---|---|---|---|
| FeedbackKitError | — | — | Base error class (statusCode, code, message) |
| AuthenticationError | 401 | UNAUTHORIZED | Invalid or missing API key |
| PaymentRequiredError | 402 | PAYMENT_REQUIRED | Subscription limit exceeded |
| ForbiddenError | 403 | FORBIDDEN | Action not allowed (archived project, voting blocked) |
| NotFoundError | 404 | NOT_FOUND | Resource not found |
| ConflictError | 409 | CONFLICT | Duplicate action (e.g., already voted) |
| ValidationError | 400 | BAD_REQUEST | Request validation failed |
| NetworkError | 0 | NETWORK_ERROR | Network connectivity issue |
import {
FeedbackKit,
FeedbackKitError,
AuthenticationError,
PaymentRequiredError,
NotFoundError,
ConflictError,
NetworkError
} from 'feedbackkit-js';
try {
const feedback = await client.feedback.create({
title: 'My feedback',
description: 'Details here',
category: FeedbackCategory.FeatureRequest,
userId: 'user_12345'
});
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Invalid API key');
} else if (error instanceof PaymentRequiredError) {
console.error('Upgrade your plan to continue');
} else if (error instanceof NotFoundError) {
console.error('Resource not found');
} else if (error instanceof ConflictError) {
console.error('Action already performed');
} else if (error instanceof NetworkError) {
console.error('Check your internet connection');
} else if (error instanceof FeedbackKitError) {
// Catch-all for other API errors
console.error(`Error ${error.statusCode}: ${error.message}`);
}
}