Skip to content

feat: add API key editing functionality to the dashboard#358

Merged
KMKoushik merged 1 commit intousesend:mainfrom
99power:edit-api-keys
Feb 25, 2026
Merged

feat: add API key editing functionality to the dashboard#358
KMKoushik merged 1 commit intousesend:mainfrom
99power:edit-api-keys

Conversation

@99power
Copy link
Contributor

@99power 99power commented Feb 24, 2026

Once API keys have been created, we can't change neither name nor scope. In order to avoid rolling/recreating of a key I thought it'd be useful to edit scope/name.

  • new edit button in /dev-settings
  • new updateApiKey mutation in api router
  • new edit dialog-component
  • new update-function in api-service
  • changed sorting of api-key query to avoid list items jumping after updates

Summary by cubic

Added API key editing in the dashboard to rename keys and change domain access without regenerating keys.

  • New Features

    • Edit button and dialog in Dev Settings to update name and domain access ("All Domains" or a specific domain).
    • Added updateApiKey mutation and service with team-domain validation; updates the list and shows success/error toasts.
  • Bug Fixes

    • Sort API keys by createdAt (desc) to keep the list stable after edits.

Written for commit dbb416c. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Added the ability to edit existing API keys. Users can now update an API key's name and domain assignment directly from the API keys list using an intuitive editing interface.

- new edit button in /dev-settings
- new updateApiKey mutation in api router
- new edit dialog-component
- new update-function in api-service
- changed sorting of api-key query to avoid list items jumping after updates
@vercel
Copy link

vercel bot commented Feb 24, 2026

@99power is attempting to deploy a commit to the kmkoushik's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

Walkthrough

This change introduces API key editing functionality. A new EditApiKeyDialog component is added as a client-side form to edit API key names and domain assignments. The ApiList UI is updated to include an edit button and integrate the dialog, with local state tracking the currently edited key. Backend changes add an updateApiKey tRPC router endpoint and service function that validates domain ownership and conditionally updates the name and domainId fields. Minor formatting adjustments are made to date display logic.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add API key editing functionality to the dashboard' accurately and concisely summarizes the main change: introducing API key editing capability. It is specific, clear, and directly reflects the primary objective of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/app/`(dashboard)/dev-settings/api-keys/api-list.tsx:
- Around line 80-86: The icon-only Edit button lacks an accessible label; update
the Button (the onClick handler that calls setEditingId(apiKey.id) and renders
the Edit3 icon) to include an accessible name—e.g., add an aria-label that
includes identifying context (like the API key name or id) or include
visually-hidden text next to the Edit3 icon—so screen readers can announce which
API key will be edited.
- Around line 13-15: The imports at the top of the file use relative/local paths
instead of the apps/web "~/ " alias; update the three local imports so they use
the "~/..." alias (replace ./delete-api-key and ./edit-api-key with imports from
'~/app/(dashboard)/dev-settings/api-keys/delete-api-key' and
'~/app/(dashboard)/dev-settings/api-keys/edit-api-key' or the appropriate module
paths) while leaving external library imports (Spinner from `@usesend/ui`)
unchanged; ensure symbols DeleteApiKey and EditApiKeyDialog remain correctly
referenced after switching to the alias.

In `@apps/web/src/server/service/api-service.ts`:
- Around line 96-128: The updateApiKey function can call db.apiKey.update with
an empty data object when both name and domainId are undefined; add an early
guard in updateApiKey that checks if name === undefined && domainId ===
undefined and throw a clear error (e.g., "NO_UPDATE_PAYLOAD" or descriptive
message) to avoid a no-op update, while still allowing domainId === null to be
used to clear the domain; update the logic around db.apiKey.update (referencing
updateApiKey and the db.apiKey.update call) to only proceed when there is at
least one field to change.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c9ebc8 and dbb416c.

📒 Files selected for processing (4)
  • apps/web/src/app/(dashboard)/dev-settings/api-keys/api-list.tsx
  • apps/web/src/app/(dashboard)/dev-settings/api-keys/edit-api-key.tsx
  • apps/web/src/server/api/routers/api.ts
  • apps/web/src/server/service/api-service.ts

Comment on lines 13 to 15
import DeleteApiKey from "./delete-api-key";
import { EditApiKeyDialog } from "./edit-api-key";
import Spinner from "@usesend/ui/src/spinner";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use the ~/ alias for local imports in apps/web.

🔧 Proposed fix
-import DeleteApiKey from "./delete-api-key";
-import { EditApiKeyDialog } from "./edit-api-key";
+import DeleteApiKey from "~/app/(dashboard)/dev-settings/api-keys/delete-api-key";
+import { EditApiKeyDialog } from "~/app/(dashboard)/dev-settings/api-keys/edit-api-key";

As per coding guidelines, "Use the /alias for src imports in apps/web (e.g., import { x } from '/utils/x')."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import DeleteApiKey from "./delete-api-key";
import { EditApiKeyDialog } from "./edit-api-key";
import Spinner from "@usesend/ui/src/spinner";
import DeleteApiKey from "~/app/(dashboard)/dev-settings/api-keys/delete-api-key";
import { EditApiKeyDialog } from "~/app/(dashboard)/dev-settings/api-keys/edit-api-key";
import Spinner from "@usesend/ui/src/spinner";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/`(dashboard)/dev-settings/api-keys/api-list.tsx around lines
13 - 15, The imports at the top of the file use relative/local paths instead of
the apps/web "~/ " alias; update the three local imports so they use the "~/..."
alias (replace ./delete-api-key and ./edit-api-key with imports from
'~/app/(dashboard)/dev-settings/api-keys/delete-api-key' and
'~/app/(dashboard)/dev-settings/api-keys/edit-api-key' or the appropriate module
paths) while leaving external library imports (Spinner from `@usesend/ui`)
unchanged; ensure symbols DeleteApiKey and EditApiKeyDialog remain correctly
referenced after switching to the alias.

Comment on lines +80 to +86
<Button
variant="ghost"
size="sm"
onClick={() => setEditingId(apiKey.id)}
>
<Edit3 className="h-4 w-4" />
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add an accessible label for the icon-only Edit button.

✅ Suggested fix
                       <Button
                         variant="ghost"
                         size="sm"
+                        aria-label="Edit API key"
                         onClick={() => setEditingId(apiKey.id)}
                       >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
variant="ghost"
size="sm"
onClick={() => setEditingId(apiKey.id)}
>
<Edit3 className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
aria-label="Edit API key"
onClick={() => setEditingId(apiKey.id)}
>
<Edit3 className="h-4 w-4" />
</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/`(dashboard)/dev-settings/api-keys/api-list.tsx around lines
80 - 86, The icon-only Edit button lacks an accessible label; update the Button
(the onClick handler that calls setEditingId(apiKey.id) and renders the Edit3
icon) to include an accessible name—e.g., add an aria-label that includes
identifying context (like the API key name or id) or include visually-hidden
text next to the Edit3 icon—so screen readers can announce which API key will be
edited.

Comment on lines +96 to +128
export async function updateApiKey({
id,
teamId,
name,
domainId,
}: {
id: number;
teamId: number;
name?: string;
domainId?: number | null;
}) {
try {
if (domainId !== undefined && domainId !== null) {
const domain = await db.domain.findUnique({
where: {
id: domainId,
teamId: teamId,
},
select: { id: true },
});

if (!domain) {
throw new Error("DOMAIN_NOT_FOUND");
}
}

return await db.apiKey.update({
where: { id, teamId },
data: {
...(name !== undefined && { name }),
...(domainId !== undefined && { domainId }),
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against empty update payloads.

If name and domainId are both undefined, the update becomes a no-op and can surface a confusing error downstream. Add an early guard with a clear message.

🛠️ Proposed fix
   try {
+    if (name === undefined && domainId === undefined) {
+      throw new Error("NO_UPDATE_FIELDS");
+    }
+
     if (domainId !== undefined && domainId !== null) {
       const domain = await db.domain.findUnique({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function updateApiKey({
id,
teamId,
name,
domainId,
}: {
id: number;
teamId: number;
name?: string;
domainId?: number | null;
}) {
try {
if (domainId !== undefined && domainId !== null) {
const domain = await db.domain.findUnique({
where: {
id: domainId,
teamId: teamId,
},
select: { id: true },
});
if (!domain) {
throw new Error("DOMAIN_NOT_FOUND");
}
}
return await db.apiKey.update({
where: { id, teamId },
data: {
...(name !== undefined && { name }),
...(domainId !== undefined && { domainId }),
},
});
export async function updateApiKey({
id,
teamId,
name,
domainId,
}: {
id: number;
teamId: number;
name?: string;
domainId?: number | null;
}) {
try {
if (name === undefined && domainId === undefined) {
throw new Error("NO_UPDATE_FIELDS");
}
if (domainId !== undefined && domainId !== null) {
const domain = await db.domain.findUnique({
where: {
id: domainId,
teamId: teamId,
},
select: { id: true },
});
if (!domain) {
throw new Error("DOMAIN_NOT_FOUND");
}
}
return await db.apiKey.update({
where: { id, teamId },
data: {
...(name !== undefined && { name }),
...(domainId !== undefined && { domainId }),
},
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/server/service/api-service.ts` around lines 96 - 128, The
updateApiKey function can call db.apiKey.update with an empty data object when
both name and domainId are undefined; add an early guard in updateApiKey that
checks if name === undefined && domainId === undefined and throw a clear error
(e.g., "NO_UPDATE_PAYLOAD" or descriptive message) to avoid a no-op update,
while still allowing domainId === null to be used to clear the domain; update
the logic around db.apiKey.update (referencing updateApiKey and the
db.apiKey.update call) to only proceed when there is at least one field to
change.

Copy link
Member

@KMKoushik KMKoushik left a comment

Choose a reason for hiding this comment

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

LGTM

@KMKoushik KMKoushik merged commit b2ed09e into usesend:main Feb 25, 2026
1 of 2 checks passed
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.

2 participants