nextauthjs/next-auth · error

Account not found

Error message

Account not found

What it means

unlinkAccount looks up the AccountModel by the composite { provider, providerAccountId } and throws 'Account not found' if no row matches, because there is nothing to remove. This adapter requires the account to exist; some other adapters just resolve silently, so callers expecting a no-op will see this error.

Source

Thrown at packages/adapter-mikro-orm/src/index.ts:159

    // @ts-expect-error
    async linkAccount(data) {
      const em = await getEM()
      const user = await em.findOne(UserModel, { id: data.userId })
      if (!user) throw new Error("User not found")
      const account = new AccountModel()
      wrap(account).assign(data as object)
      user.accounts.add(account)
      await em.persistAndFlush(user)

      return wrap(account).toObject()
    },
    // @ts-expect-error
    async unlinkAccount(provider_providerAccountId) {
      const em = await getEM()
      const account = await em.findOne(AccountModel, {
        ...provider_providerAccountId,
      })
      if (!account) throw new Error("Account not found")
      await em.removeAndFlush(account)

      return wrap(account).toObject()
    },
    async getSessionAndUser(sessionToken) {
      const em = await getEM()
      const session = await em.findOne(
        SessionModel,
        { sessionToken },
        { populate: ["user"] }
      )
      if (!session || !session.user) return null

      return {
        user: wrap(session.user).toObject(),
        session: wrap(session).toObject(),
      }
    },

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Confirm the account row exists for that exact provider + providerAccountId pair before unlinking
  2. Guard with a lookup (getSessionAndUser/getUser) or catch the error and treat as already-unlinked
  3. Deduplicate the unlink action in UI/API to avoid double deletion
  4. Check that provider and providerAccountId values match what was stored at link time (casing, ids)

Example fix

// before
await adapter.unlinkAccount({ provider: 'github', providerAccountId })
// after
try {
  await adapter.unlinkAccount({ provider: 'github', providerAccountId })
} catch (e) {
  if (!(e as Error).message.includes('Account not found')) throw e
}
Defensive patterns

Strategy: try-catch

Validate before calling

const user = await adapter.getUserByAccount({ provider, providerAccountId })
if (!user) return // already unlinked
await adapter.unlinkAccount({ provider, providerAccountId })

Type guard

function hasCompositeKey(v: unknown): v is { provider: string; providerAccountId: string } {
  return typeof v === 'object' && v !== null &&
    typeof (v as any).provider === 'string' && typeof (v as any).providerAccountId === 'string'
}

Try / catch

try {
  await adapter.unlinkAccount({ provider, providerAccountId })
} catch (e) {
  if ((e as Error).message === 'Account not found') {
    return // idempotent: treat as already removed
  } else throw e
}

Prevention

When it happens

Trigger: Calling unlinkAccount with a provider/providerAccountId pair that was never stored, after the account row was already deleted, or with swapped/mismatched key names (e.g., passing providerAccountId where provider is expected).

Common situations: Double-unlink from duplicated UI actions; account removed by a cleanup job but still referenced in the client; wrong composite keys after renaming fields; manual adapter calls with data shaped differently than the DB.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28). Data as JSON: /api/errors/52965504585c2d1b. Report an issue: GitHub.