Skip to content

Conversation

@bensabic
Copy link
Contributor

@bensabic bensabic commented Dec 2, 2025

Summary

Adds Dub plugin with the following actions:

  • Create Link: Create a new short link
  • Upsert Link: Create or update a link by URL or external ID

Features:

  • Grouped config fields for Link IDs, Link Preview, and UTM Parameters
  • Returns link ID, short URL, QR code URL, domain, key, and destination URL

Test plan

  • Connection test validates credentials
  • Actions execute successfully in a workflow
  • Error handling works for invalid inputs

@vercel
Copy link
Contributor

vercel bot commented Dec 2, 2025

@bensabic is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI review requested due to automatic review settings December 6, 2025 05:39
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a Dub plugin for creating and managing short links with two main actions: Create Link and Upsert Link. The implementation includes proper credential management, error handling, and grouped configuration for Link IDs, Link Preview metadata, and UTM parameters.

  • Adds complete Dub integration with API key authentication
  • Implements two actions: Create Link (POST) and Upsert Link (PUT) with comprehensive input fields
  • Returns structured link data including short URL, QR code, and metadata

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
plugins/index.ts Registers the new Dub plugin in the plugins index
plugins/dub/index.ts Main plugin configuration defining actions, form fields, and test config
plugins/dub/credentials.ts Type definition for Dub API credentials
plugins/dub/icon.tsx SVG icon component for Dub branding
plugins/dub/test.ts Connection test function to validate API credentials
plugins/dub/steps/create-link.ts Step handler for creating new short links via POST
plugins/dub/steps/upsert-link.ts Step handler for creating or updating links via PUT

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +55 to +136
async function stepHandler(
input: CreateLinkCoreInput,
credentials: DubCredentials
): Promise<CreateLinkResult> {
const apiKey = credentials.DUB_API_KEY;

if (!apiKey) {
return {
success: false,
error:
"DUB_API_KEY is not configured. Please add it in Project Integrations.",
};
}

if (!input.url) {
return {
success: false,
error: "Destination URL is required",
};
}

try {
const body: Record<string, string> = {
url: input.url,
};

if (input.key) body.key = input.key;
if (input.domain) body.domain = input.domain;
if (input.externalId) body.externalId = input.externalId;
if (input.tenantId) body.tenantId = input.tenantId;
if (input.programId) body.programId = input.programId;
if (input.partnerId) body.partnerId = input.partnerId;
if (input.title) body.title = input.title;
if (input.description) body.description = input.description;
if (input.image) body.image = input.image;
if (input.video) body.video = input.video;
if (input.utm_source) body.utm_source = input.utm_source;
if (input.utm_medium) body.utm_medium = input.utm_medium;
if (input.utm_campaign) body.utm_campaign = input.utm_campaign;
if (input.utm_term) body.utm_term = input.utm_term;
if (input.utm_content) body.utm_content = input.utm_content;

const response = await fetch(`${DUB_API_URL}/links`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(body),
});

if (!response.ok) {
const errorData = (await response.json().catch(() => ({}))) as {
error?: { message?: string };
message?: string;
};
const errorMessage =
errorData.error?.message || errorData.message || `HTTP ${response.status}`;
return {
success: false,
error: errorMessage,
};
}

const link = (await response.json()) as DubLinkResponse;

return {
success: true,
id: link.id,
shortLink: link.shortLink,
qrCode: link.qrCode,
domain: link.domain,
key: link.key,
url: link.url,
};
} catch (error) {
return {
success: false,
error: `Failed to create link: ${getErrorMessage(error)}`,
};
}
}
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stepHandler function in this file and plugins/dub/steps/upsert-link.ts share almost identical logic (lines 55-136 here vs lines 55-136 there). The only differences are the API endpoint, HTTP method, and error message. Consider extracting the shared logic into a common utility function to reduce code duplication and improve maintainability. For example, create a shared function that accepts the endpoint, method, and operation name as parameters.

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +170
configFields: [
{
key: "url",
label: "Destination URL",
type: "template-input",
placeholder: "https://example.com/page",
example: "https://example.com/landing-page",
required: true,
},
{
key: "key",
label: "Custom Slug",
type: "template-input",
placeholder: "my-link",
example: "summer-sale",
},
{
key: "domain",
label: "Domain",
type: "template-input",
placeholder: "dub.sh",
example: "dub.sh",
},
{
label: "Link IDs",
type: "group",
fields: [
{
key: "externalId",
label: "External ID",
type: "template-input",
placeholder: "my-external-id",
},
{
key: "tenantId",
label: "Tenant ID",
type: "template-input",
placeholder: "tenant-123",
},
{
key: "programId",
label: "Program ID",
type: "template-input",
placeholder: "program-123",
},
{
key: "partnerId",
label: "Partner ID",
type: "template-input",
placeholder: "partner-123",
},
],
},
{
label: "Link Preview",
type: "group",
fields: [
{
key: "title",
label: "Title",
type: "template-input",
placeholder: "Custom preview title",
},
{
key: "description",
label: "Description",
type: "template-input",
placeholder: "Custom preview description",
},
{
key: "image",
label: "Image URL",
type: "template-input",
placeholder: "https://example.com/image.png",
},
{
key: "video",
label: "Video URL",
type: "template-input",
placeholder: "https://example.com/video.mp4",
},
],
},
{
label: "UTM Parameters",
type: "group",
fields: [
{
key: "utm_source",
label: "Source",
type: "template-input",
placeholder: "newsletter",
},
{
key: "utm_medium",
label: "Medium",
type: "template-input",
placeholder: "email",
},
{
key: "utm_campaign",
label: "Campaign",
type: "template-input",
placeholder: "summer-sale",
},
{
key: "utm_term",
label: "Term",
type: "template-input",
placeholder: "running+shoes",
},
{
key: "utm_content",
label: "Content",
type: "template-input",
placeholder: "logolink",
},
],
},
],
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configFields array for "create-link" (lines 51-170) and "upsert-link" (lines 187-306) are identical. Consider extracting this configuration into a shared constant to reduce duplication and ensure consistency. For example: const linkConfigFields = [...] defined once and reused for both actions.

Copilot uses AI. Check for mistakes.
xmlns="http://www.w3.org/2000/svg"
>
<title>Dub</title>
<path fill-rule="evenodd" clip-rule="evenodd" d="M32 64c17.673 0 32-14.327 32-32 0-11.844-6.435-22.186-16-27.719V48h-8v-2.14A15.9 15.9 0 0 1 32 48c-8.837 0-16-7.163-16-16s7.163-16 16-16c2.914 0 5.647.78 8 2.14V1.008A32 32 0 0 0 32 0C14.327 0 0 14.327 0 32s14.327 32 32 32" fill="currentColor"/>
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SVG attributes should use React/JSX camelCase naming convention. Change fill-rule to fillRule and clip-rule to clipRule to match React's JSX syntax and be consistent with other icon components in the codebase (see plugins/fal/icon.tsx and plugins/slack/icon.tsx).

Suggested change
<path fill-rule="evenodd" clip-rule="evenodd" d="M32 64c17.673 0 32-14.327 32-32 0-11.844-6.435-22.186-16-27.719V48h-8v-2.14A15.9 15.9 0 0 1 32 48c-8.837 0-16-7.163-16-16s7.163-16 16-16c2.914 0 5.647.78 8 2.14V1.008A32 32 0 0 0 32 0C14.327 0 0 14.327 0 32s14.327 32 32 32" fill="currentColor"/>
<path fillRule="evenodd" clipRule="evenodd" d="M32 64c17.673 0 32-14.327 32-32 0-11.844-6.435-22.186-16-27.719V48h-8v-2.14A15.9 15.9 0 0 1 32 48c-8.837 0-16-7.163-16-16s7.163-16 16-16c2.914 0 5.647.78 8 2.14V1.008A32 32 0 0 0 32 0C14.327 0 0 14.327 0 32s14.327 32 32 32" fill="currentColor"/>

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant