Skip to content

fix(backend): expose externalAccountId on ExternalAccount resource#8982

Open
VihaanAgarwal wants to merge 2 commits into
clerk:mainfrom
VihaanAgarwal:fix/backend-external-account-id
Open

fix(backend): expose externalAccountId on ExternalAccount resource#8982
VihaanAgarwal wants to merge 2 commits into
clerk:mainfrom
VihaanAgarwal:fix/backend-external-account-id

Conversation

@VihaanAgarwal

@VihaanAgarwal VihaanAgarwal commented Jun 24, 2026

Copy link
Copy Markdown

Description

The backend ExternalAccount resource maps the API's id field, which holds an idn_-prefixed identification id, and never reads external_account_id (the eac_-prefixed resource id) from the response. The eac_ id is therefore unreachable from the SDK.

This bites anyone deleting an external account. users.deleteUserExternalAccount() builds its path from the eac_ id, so passing getUser().externalAccounts[0].id (an idn_ value) returns 404 external_account_not_found. The only workaround today is a raw fetch to read external_account_id off the JSON.

The raw user response already contains both ids on each external account:

{
  "id": "idn_...",
  "external_account_id": "eac_...",
  "identification_id": "idn_...",
  "provider": "oauth_google"
}

This PR maps external_account_id to a new externalAccountId property on ExternalAccount. id keeps its current value, so the change is additive and backwards compatible. Callers can now do:

const user = await clerk.users.getUser(userId);
await clerk.users.deleteUserExternalAccount({
  userId,
  externalAccountId: user.externalAccounts[0].externalAccountId,
});

Fixes #7936
Fixes #7584

How to test

ExternalAccount.fromJSON is covered by a new unit test in packages/backend/src/api/resources/__tests__/ExternalAccount.test.ts that asserts external_account_id maps to externalAccountId while id and identificationId stay on the idn_ value. The full @clerk/backend suite passes.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features
    • External account data now exposes an additional, provider resource identifier (externalAccountId) alongside the existing identification identifier.
  • Bug Fixes
    • External accounts retrieved from user lookups can now be deleted reliably using the correct account identifier (avoids not-found errors).
  • Tests
    • Added test coverage to verify identifier mapping across providers and ensure both identifiers are preserved.

The backend ExternalAccount mapped the API's `id` field, which holds an
`idn_` identification id, and never read `external_account_id` (the `eac_`
resource id). Passing `getUser().externalAccounts[].id` to
`deleteUserExternalAccount()` then returned a 404 because that method
expects the `eac_` id.

Map `external_account_id` to a new `externalAccountId` property so the
resource id is reachable without a raw API call. `id` keeps its existing
value to stay backwards compatible.
@changeset-bot

changeset-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f16e143

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@clerk/backend Patch
@clerk/astro Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/hono Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

@VihaanAgarwal is attempting to deploy a commit to the Clerk Production Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c96f7bb-5eb7-4b48-a791-004d35cd26ba

📥 Commits

Reviewing files that changed from the base of the PR and between 8097c8a and f16e143.

📒 Files selected for processing (4)
  • .changeset/external-account-id.md
  • packages/backend/src/api/resources/ExternalAccount.ts
  • packages/backend/src/api/resources/JSON.ts
  • packages/backend/src/api/resources/__tests__/ExternalAccount.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/external-account-id.md

📝 Walkthrough

Walkthrough

ExternalAccount now accepts and stores externalAccountId from API JSON, the backend JSON type includes external_account_id, and a new test verifies the mapping and ID preservation. A changeset documents the backend patch release note.

Changes

External account ID mapping

Layer / File(s) Summary
JSON contract and resource mapping
packages/backend/src/api/resources/JSON.ts, packages/backend/src/api/resources/ExternalAccount.ts
ExternalAccountJSON gains external_account_id, and ExternalAccount stores it as externalAccountId when built from JSON.
Mapping test and release note
packages/backend/src/api/resources/__tests__/ExternalAccount.test.ts, .changeset/external-account-id.md
A new test checks the external_account_id mapping and identification-id preservation, and the changeset records the backend patch note.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I hopped through IDs, both old and new,
The eac_ one now comes shining through.
idn_ stays put where it ought to be,
So delete can hop off happily.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main change: exposing externalAccountId on the backend ExternalAccount resource.
Linked Issues check ✅ Passed The PR satisfies both linked issues by exposing the eac_ external account ID additively and keeping id unchanged for compatibility.
Out of Scope Changes check ✅ Passed The changes stay focused on the ExternalAccount mapping, its JSON shape, tests, and release note, with no unrelated scope creep.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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


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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/backend/src/api/resources/ExternalAccount.ts (1)

10-19: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

New required constructor arg can break existing consumers

Adding externalAccountId as a required positional constructor parameter is not additive for callers that do new ExternalAccount(...); it introduces a source-level breaking change. Consider making it optional with a default, or moving to an options object constructor to preserve compatibility.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/backend/src/api/resources/ExternalAccount.ts` around lines 10 - 19,
The ExternalAccount constructor change is breaking existing callers because
externalAccountId is now a required positional argument. Update ExternalAccount
to preserve backward compatibility by making externalAccountId optional with a
safe default, or switch the constructor to accept an options object while
keeping existing new ExternalAccount(...) call sites working. Make sure the
change is localized in the ExternalAccount class and any related usages that
rely on its constructor signature.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/backend/src/api/resources/ExternalAccount.ts`:
- Around line 10-19: The ExternalAccount constructor change is breaking existing
callers because externalAccountId is now a required positional argument. Update
ExternalAccount to preserve backward compatibility by making externalAccountId
optional with a safe default, or switch the constructor to accept an options
object while keeping existing new ExternalAccount(...) call sites working. Make
sure the change is localized in the ExternalAccount class and any related usages
that rely on its constructor signature.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: b7af75fd-ef91-4426-a550-a55759eab66f

📥 Commits

Reviewing files that changed from the base of the PR and between f0b0773 and 8097c8a.

📒 Files selected for processing (4)
  • .changeset/external-account-id.md
  • packages/backend/src/api/resources/ExternalAccount.ts
  • packages/backend/src/api/resources/JSON.ts
  • packages/backend/src/api/resources/__tests__/ExternalAccount.test.ts

@jacekradko

Copy link
Copy Markdown
Member

Thanks for this, @VihaanAgarwal.

One behavior to flag: the Backend API only includes external_account_id on Google and Facebook accounts, where id is the idn_ value. For other providers id is already the eac_ id and there's no external_account_id. So it works best as an optional field, and the id to delete with is externalAccountId ?? id (using externalAccountId alone would be undefined for, say, a GitHub account).

I opened #8995 building on your change with those tweaks: optional field, ?? id guidance, and a fixture covering both shapes. It links the same issues, so #7936 and #7584 close when it merges.

Only Google and Facebook accounts return external_account_id, so model it
as an optional field at the end of the constructor instead of a required
one, keeping it backwards-compatible. Cover the non-Google shape in the
test and note externalAccountId ?? id in the changeset.
@VihaanAgarwal

Copy link
Copy Markdown
Author

Thanks for the context, that clears it up. I didn't realize the Backend API only returns external_account_id for Google and Facebook.

I pushed an update here so it matches what you described: externalAccountId is optional now and sits at the end of the constructor to stay backwards-compatible, the test covers the non-Google shape where id is already the eac_ value, and the changeset calls out externalAccountId ?? id.

Happy to go with whichever is less work for you. Fine to close this in favor of #8995 if you'd rather keep building on that one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants