Skip to content

createPlugin

Factory function for building configurable plugins with automatic type inference.

Signature

ts
function createPlugin<TBase, TWhen, TEither, TOAuthKey, TOptions>(
  config: DefinePluginConfig<TBase, TWhen, TEither, TOAuthKey, TOptions>
): <const T>(options?: T) => NevrEnvPlugin<ResolvedSchema<TBase, TWhen, TEither, TOAuthKey, T>>

All type parameters are inferred from the config object — no explicit generics needed.

Usage

ts
import { createPlugin } from "nevr-env";
import { z } from "zod";

export const myService = createPlugin({
  id: "my-service",
  name: "My Service",

  base: {
    MY_API_KEY: z.string().min(1),
  },

  when: {
    debug: {
      MY_DEBUG: z.coerce.boolean().default(false),
    },
  },
});

// Use in createEnv
createEnv({
  plugins: [myService({ debug: true })],
  runtimeEnv: process.env,
});

Config

PropertyTypeRequiredDescription
idstringYesUnique identifier
namestringYesDisplay name
baseRecord<string, ZodType>YesAlways-present schemas
whenRecord<string, Record<string, ZodType>>Additive conditional schemas
eitherRecord<string, { true: ..., false: ... }>Mutually exclusive schemas
oauthProvidersstringOption key for OAuth providers array
$optionsTOptionsPhantom type for extra options
prefixstringVariable prefix
sensitiveboolean | Record<string, boolean>Mark secrets
runtimeSchema(opts, schema) => voidRuntime schema adjustments
dynamicSchema(opts) => Record<string, ZodType>Dynamic schema builder
cli(opts) => CliConfigCLI prompts
hooks(opts) => HooksLifecycle hooks
discover(opts) => () => Promise<Results>Auto-discovery
autoDiscoverboolean | (opts) => booleanEnable/disable auto-discovery

Examples

Basic Plugin

ts
export const redis = createPlugin({
  id: "redis",
  name: "Redis",
  base: {
    REDIS_URL: z.string().url(),
  },
});

With Additive Flags (when)

ts
export const postgres = createPlugin({
  id: "postgres",
  name: "PostgreSQL",
  sensitive: true,

  base: {
    DATABASE_URL: z.string().url(),
  },

  when: {
    directUrl: {
      DIRECT_URL: z.string().url(),
    },
    pool: {
      DATABASE_POOL_SIZE: z.coerce.number().default(10),
    },
  },
});

postgres()                    // → { DATABASE_URL }
postgres({ directUrl: true }) // → { DATABASE_URL, DIRECT_URL }

With Mutually Exclusive Flags (either)

ts
export const openai = createPlugin({
  id: "openai",
  name: "OpenAI",

  base: {},

  either: {
    azure: {
      true: { AZURE_OPENAI_ENDPOINT: z.string().url() },
      false: { OPENAI_API_KEY: z.string().startsWith("sk-") },
    },
  },
});

openai()                // → { OPENAI_API_KEY }
openai({ azure: true }) // → { AZURE_OPENAI_ENDPOINT }

With Extra Options ($options)

ts
export const stripe = createPlugin({
  id: "stripe",
  name: "Stripe",

  $options: {} as { testMode?: boolean; variableNames?: { secretKey?: string } },

  base: {
    STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
  },

  runtimeSchema: (opts, schema) => {
    if (opts.variableNames?.secretKey) {
      schema[opts.variableNames.secretKey] = schema.STRIPE_SECRET_KEY;
      delete schema.STRIPE_SECRET_KEY;
    }
  },
});

With CLI Prompts

ts
export const stripe = createPlugin({
  id: "stripe",
  name: "Stripe",

  base: {
    STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
  },

  cli: () => ({
    docs: "https://dashboard.stripe.com/apikeys",
    prompts: {
      STRIPE_SECRET_KEY: {
        message: "Enter your Stripe secret key",
        type: "password",
      },
    },
  }),
});

With Discovery

ts
export const postgres = createPlugin({
  id: "postgres",
  name: "PostgreSQL",

  base: {
    DATABASE_URL: z.string().url(),
  },

  discover: () => async () => {
    try {
      const { execSync } = await import("child_process");
      const output = execSync('docker ps', { encoding: "utf8" });
      if (output.includes("postgres")) {
        return {
          DATABASE_URL: {
            value: "postgres://localhost:5432/postgres",
            source: "docker",
            description: "Found Postgres container",
            confidence: 0.9,
          },
        };
      }
    } catch {}
    return {};
  },
});

With extend (User-Side)

ts
const env = createEnv({
  plugins: [
    postgres({
      pool: true,
      extend: {
        DATABASE_MAX_RETRIES: z.coerce.number().default(3),
      },
    }),
  ],
});

Type Inference

Full TypeScript inference — no manual type annotations needed:

ts
// All keys are inferred from the declarative config
const plugin = postgres({ directUrl: true, pool: true });
// plugin.schema keys: DATABASE_URL, DIRECT_URL, DATABASE_POOL_SIZE, ...

const env = createEnv({
  plugins: [plugin],
  runtimeEnv: process.env,
});

env.DATABASE_URL; // string
env.DIRECT_URL;   // string (only present because directUrl: true)

See Also

Released under the MIT License.