CodexaCodexa

Storage Config

Build a fully typed storage config from environment variables, narrowed to exactly the fields the chosen provider needs.

buildStorageConfig(env) turns a plain object of environment variables into the StorageConfig that createStorageManager expects. It is a discriminated union over one field, STORAGE_PROVIDER, and TypeScript enforces every other required field once that one is set.

import { buildStorageConfig } from '@codexa/core/config';

const cfg = buildStorageConfig({
  STORAGE_PROVIDER: 's3',
  S3_BUCKET: env.get('S3_BUCKET')!,
  S3_REGION: env.get('S3_REGION')!,
  S3_ACCESS_KEY: env.get('S3_ACCESS_KEY')!,
  S3_SECRET_KEY: env.get('S3_SECRET_KEY')!,
});

cfg.s3.bucket; // no type guard needed, TypeScript already knows this is the S3 branch

Because STORAGE_PROVIDER: 's3' narrows the whole object, leaving out S3_BUCKET here is a compile error, not a runtime surprise the first time an upload is attempted.

The four providers

Each provider has its own required and optional environment shape.

// Cloudinary
buildStorageConfig({
  STORAGE_PROVIDER: 'cloudinary',
  CLOUDINARY_CLOUD_NAME: env.get('CLOUDINARY_CLOUD_NAME')!,
  CLOUDINARY_API_KEY: env.get('CLOUDINARY_API_KEY')!,
  CLOUDINARY_API_SECRET: env.get('CLOUDINARY_API_SECRET')!,
});

// ImageKit
buildStorageConfig({
  STORAGE_PROVIDER: 'imagekit',
  IMAGEKIT_PUBLIC_KEY: env.get('IMAGEKIT_PUBLIC_KEY')!,
  IMAGEKIT_PRIVATE_KEY: env.get('IMAGEKIT_PRIVATE_KEY')!,
  IMAGEKIT_URL_ENDPOINT: env.get('IMAGEKIT_URL_ENDPOINT')!, // e.g. https://ik.imagekit.io/myapp
});

// Local filesystem
buildStorageConfig({
  STORAGE_PROVIDER: 'local',
  LOCAL_STORAGE_DIR: env.get('LOCAL_STORAGE_DIR'), // optional, defaults to './uploads'
  LOCAL_BASE_URL: env.get('LOCAL_BASE_URL'),        // optional
});
ProviderRequiredOptional
s3S3_BUCKET, S3_REGION, S3_ACCESS_KEY, S3_SECRET_KEYS3_ENDPOINT (S3-compatible services like MinIO or Cloudflare R2), S3_CDN_BASE_URL
cloudinaryCLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRETnone
imagekitIMAGEKIT_PUBLIC_KEY, IMAGEKIT_PRIVATE_KEY, IMAGEKIT_URL_ENDPOINTnone
localnoneLOCAL_STORAGE_DIR (default './uploads'), LOCAL_BASE_URL

Casting from a raw environment object

Deno.env.toObject() returns a plain Record<string, string>, which TypeScript cannot check against the StorageEnv union on its own, since it does not know which provider's fields are actually present. Cast to the specific provider type you know is active at runtime.

import { buildStorageConfig, type S3Env } from '@codexa/core/config';

const cfg = buildStorageConfig(Deno.env.toObject() as S3Env);

Casting bypasses the compile-time field checking that makes buildStorageConfig worth using in the first place. Prefer building the object explicitly, one field per environment variable, as in the examples above. Reach for casting only when the environment shape is controlled entirely by your own deployment and you are confident every required field is actually set.

Feeding the result to a storage manager

buildStorageConfig's return value is exactly what createStorageManager takes as its config.

import { createStorageManager } from '@codexa/core/storage';

const storage = createStorageManager(
  buildStorageConfig({
    STORAGE_PROVIDER: 'local',
    LOCAL_STORAGE_DIR: './uploads',
    LOCAL_BASE_URL: 'http://localhost:8000/uploads',
  }),
);

await storage.upload(fileBytes, { folder: 'avatars', contentType: 'image/png' });

On this page