Skip to content

JavaScript SDK

The FeedbackKit JavaScript SDK is a TypeScript client for listing, submitting and voting on feedback from any web app, in the browser or in Node.js. Create a client with your project API key and build the feedback UI your site needs on top of it.

Node.js 18+ Browsers GitHub
npm install feedbackkit-js

Installation

Install the FeedbackKit JavaScript SDK using your preferred package manager.

terminal
# Using npm
npm install feedbackkit-js

# Using yarn
yarn add feedbackkit-js

# Using pnpm
pnpm add feedbackkit-js

The SDK is written in TypeScript and ships with full type definitions. No additional @types package is needed. There is no UMD or IIFE build and no global. Node.js 18+ is required because the client uses global fetch. The registry copy is 1.0.1. Snippets on this page are tag 2.1.0. That tag does not commit dist/, so a git dependency on it has to be built before it can be imported.

Quick Start

Initialize the client with your project API key and start collecting feedback in minutes.

app.ts
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 local_<uuid> Session id. Omit it and a local_ UUID is generated. 1.0.1 does not generate one.
baseUrl string No https://api.prod.getfeedbackkit.com/api/v1 https only, except http on localhost, 127.0.0.1, and ::1. Redirects are refused. 1.0.1 defaults to https://feedbackkit.swiftly-workspace.com/api/v1 and allows any http URL.
bearerToken string No — Server credential for admin replies. Leave unset in a browser app. Not in 1.0.1.
timeout number No 30000 Request timeout in ms

Anonymous id, https, and where it runs

With no userId, 2.1.0 stores local_<uuid> in localStorage under feedbackkit_user_id when storage works. Node and a throwing localStorage keep it in memory. setUserId(undefined) clears it. The next request generates a new one and sends it as X-User-Id. Create, vote, unvote, and comment copy it into user_id when that call omits userId. A different userId on the call does not replace the session id, and the header is omitted for that call so the two cannot disagree. users.register and events.track still require their own userId.

A non-loopback http:// base URL throws ValidationError. fetch uses redirect: 'error'. The client does not retry. A 429 is RateLimitError, and retryAfterSeconds is set from Retry-After when that header is delta-seconds or an HTTP date. 1.0.1 turns 429 and 5xx into the base FeedbackKitError with code SERVER_ERROR and ignores Retry-After.

In the browser the project API key is in the bundle. It identifies a project, not a person. On the SDK API this package calls, CORS allows FeedbackKit's own origins and a few localhost ports. A customer origin is not on that list, and there is no project setting that adds one, so a browser fetch from your production site fails before you see a status. Call the SDK from Node on your server instead. The embed API is the exception: /api/v1/embed/, which the script widget (/embed/feedbackkit-embed.js) calls, accepts cross-origin requests from any site, without cookies or credentials. So the script widget, or an iframe of /embed/<apiKey>/kanban, /roadmap, or /form, works on any site where embedding is turned on for the project. 1.0.1 does not generate an anonymous id, so a client with no userId sends no X-User-Id and every row has hasVoted: false. 1.0.1 also returns JSON keys as the server sent them (vote_count). This source camelCases response keys. values and event properties stay verbatim.

Client Methods

config.ts
import { FeedbackKit } from 'feedbackkit-js';

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
feedback-api.ts
import { FeedbackKit, FeedbackCategory, FeedbackSort, FeedbackStatus } from 'feedbackkit-js';

const client = new FeedbackKit({ apiKey: 'sf_your_api_key' });

// 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,
    sort: FeedbackSort.Newest
});

// 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. The request defaults to an empty object. On npm 1.0.1 the request argument and its userId are required.
unvote(feedbackId: string, request?: UnvoteRequest): Promise<VoteResponse> Remove a vote. The request defaults to an empty object. On npm 1.0.1 the request argument and its userId are required.
votes-api.ts
import { FeedbackKit } from 'feedbackkit-js';

const client = new FeedbackKit({ apiKey: 'sf_your_api_key' });

// Simple vote. userId is optional in 2.1.0. It is required on npm 1.0.1.
const voted = await client.votes.vote('feedback-id', {
    userId: 'user_12345'
});

// Vote with email notification opt-in. Stored only on the Team tier, and only with an email.
const notified = 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
comments-api.ts
import { FeedbackKit } from 'feedbackkit-js';

const client = new FeedbackKit({
    apiKey: 'sf_your_api_key',
    bearerToken: 'server-bearer-token'
});

// 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'
});

// Admin response. bearerToken is required. Do not ship it in a browser bundle.
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
users-api.ts
import { FeedbackKit } from 'feedbackkit-js';

const client = new FeedbackKit({ apiKey: 'sf_your_api_key' });

// Register a free user. userId is required. The session id is not copied in.
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
events-api.ts
import { FeedbackKit, SDKEvents } from 'feedbackkit-js';

const client = new FeedbackKit({ apiKey: 'sf_your_api_key' });

// 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
isHiddenOverLimit boolean? Free-tier cap. The write still returns 2xx. Later reads hide it except from that submitter. Not on the npm 1.0.1 type.

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 No Defaults to the session id. Required on npm 1.0.1.
userEmail string No Optional email for status update notifications

VoteRequest

Field Type Required Description
userId string No Defaults to the session id. Required on npm 1.0.1. Status emails need Team and a non-empty email.
email string No Email for status change notifications
notifyStatusChange boolean No Opt-in to receive email notifications

UnvoteRequest

Field Type Required Description
userId string No Defaults to the session id. Required on npm 1.0.1.

CreateCommentRequest

Field Type Required Description
content string Yes Comment text (1-2000 chars)
userId string No Defaults to the session id. Required on npm 1.0.1. isAdmin needs bearerToken in 2.1.0.
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. Sent as the query key includeMerged.
sort FeedbackSort No votes (default), newest, oldest, or comments. Not on npm 1.0.1.

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 A subscription gate. The Free feedback cap is not a 402.
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 or 422 BAD_REQUEST / UNPROCESSABLE_ENTITY Validation failed. 422 when the body has no machine code. A missing apiKey throws this before the request.
RateLimitError 429 RATE_LIMITED retryAfterSeconds from Retry-After. The client does not retry. Not in npm 1.0.1.
ServerError 5xx SERVER_ERROR The server failed. Not in npm 1.0.1, where 5xx is the base class.
NetworkError 0 NETWORK_ERROR Network connectivity issue
error-handling.ts
import {
    FeedbackKit,
    FeedbackCategory,
    FeedbackKitError,
    AuthenticationError,
    PaymentRequiredError,
    NotFoundError,
    ConflictError,
    RateLimitError,
    ServerError,
    NetworkError
} from 'feedbackkit-js';

const client = new FeedbackKit({ apiKey: 'sf_your_api_key' });

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 RateLimitError) {
        console.error(error.retryAfterSeconds);
    } else if (error instanceof ServerError) {
        console.error(error.statusCode);
    } 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}`);
    }
}