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

standardenv

v0.0.2

Published

Type-safe, structured environment variable parsing using [Standard Schema](https://standardschema.dev/) compatible validation libraries.

Readme

Standard Env

Type-safe, structured environment variable parsing using Standard Schema compatible validation libraries.

Features

  • 🔒 Type-safe - Full TypeScript support with automatic type inference
  • 🏗️ Structured config - Organize environment variables into nested objects
  • 🚀 Declarative API - Define your config structure in a single object
  • 🔄 Library agnostic - Works with any Standard Schema compatible library (arktype, zod, valibot, etc.)
  • 🎯 Default values & optional properties - Flexible configuration with type safety
  • 📦 Zero runtime dependencies - Lightweight and focused

Installation

bun add standardenv

# Also install your preferred validation library
bun add arktype  # or zod, valibot, etc.

Quick Start

import { envParse } from "standardenv";
import { type } from "arktype";

const config = envParse(process.env, {
  server: {
    port: {
      format: type('string.numeric.parse'),
      default: 3000,
      env: 'PORT'
    },
    nodeEnv: {
      format: type('"development" | "production" | "test"'),
      default: 'development',
      env: 'NODE_ENV'
    }
  },
  db: {
    url: {
      format: type('string'),
      env: 'DATABASE_URL'
    }
  }
});

// config.server.port is number (3000)
// config.server.nodeEnv is string ('development' | 'production' | 'test')
// config.db.url is string
// All fully typed with zero additional type definitions needed! ✨

Core Concepts

Declarative Configuration

Instead of separate schemas and defaults, define everything in one place:

const config = envParse(process.env, {
  // Nested structure for organization
  db: {
    url: {
      format: type('string'),           // Validation schema
      env: 'DATABASE_URL',             // Environment variable name
      // Required by default (no default provided)
    },
    maxConnections: {
      format: type('string.numeric.parse'),
      default: 10,                   // Default value
      env: 'DB_MAX_CONNECTIONS',
    }
  },

  server: {
    port: {
      format: type('string.numeric.parse'),
      default: 3000,
      env: 'PORT',
    },
    debug: {
      format: type('string').pipe(s => s === 'true'),
      default: false,
      env: 'DEBUG',
    }
  }
});

Property Configuration

Each property has these fields:

  • format: StandardSchema validator (required)
  • env: Environment variable name (required)
  • default: Default value (optional)
  • optional: If true, property can be undefined (optional)

Optional Properties

Mark properties as optional when they might not be set:

const config = envParse(process.env, {
  db: {
    url: {
      format: type('string'),
      env: 'DATABASE_URL', // Required - will throw if missing
    },
    redis: {
      url: {
        format: type('string'),
        env: 'REDIS_URL',
        optional: true, // Optional - will be undefined if not set
      }
    }
  },
  features: {
    analytics: {
      format: type('string').pipe(s => s === 'true'),
      env: 'ENABLE_ANALYTICS',
      optional: true, // Optional - will be undefined if not set
    }
  }
});

// TypeScript knows:
// config.db.url: string (required)
// config.db.redis.url: string | undefined (optional)
// config.features.analytics: boolean | undefined (optional)

Type Transformations

Environment variables are strings, but you often need other types:

Common Transformations with arktype

import { envParse } from "standardenv";
import { type } from "arktype";

const config = envParse(process.env, {
  server: {
    // String to number
    port: {
      format: type('string.numeric.parse'),
      default: 3000,
      env: 'PORT',
    },

    // String to boolean
    debug: {
      format: type('string').pipe(s => s === 'true' || s === '1'),
      default: false,
      env: 'DEBUG',
    },

    // String to array
    allowedOrigins: {
      format: type('string').pipe(s => s.split(',').map(origin => origin.trim())),
      default: ['http://localhost:3000'],
      env: 'ALLOWED_ORIGINS',
    },

    // String to JSON object
    featureFlags: {
      format: type('string').pipe((s): Record<string, boolean> => {
        try {
          return JSON.parse(s);
        } catch {
          return {};
        }
      }),
      default: {},
      env: 'FEATURE_FLAGS',
    }
  }
});

// Result types:
// config.server.port: number
// config.server.debug: boolean
// config.server.allowedOrigins: string[]
// config.server.featureFlags: Record<string, boolean>

Deeply Nested Configuration

Organize complex applications with deep nesting:

const config = envParse(process.env, {
  database: {
    primary: {
      url: {
        format: type('string'),
        env: 'DATABASE_URL',
      },
      maxConnections: {
        format: type('string.numeric.parse'),
        default: 20,
        env: 'DB_MAX_CONNECTIONS',
      }
    },
    cache: {
      redis: {
        url: {
          format: type('string'),
          env: 'REDIS_URL',
          optional: true,
        },
        ttl: {
          format: type('string.numeric.parse'),
          default: 3600,
          env: 'CACHE_TTL',
        }
      }
    }
  },

  auth: {
    jwt: {
      secret: {
        format: type('string'),
        env: 'JWT_SECRET',
      },
      expiresIn: {
        format: type('string'),
        default: '7d',
        env: 'JWT_EXPIRES_IN',
      }
    },
    oauth: {
      providers: {
        format: type('string').pipe(s => s.split(',')),
        default: ['google', 'github'],
        env: 'OAUTH_PROVIDERS',
      }
    }
  },

  logging: {
    level: {
      format: type('"debug" | "info" | "warn" | "error"'),
      default: 'info',
      env: 'LOG_LEVEL',
    },
    destination: {
      format: type('string'),
      default: 'console',
      env: 'LOG_DESTINATION',
      optional: true,
    }
  }
});

// Access with clean, organized structure:
// config.database.primary.url
// config.database.cache.redis.url
// config.auth.jwt.secret
// config.auth.oauth.providers
// config.logging.level

Library Compatibility

Works with any Standard Schema compatible library:

Arktype

import { type } from "arktype";

const config = envParse(process.env, {
  port: {
    format: type('string.numeric.parse'),
    env: 'PORT',
  }
});

Zod

import { z } from "zod";

const config = envParse(process.env, {
  port: {
    format: z.string().transform(Number),
    env: 'PORT',
  }
});

Valibot

import * as v from "valibot";

const config = envParse(process.env, {
  port: {
    format: v.pipe(v.string(), v.transform(Number)),
    env: 'PORT',
  }
});

Error Handling

Get clear error messages for validation failures:

import { EnvValidationError } from "standardenv";

try {
  const config = envParse(process.env, {
    port: {
      format: type('string.numeric.parse'),
      env: 'PORT', // Required, no default
    }
  });
} catch (error) {
  if (error instanceof EnvValidationError) {
    console.error("Environment validation failed:", error.message);
    console.error("Issues:", error.issues);
    console.error("Validator:", error.vendor);
  }
}

API Reference

envParse(env, config)

  • env: Record<string, string | undefined> - Environment variables (typically process.env)
  • config: ConfigDefinition - Declarative configuration structure

Returns the validated and typed configuration object with inferred types.

Configuration Properties

Each property in your config can have:

  • format (required): StandardSchema validator for the environment variable
  • env (required): Environment variable name to read from
  • default (optional): Default value (must be string - will be validated by format)
  • optional (optional): If true, property will be T | undefined instead of T

Error Classes

  • EnvValidationError - Thrown when environment variables fail validation
  • AsyncValidationError - Thrown when async validation is attempted (not supported)

Best Practices

✅ Do

// Organize related config into nested objects
const config = envParse(process.env, {
  database: {
    url: { format: type('string'), env: 'DATABASE_URL' },
    poolSize: { format: type('string.numeric.parse'), default: 10, env: 'DB_POOL_SIZE' }
  }
});

// Use meaningful default values
const config = envParse(process.env, {
  server: {
    port: { format: type('string.numeric.parse'), default: 3000, env: 'PORT' }
  }
});

// Mark truly optional config as optional
const config = envParse(process.env, {
  monitoring: {
    sentryDsn: { format: type('string'), env: 'SENTRY_DSN', optional: true }
  }
});

❌ Don't

// Don't use non-string types in format without transformation
const config = envParse(process.env, {
  port: { format: type('number'), env: 'PORT' } // ❌ Will fail - env vars are strings
});

// Don't put defaults that don't match the expected format
const config = envParse(process.env, {
  port: { format: type('string.numeric.parse'), default: '3000', env: 'PORT' } // ❌ Default should be number
});

Complete Example

import { envParse } from "standardenv";
import { type } from "arktype";

export const config = envParse(process.env, {
  app: {
    name: {
      format: type('string'),
      default: 'my-app',
      env: 'APP_NAME',
    },
    version: {
      format: type('string'),
      default: '1.0.0',
      env: 'APP_VERSION',
    }
  },

  server: {
    port: {
      format: type('string.numeric.parse'),
      default: 3000,
      env: 'PORT',
    },
    host: {
      format: type('string'),
      default: '0.0.0.0',
      env: 'HOST',
    },
    cors: {
      origins: {
        format: type('string').pipe(s => s.split(',').map(o => o.trim())),
        default: ['http://localhost:3000'],
        env: 'CORS_ORIGINS',
      }
    }
  },

  database: {
    url: {
      format: type('string'),
      env: 'DATABASE_URL',
    },
    ssl: {
      format: type('string').pipe(s => s === 'true'),
      default: false,
      env: 'DATABASE_SSL',
    }
  },

  auth: {
    clerk: {
      secretKey: {
        format: type('string'),
        env: 'CLERK_SECRET_KEY',
      },
      publishableKey: {
        format: type('string'),
        env: 'CLERK_PUBLISHABLE_KEY',
      }
    }
  },

  features: {
    analytics: {
      format: type('string').pipe(s => s === 'true'),
      default: false,
      env: 'ENABLE_ANALYTICS',
    },
    monitoring: {
      format: type('string').pipe(s => s === 'true'),
      default: false,
      env: 'ENABLE_MONITORING',
      optional: true,
    }
  }
});

// config is fully typed as:
// {
//   app: { name: string; version: string };
//   server: { port: number; host: string; cors: { origins: string[] } };
//   database: { url: string; ssl: boolean };
//   auth: { clerk: { secretKey: string; publishableKey: string } };
//   features: { analytics: boolean; monitoring?: boolean };
// }