npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@forgehustle/better-auth-soft-deletion

v1.1.1

Published

Production-grade soft deletion plugin for Better Auth

Readme

@forgehustle/better-auth-soft-deletion

Soft deletion plugin for Better Auth with:

  • user soft delete (status = "deleted", deletedAt)
  • sign-in blocking for deleted users
  • optional re-registration blocking during retention window
  • account restore endpoint
  • immediate revocation of all user sessions on delete

What this plugin does

When a user deletes their account:

  1. User row is kept (not hard-deleted)
  2. status becomes "deleted" and deletedAt is set
  3. All active sessions are revoked
  4. (Optional) Email is blocked from re-registering for retentionDays

When a deleted user tries to sign in:

  • plugin returns 403 FORBIDDEN with code ACCOUNT_DELETED

When restore is called with valid email/password:

  • user status is set back to "active"
  • deletedAt is cleared
  • blocked identifier entry is removed

Restore endpoint exposed by this plugin:

  • POST /soft-deletion/restore

Installation

npm install @forgehustle/better-auth-soft-deletion
# or
bun add @forgehustle/better-auth-soft-deletion

Server setup (Better Auth)

import { betterAuth } from "better-auth";
import { softDeletion } from "@forgehustle/better-auth-soft-deletion";

export const auth = betterAuth({
  // your adapter
  // database: drizzleAdapter(...)
  plugins: [
    softDeletion({
      retentionDays: 30,         // default: 30
      blockReRegistration: true, // default: true
    }),
  ],
  user: {
    deleteUser: {
      enabled: true,
    },
  },
});

Important:

  • deleteUser must be enabled in Better Auth config.
  • your client should send password confirmation to delete account (password string).

Client setup

import { createAuthClient } from "better-auth/react";
import { softDeletionClient } from "@forgehustle/better-auth-soft-deletion/client";

export const authClient = createAuthClient({
  baseURL: "http://localhost:5000/auth", // adjust for your app
  plugins: [softDeletionClient()],
});

Important:

  • Prefer plugin actions from authClient for restore operations.
  • Avoid manual fetch calls to plugin endpoints in React apps.

Usage examples

Delete account (requires password confirmation)

await authClient.deleteUser({
  password: "CurrentPassword123!",
});

If password is missing/invalid shape, Better Auth will reject the request.

Restore account (recommended)

const { data, error } = await authClient.restoreAccount({
  email: "[email protected]",
  password: "CurrentPassword123!",
});

if (error) {
  // handle by error.code
  console.error(error.code, error.message);
} else {
  console.log(data.message); // "Account restored successfully."
}

Options

type SoftDeletionOptions = {
  retentionDays?: number;         // default: 30
  blockReRegistration?: boolean;  // default: true
  restoreRateLimit?: (
    params: { email: string; context: unknown }
  ) => boolean | { allowed: boolean; code?: string; message?: string; status?: number };
};

Added schema

This plugin extends Better Auth schema with:

  • user.status (string, default: "active")
  • user.deletedAt (date | null)
  • blockedIdentifier model:
    • identifierHash
    • type
    • expiresAt

Run your Better Auth/ORM migration flow after enabling plugin schema changes.


Error codes you should handle on client

  • ACCOUNT_DELETED (403): deleted user attempted sign-in
  • EMAIL_BLOCKED (403): re-registration blocked during retention window
  • RESTORE_INPUT_REQUIRED (400): email/password missing on restore
  • ACCOUNT_NOT_DELETED (400): restore requested for active account
  • NO_PASSWORD_CREDENTIAL (400): credential password not available (for example OAuth-only account)
  • AUTH_INVALID_CREDENTIALS (401): invalid email/password on restore

Security notes

  • Always send restore credentials in POST JSON body.
  • Do not put email/password in query string.
  • Revoke sessions on delete is already built in:
    • uses Better Auth internal adapter deleteSessions(userId) when available
    • fallback removes session model rows by userId
  • If your app uses Better Auth session.cookieCache, revoked sessions may appear active until cache refresh on other devices. For strict behavior, disable cookie cache or force disableCookieCache: true on session refresh checks.

Troubleshooting

  • Password confirmation is required to delete your account:

    • ensure deleteUser({ password: "..." }) is sent with password as string.
  • VALIDATION_ERROR [body.password] expected string, received object:

    • your payload shape is wrong. Send a plain string value for password.
  • RESTORE_INPUT_REQUIRED:

    • restore request is missing email or password.
    • verify request body is JSON and keys are exact: email, password.
  • Deleted user can still access protected routes on another browser:

    • this is usually session cookie cache behavior, not DB/Redis session persistence.
    • disable Better Auth session cookie cache for strict immediate invalidation.

License

MIT