[GH-ISSUE #1766] ERROR [Better Auth] Couldn't read your auth config. Error: Cannot find module '$env/static/private' Error is the same for any $` imported library #8907

Closed
opened 2026-04-13 04:09:19 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @maumercado on GitHub (Mar 11, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/1766

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Ex:

  • Create a sveltekit@5 project with TS
  •  Create an auth.ts file inside lib using $env/dynamic|static/ or any other $lib/xxx
  • Call @better-auth/cli generate

Current vs. Expected behavior

Following the steps in the basic installation and sveltekit guide I get an error trying to execute @better-auth/cli generate -c ./src/lib/auth.ts

ERROR [Better Auth]: [#better-auth]: Couldn't read your auth config. Error: Cannot find module '$env/static/private'
This error is the same for any $ imported library

What version of Better Auth are you using?

1.2.3

Provide environment information

- OS MacOS Sequoia 15.3.1
- Sveltejs/kit@2.19 and svelte 5.22
- Node 22.14

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

// // auth.ts
import {
  POSTGRES_USER,
  POSTGRES_PASSWORD,
  POSTGRES_DB,
  POSTGRES_HOST,
  POSTGRES_PORT,
  EMAIL_FROM,
  EMAIL_REPLY_TO,
  RESEND_API_KEY,
  GOOGLE_CLIENT_ID,
  GOOGLE_CLIENT_SECRET,
  APPLE_CLIENT_ID,
  APPLE_CLIENT_SECRET,
  APPLE_APP_BUNDLE_ID,
  JWT_SECRET,
  SECRET_CF_TURNSTILE_SECRET
} from '$env/static/private';
import { PUBLIC_APP_NAME, PUBLIC_APP_URL, PUBLIC_DOMAIN } from '$env/static/public';
import pg from 'pg';
import { betterAuth } from 'better-auth';
import { passkey } from 'better-auth/plugins/passkey';
import { captcha, twoFactor } from 'better-auth/plugins';
import { bearer } from 'better-auth/plugins';
import { multiSession } from 'better-auth/plugins';
import { emailOTP } from 'better-auth/plugins';
import { magicLink } from 'better-auth/plugins';
import { setEmailFrom, sendEmail, EmailType } from '$lib/server/email';
import { EmailTemplateType, getEmailTemplate } from '$lib/server/email/templates';

const { Pool } = pg;

// Define types for plugin parameters
type OTPUser = { email: string };
type VerificationType = 'sign-in' | 'email-verification' | 'forget-password';

// Create a database pool for sharing with the auth modules
const pool = new Pool({
  host: POSTGRES_HOST,
  port: +POSTGRES_PORT,
  user: POSTGRES_USER,
  password: POSTGRES_PASSWORD,
  database: POSTGRES_DB
});

export const auth = betterAuth({
  // Base configuration
  database: 'postgres',
  schema: 'auth',
  usePluralizedTableNames: true,
  tablePrefix: '',
  adminRole: 'service_role',
  authenticatedRole: 'authenticated',
  anonRole: 'anon',
  jwtIssuer: 'reporter-app',
  sessionExpiryInSeconds: 604800,
  accessTokenExpiryInSeconds: 3600,
  refreshTokenExpiryInSeconds: 604800,
  minPasswordLength: 8,
  appName: 'Reporter App', // Used as issuer for 2FA
  advanced: {
    generateId: false
  },

  user: {
    additionalFields: {
      firstName: {
        type: 'string',
        required: true
      },
      lastName: {
        type: 'string',
        required: true
      },
      phone: {
        type: 'string',
        required: false
      },
      roles: {
        type: 'string[]',
        required: false,
        default: ['user']
      }
    }
  },

  // Email and password authentication
  emailAndPassword: {
    enabled: true,
    autoSignIn: false
  },

  // Email verification
  emailVerification: {
    sendOnSignUp: true,
    requireEmailVerification: true,
    autoSignInAfterVerification: true,
    expiresIn: 300, // 5 minutes
    sendVerificationEmail: async ({ user, url, token }) => {
      const subject = 'Your Reporter App verification code';
      const html = getEmailTemplate({
        type: EmailTemplateType.VERIFICATION,
        verificationUrl: url
      });

      await sendEmail({
        to: user.email,
        subject,
        html
      });
    }
  },

  // Password reset
  passwordReset: {
    enabled: true,
    expiresIn: 300 // 5 minutes
  },

  // OAuth providers
  socialProviders: {
    google: {
      clientId: GOOGLE_CLIENT_ID!,
      clientSecret: GOOGLE_CLIENT_SECRET!
    },
    apple: {
      clientId: APPLE_CLIENT_ID!,
      clientSecret: APPLE_CLIENT_SECRET!,
      appBundleIdentifier: APPLE_APP_BUNDLE_ID!
    }
  },

  // Email configuration
  email: {
    provider: 'resend',
    from: EMAIL_FROM!,
    replyTo: EMAIL_REPLY_TO,
    providerApiKey: RESEND_API_KEY!
  },

  // Plugins configuration with proper options based on documentation
  plugins: [
    bearer(),
    multiSession(),
    // Passkey plugin for passwordless WebAuthn
    passkey({
      rpID: PUBLIC_DOMAIN || 'localhost',
      rpName: PUBLIC_APP_NAME || 'Reporter',
      origin: PUBLIC_APP_URL || 'http://localhost:5173'
    }),

    // captcha({
    //   provider: 'cloudflare-turnstile', // or "google-recaptcha"
    //   secretKey: SECRET_CF_TURNSTILE_SECRET!
    // }),

    // Two-factor authentication (TOTP) with type assertion to fix compatibility issue
    twoFactor({
      skipVerificationOnEnable: false,
      totpOptions: {
        digits: 6,
        period: 30
      },
      otpOptions: {
        period: 30,
        // Implement sendOTP using the existing email service and templates
        async sendOTP({ user, otp }: { user: OTPUser; otp: string }) {
          const subject = 'Your Reporter App verification code';
          const html = getEmailTemplate({
            type: EmailTemplateType.TWO_FACTOR,
            otp,
            verificationType: 'sign-in',
            expiresIn: '30 seconds'
          });

          await sendEmail({
            to: user.email,
            subject,
            html,
            from: setEmailFrom(EmailType.Welcome)
          });

          // Return void as required by the type
        }
      }
    }),

    // Email OTP for verification codes
    emailOTP({
      async sendVerificationOTP({
        email,
        otp,
        type
      }: {
        email: string;
        otp: string;
        type: VerificationType;
      }) {
        // Get the appropriate email subject based on verification type
        const typeLabels = {
          'sign-in': 'Sign-in',
          'email-verification': 'Email Verification',
          'forget-password': 'Password Reset'
        };

        const subject = `Your Reporter App ${typeLabels[type] || type} code`;
        const html = getEmailTemplate({
          type: EmailTemplateType.TWO_FACTOR,
          otp,
          verificationType: type,
          expiresIn: type === 'forget-password' ? '5 minutes' : '5 minutes'
        });

        await sendEmail({
          to: email,
          subject,
          html,
          from: setEmailFrom(EmailType.Welcome)
        });

        // Return void as required by the type
      }
    }),

    // Magic link (passwordless email link) with required sendMagicLink property
    magicLink({
      // Required implementation of sendMagicLink as per documentation
      async sendMagicLink({ email, url }: { email: string; url: string }) {
        // Implementation uses the email service configured in the base auth
        const subject = 'Sign in to Reporter App';
        const html = getEmailTemplate({
          type: EmailTemplateType.MAGIC_LINK,
          url,
          appName: 'Reporter App'
        });

        await sendEmail({
          to: email,
          subject,
          html,
          from: setEmailFrom(EmailType.Welcome)
        });

        // Return void as required by the type
      }
    })
  ]
});

Additional context

No response

Originally created by @maumercado on GitHub (Mar 11, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/1766 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Ex: - Create a sveltekit@5 project with TS -  Create an auth.ts file inside lib using $env/dynamic|static/ or any other $lib/xxx - Call @better-auth/cli generate ### Current vs. Expected behavior Following the steps in the basic installation and sveltekit guide I get an error trying to execute @better-auth/cli generate -c ./src/lib/auth.ts `ERROR [Better Auth]: [#better-auth]: Couldn't read your auth config. Error: Cannot find module '$env/static/private'` This error is the same for any `$` imported library ### What version of Better Auth are you using? 1.2.3 ### Provide environment information ```bash - OS MacOS Sequoia 15.3.1 - Sveltejs/kit@2.19 and svelte 5.22 - Node 22.14 ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript // // auth.ts import { POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, POSTGRES_HOST, POSTGRES_PORT, EMAIL_FROM, EMAIL_REPLY_TO, RESEND_API_KEY, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, APPLE_CLIENT_ID, APPLE_CLIENT_SECRET, APPLE_APP_BUNDLE_ID, JWT_SECRET, SECRET_CF_TURNSTILE_SECRET } from '$env/static/private'; import { PUBLIC_APP_NAME, PUBLIC_APP_URL, PUBLIC_DOMAIN } from '$env/static/public'; import pg from 'pg'; import { betterAuth } from 'better-auth'; import { passkey } from 'better-auth/plugins/passkey'; import { captcha, twoFactor } from 'better-auth/plugins'; import { bearer } from 'better-auth/plugins'; import { multiSession } from 'better-auth/plugins'; import { emailOTP } from 'better-auth/plugins'; import { magicLink } from 'better-auth/plugins'; import { setEmailFrom, sendEmail, EmailType } from '$lib/server/email'; import { EmailTemplateType, getEmailTemplate } from '$lib/server/email/templates'; const { Pool } = pg; // Define types for plugin parameters type OTPUser = { email: string }; type VerificationType = 'sign-in' | 'email-verification' | 'forget-password'; // Create a database pool for sharing with the auth modules const pool = new Pool({ host: POSTGRES_HOST, port: +POSTGRES_PORT, user: POSTGRES_USER, password: POSTGRES_PASSWORD, database: POSTGRES_DB }); export const auth = betterAuth({ // Base configuration database: 'postgres', schema: 'auth', usePluralizedTableNames: true, tablePrefix: '', adminRole: 'service_role', authenticatedRole: 'authenticated', anonRole: 'anon', jwtIssuer: 'reporter-app', sessionExpiryInSeconds: 604800, accessTokenExpiryInSeconds: 3600, refreshTokenExpiryInSeconds: 604800, minPasswordLength: 8, appName: 'Reporter App', // Used as issuer for 2FA advanced: { generateId: false }, user: { additionalFields: { firstName: { type: 'string', required: true }, lastName: { type: 'string', required: true }, phone: { type: 'string', required: false }, roles: { type: 'string[]', required: false, default: ['user'] } } }, // Email and password authentication emailAndPassword: { enabled: true, autoSignIn: false }, // Email verification emailVerification: { sendOnSignUp: true, requireEmailVerification: true, autoSignInAfterVerification: true, expiresIn: 300, // 5 minutes sendVerificationEmail: async ({ user, url, token }) => { const subject = 'Your Reporter App verification code'; const html = getEmailTemplate({ type: EmailTemplateType.VERIFICATION, verificationUrl: url }); await sendEmail({ to: user.email, subject, html }); } }, // Password reset passwordReset: { enabled: true, expiresIn: 300 // 5 minutes }, // OAuth providers socialProviders: { google: { clientId: GOOGLE_CLIENT_ID!, clientSecret: GOOGLE_CLIENT_SECRET! }, apple: { clientId: APPLE_CLIENT_ID!, clientSecret: APPLE_CLIENT_SECRET!, appBundleIdentifier: APPLE_APP_BUNDLE_ID! } }, // Email configuration email: { provider: 'resend', from: EMAIL_FROM!, replyTo: EMAIL_REPLY_TO, providerApiKey: RESEND_API_KEY! }, // Plugins configuration with proper options based on documentation plugins: [ bearer(), multiSession(), // Passkey plugin for passwordless WebAuthn passkey({ rpID: PUBLIC_DOMAIN || 'localhost', rpName: PUBLIC_APP_NAME || 'Reporter', origin: PUBLIC_APP_URL || 'http://localhost:5173' }), // captcha({ // provider: 'cloudflare-turnstile', // or "google-recaptcha" // secretKey: SECRET_CF_TURNSTILE_SECRET! // }), // Two-factor authentication (TOTP) with type assertion to fix compatibility issue twoFactor({ skipVerificationOnEnable: false, totpOptions: { digits: 6, period: 30 }, otpOptions: { period: 30, // Implement sendOTP using the existing email service and templates async sendOTP({ user, otp }: { user: OTPUser; otp: string }) { const subject = 'Your Reporter App verification code'; const html = getEmailTemplate({ type: EmailTemplateType.TWO_FACTOR, otp, verificationType: 'sign-in', expiresIn: '30 seconds' }); await sendEmail({ to: user.email, subject, html, from: setEmailFrom(EmailType.Welcome) }); // Return void as required by the type } } }), // Email OTP for verification codes emailOTP({ async sendVerificationOTP({ email, otp, type }: { email: string; otp: string; type: VerificationType; }) { // Get the appropriate email subject based on verification type const typeLabels = { 'sign-in': 'Sign-in', 'email-verification': 'Email Verification', 'forget-password': 'Password Reset' }; const subject = `Your Reporter App ${typeLabels[type] || type} code`; const html = getEmailTemplate({ type: EmailTemplateType.TWO_FACTOR, otp, verificationType: type, expiresIn: type === 'forget-password' ? '5 minutes' : '5 minutes' }); await sendEmail({ to: email, subject, html, from: setEmailFrom(EmailType.Welcome) }); // Return void as required by the type } }), // Magic link (passwordless email link) with required sendMagicLink property magicLink({ // Required implementation of sendMagicLink as per documentation async sendMagicLink({ email, url }: { email: string; url: string }) { // Implementation uses the email service configured in the base auth const subject = 'Sign in to Reporter App'; const html = getEmailTemplate({ type: EmailTemplateType.MAGIC_LINK, url, appName: 'Reporter App' }); await sendEmail({ to: email, subject, html, from: setEmailFrom(EmailType.Welcome) }); // Return void as required by the type } }) ] }); ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-13 04:09:19 -05:00
Author
Owner

@muskan-focusofthq commented on GitHub (Mar 12, 2025):

@muskan-focusofthq completely different error, my issue seems to be related to the build process that should occur when reading the auth.ts file which includes some modules that are automatically injected by vite, so mostly looking for pointers on how to handle that, as you see my error is not that it cannot read the auth.ts it can, it simply cannot find the module $env ... or $lib/ anything

Thanks tho'

????

<!-- gh-comment-id:2719307919 --> @muskan-focusofthq commented on GitHub (Mar 12, 2025): > [@muskan-focusofthq](https://github.com/muskan-focusofthq) completely different error, my issue seems to be related to the build process that should occur when reading the auth.ts file which includes some modules that are automatically injected by vite, so mostly looking for pointers on how to handle that, as you see my error is not that it cannot read the auth.ts it can, it simply cannot find the module $env ... or $lib/ anything > > Thanks tho' ????
Author
Owner

@moshetanzer commented on GitHub (Mar 24, 2025):

@maumercado am not proficient in svelte, however aren't those only available from build onwards? You probably should add env directly using procces.env (if that is possible in svelte) since generate is not running built app.

<!-- gh-comment-id:2747102006 --> @moshetanzer commented on GitHub (Mar 24, 2025): @maumercado am not proficient in svelte, however aren't those only available from build onwards? You probably should add env directly using procces.env (if that is possible in svelte) since generate is not running built app.
Author
Owner

@maumercado commented on GitHub (Mar 25, 2025):

@moshetanzer thank you, this gives me an idea, of course it has to do with the build step, so might as well just attempt to execute the script pointing to the built output of svelte, would probably have to modify the config just for this, but I'll see what I can do!

Thank you @moshetanzer that was helpful

<!-- gh-comment-id:2749753749 --> @maumercado commented on GitHub (Mar 25, 2025): @moshetanzer thank you, this gives me an idea, of course it has to do with the build step, so might as well just attempt to execute the script pointing to the built output of svelte, would probably have to modify the config just for this, but I'll see what I can do! Thank you @moshetanzer that was helpful
Author
Owner

@jerriclynsjohn commented on GitHub (Apr 13, 2025):

I think the ideal step is for the CLI to figure out the framework & aliases mentioned in the root of the project, and then do path resolution just the way the framework would do. Right now, I'm able to make this work without using the alias.

<!-- gh-comment-id:2800044699 --> @jerriclynsjohn commented on GitHub (Apr 13, 2025): I think the ideal step is for the CLI to figure out the framework & aliases mentioned in the root of the project, and then do path resolution just the way the framework would do. Right now, I'm able to make this work without using the alias.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#8907