
Display custom data for each company or person in each thread. Display things like their pricing tier, usage, an impersonation button, activation milestones and more to enable an excellent and fast support experience for your customers.
Productlane does not have a generic "add a custom field" builder for customers or threads. Companies and contacts ship with a fixed set of built-in properties (status, tier, owner, revenue, size, logo, domains), and threads carry built-in metadata like status, tags, and assignee.
If you need something beyond that, like a Stripe MRR figure, a usage counter, a feature flag, or an impersonation link, use a Connector instead of a custom field:
Built-in properties | Connectors | |
|---|---|---|
Where data lives | Stored in Productlane | Stays in your own system, fetched live |
Editable in Productlane UI | Yes | No, read-only display |
Filterable / used in views | Yes | No |
Data source | Set manually or via CRM sync (Attio, HubSpot) | Any endpoint you control |
Best for | Segmenting and filtering customers | Showing rich, live, or fast-changing data on a thread or contact |
In short: a Connector is how you show custom customer or thread data in Productlane without needing a dedicated custom-properties feature. It renders on the thread and on the company or contact sidebar, pulling fresh data from your own backend on every load.
In your Custom Data settings you specify the endpoint that returns the JSON data of your components. This handler also takes care of fetching and parsing data to be represented. You can also provide headers optionally when creating a Custom Data.
The specified endpoint can handle complex data and integrate multiple data sources. In the end it is important that it returns your customized components structure. The following shows an example using NextJS.
Create the function that serves the request under e.g. pages/api/sidebar/section.ts.
export default async function handler(req: NextApiRequest, res: NextApiResponse) { return res.status(200).json({ components: [ ... // Components structure ] }) }
Integrate data sources and fetch all data you want e.g. Stripe, Prisma, etc.
export default async function handler(req: NextApiRequest, res: NextApiResponse) { // Use emailconst email = (req.body as { email: string; threadId: string }).email; // Fetch for customer dataconst customer = await prisma.customer.findUnique({ where: { email, }, }); // Get subscription from customerconst { items } = await stripe.subscriptions.retrieve( customer.subscriptionId ); // Take priceId from first subscriptionconst priceId = items.data[0]?.price.id; return res.status(200).json({ components: [ { // Components structure type: "row", left: [ { type: "text", color: "normal", size: "m", text: "Current price", }, ], right: [ { type: "text", color: "muted", size: "s", text: priceId, }, ], }, ], }); }
The request body contains:
threadId: string
email: string
Back on the Thread you will find a new section with the title and the data.
We provide you with a set of components including text and button to help you build your custom data integration. Each component needs to be included in a row which is separated by a left and right section for proper vertical and horizontal layout. Choose the attributes you want to apply and add them into a JSON response from your endpoint.
Type | Attributes |
|---|---|
Row |
|
{ type: "row", left: [ { type: "text", color: "normal", size: "m", text: "Threads created" }, { type: "text", color: "muted", size: "s", text: "In the last 2 weeks" }, ], right: [{ type: "badge", color: "green", text: "25 of 100" }], }
Type | Attributes |
|---|---|
Text |
|
{ type: "text", color: "normal | muted", size: "s | m | l", text: "Threads created" }
Type | Attributes |
|---|---|
Badge |
|
{ type: "badge", color: "green | gray", text: "25 of 100" }
Type | Attributes |
|---|---|
Spacer |
|
{ type: "spacer", size: "s | m | l" }
### LinkButton Type | Attributes |
|---|---|
LinkButton |
|
{ type: "linkButton", label: "View usage", url: "https://google.com" }
Type | Attributes |
|---|---|
CopyButton |
|
{ type: "copyButton", value: `https://productlane.com/threads/${id}`, tooltip: "Copy Productlane thread link" }
One full example might look like the following. You need to return this structure from your defined webhook/endpoint in order to be rendered as Custom Sidebar Cards.
const components = [ { type: "row", left: [ { type: "text", color: "normal", size: "m", text: "Threads created" }, { type: "text", color: "muted", size: "s", text: "In the last 2 weeks" }, ], right: [{ type: "badge", color: "green", text: "25 of 100" }], }, { type: "spacer", size: "m" }, { type: "row", left: [{ type: "text", color: "muted", size: "s", text: "MRR" }], right: [{ type: "text", color: "normal", size: "m", text: "149" }], }, { type: "spacer", size: "m" }, { type: "row", left: [{ type: "text", color: "muted", size: "s", text: "ARR" }], right: [{ type: "text", color: "normal", size: "m", text: "1.788" }], }, { type: "spacer", size: "m" }, { type: "linkButton", label: "View usage", url: "https://google.com" }, ];