Configuration
You can read runtime configuration values in a TypeScript component with the wasi:cli/environment interface. Configuration values can be supplied from inline key-value pairs, Kubernetes ConfigMaps, and Kubernetes Secrets. All arrive through the same interface regardless of source.
This guide walks through adding wasi:cli/environment to the wasmCloud http-service-hono template and shows how to provide values via a Kubernetes workload manifest.
Overview
wasi:cli/environment is part of the standard WASI P2 suite and is always available in a wasmCloud host. It exposes a getEnvironment function that returns all configuration key-value pairs available to the component. In production, values come from localResources.environment in a Workload or WorkloadDeployment manifest, which accepts inline key-value pairs, Kubernetes ConfigMaps, and Kubernetes Secrets.
The Secrets and Configuration Management page in the Operator Manual describes how the Kubernetes operator injects values via wasi:cli/environment. This guide covers the component author's side: how to declare and read those values in TypeScript.
Our changes focus on two files:
wit/world.wit— Declare thewasi:cli/environmentimportsrc/component.ts(or your main handler file) — Read configuration values in application code
No changes are needed to rolldown.config.mjs: the default external: /wasi:.*/ pattern already covers wasi:cli imports.
Step 1: Declare the WIT import in wit/world.wit
Every capability your component uses must be declared in its WIT world. Open wit/world.wit and add an import line for the cli environment interface.
package wasmcloud:templates@0.1.0;
world typescript-http-service-hono {
import wasi:cli/environment@0.2.3;
export wasi:http/incoming-handler@0.2.6;
}What this interface provides
wasi:cli/environment exposes one function for reading configuration:
getEnvironment()— Returns all configuration key-value pairs as an array of[string, string]tuples.
After adding the import and running npm run build, JCO generates a typed definition for the interface in generated/types/interfaces/wasi-cli-environment.d.ts:
declare module 'wasi:cli/environment@0.2.3' {
export function getEnvironment(): Array<[string, string]>;
export function getArguments(): Array<string>;
export function initialCwd(): string | undefined;
}How dependency resolution works
When you run npm run build, the build pipeline calls wkg wit fetch as a setup step. This resolves the WIT package reference and downloads the definition into wit/deps/ automatically. You don't need to manually download any WIT files.
Step 2: Import and use the interface in TypeScript
With the WIT world updated, you can import getEnvironment in your TypeScript code. Because JCO generates types for wasi:cli/environment, no @ts-expect-error directive is needed.
Importing WIT interfaces
Add this import at the top of your handler file:
import { getEnvironment } from 'wasi:cli/environment@0.2.3';Reading configuration values
getEnvironment() returns all key-value pairs as an array. Convert it to a plain object with Object.fromEntries for convenient key-based access:
import { getEnvironment } from 'wasi:cli/environment@0.2.3';
app.get('/', (c) => {
const config = Object.fromEntries(getEnvironment());
const appName = config['APP_NAME'] ?? 'My App';
const upstreamUrl = config['UPSTREAM_URL'];
if (!upstreamUrl) {
return c.text('UPSTREAM_URL is not configured', 500);
}
return c.json({ app: appName, upstream: upstreamUrl });
});Call getEnvironment() inside your route handlers rather than at module scope. Configuration is stable for the lifetime of each invocation, but calling it at module scope may execute before the host has provided the values.
Hono env pattern
When using Hono, you can load all configuration once in the fetch event listener and pass it as the env argument to app.fetch. This gives every route and middleware access to config through c.env, and keeps configuration loading in one place:
/// <reference lib="WebWorker" />
declare const self: ServiceWorkerGlobalScope;
import { Hono } from 'hono';
import { getEnvironment } from 'wasi:cli/environment@0.2.3';
type AppConfig = {
APP_NAME?: string;
UPSTREAM_URL?: string;
};
const app = new Hono<{ Bindings: AppConfig }>();
app.get('/', (c) => {
const appName = c.env.APP_NAME ?? 'My App';
const upstreamUrl = c.env.UPSTREAM_URL;
if (!upstreamUrl) {
return c.text('UPSTREAM_URL is not configured', 500);
}
return c.json({ app: appName, upstream: upstreamUrl });
});
self.addEventListener('fetch', (event) => {
const config = Object.fromEntries(getEnvironment()) as AppConfig;
event.respondWith(app.fetch(event.request, config));
});The fetch event listener shown above also works when you're using @bytecodealliance/jco-std — jco-std's fetch-hono fixture uses the same self.addEventListener('fetch', ...) approach. If you'd rather keep using the fire() helper, read config with getEnvironment() inside each route handler instead.
jco-std also ships a Hono middleware, wasiEnvMiddleware, that injects an environment helper into the Hono context. See the fetch-hono-config-use fixture for an example. The minimal pattern above keeps the WASI plumbing visible; the middleware is a convenience wrapper on top of the same interface.
Synchronous API
getEnvironment() is synchronous. It doesn't return a Promise and requires no await. This is consistent with the WebAssembly component model's blocking call convention.
Step 3: Provide configuration values
In production (Kubernetes manifests)
All configuration values are delivered to the component through the localResources.environment field in a Workload or WorkloadDeployment manifest. Three sources are supported and can be combined.
Inline values
The simplest approach — provide key-value pairs directly in the manifest:
components:
- name: http-component
image: ghcr.io/your-org/your-component:latest
localResources:
environment:
config:
APP_NAME: My Service
UPSTREAM_URL: https://api.example.comInline values are suitable for non-sensitive configuration that is safe to store in version control.
From a Kubernetes ConfigMap
Reference a ConfigMap by name. Each key in the ConfigMap becomes a configuration value in the component:
components:
- name: http-component
image: ghcr.io/your-org/your-component:latest
localResources:
environment:
configFrom:
- name: app-configCreate the ConfigMap separately:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: default
data:
APP_NAME: My Service
UPSTREAM_URL: https://api.example.comFrom a Kubernetes Secret
Reference a Secret by name for sensitive values such as API keys or credentials:
components:
- name: http-component
image: ghcr.io/your-org/your-component:latest
localResources:
environment:
secretFrom:
- name: app-secretsSecret values are delivered to the component as plain strings. No decoding is required in your component code.
Combining sources
All three sources can be used together. When the same key appears in multiple sources, the order of precedence (lowest to highest) is: config → configFrom → secretFrom.
apiVersion: runtime.wasmcloud.dev/v1alpha1
kind: WorkloadDeployment
metadata:
name: my-app
namespace: default
spec:
replicas: 1
template:
spec:
hostSelector:
hostgroup: default
components:
- name: http-component
image: ghcr.io/your-org/your-component:latest
localResources:
environment:
config:
APP_NAME: My Service # inline (lowest precedence)
configFrom:
- name: app-config # non-sensitive config from ConfigMap
secretFrom:
- name: app-secrets # sensitive values from Secret (highest precedence)
hostInterfaces:
- namespace: wasi
package: http
interfaces:
- incoming-handler
config:
address: '0.0.0.0:8080'For a complete reference to localResources.environment fields, including precedence rules and RBAC considerations, see Secrets and Configuration Management.
During development (wash dev)
wash dev provides the wasi:cli/environment interface, but localResources.environment defaults to empty — there is currently no mechanism in .wash/config.yaml or the wash dev CLI to inject test configuration values into a component locally.
The practical approach for development is to supply fallback defaults in your component code:
const config = Object.fromEntries(getEnvironment());
// Use ?? to supply development defaults when values aren't set
const appName = config['APP_NAME'] ?? 'My App (dev)';
const upstreamUrl = config['UPSTREAM_URL'] ?? 'http://localhost:9090';This lets your component run correctly in development while reading real values in production. Test configuration-dependent behavior by deploying to a Kubernetes environment with the full manifest.
Build and verify
Build
npm run buildThis runs the full pipeline:
wash wit fetch— downloadswasi:cli/environmentWIT definitions intowit/deps/jco guest-types— generates TypeScript type definitions ingenerated/types/rolldown— bundles TypeScript intodist/component.js(leavingwasi:imports external)jco componentize— compilesdist/component.jsinto a.wasmcomponent
Verify the WIT import
Use wash inspect to confirm that your component correctly declares a wasi:cli/environment import:
wash inspect dist/http_service_hono.wasmYou should see a line like:
import wasi:cli/environment@0.2.x;
The exact patch version may differ from what you declared in wit/world.wit — this is normal. The JavaScript engine embedded by ComponentizeJS has its own WASI version requirements, and jco componentize resolves them automatically.
Run
npm run devThe component loads and serves requests. In development, getEnvironment() returns an empty list — your component will use whatever fallback defaults you've provided. Deploy to Kubernetes to test configuration behavior end-to-end.
Summary: checklist for adding configuration
- Add
import wasi:cli/environment@0.2.3towit/world.wit. - Run
npm run buildonce sowash wit fetchdownloads the WIT definition andjco guest-typesgenerates the TypeScript type stubs ingenerated/types/. - Import
getEnvironmentin TypeScript — no@ts-expect-errordirective is needed because JCO generates types for this interface. - Call
getEnvironment()inside route handlers, not at module scope. - Convert with
Object.fromEntries(getEnvironment())for plain-object key-based access. - Provide fallback defaults with
??for every value your component reads — this keeps the component functional duringwash devwhen no configuration is injected. - No rolldown changes needed —
wasi:cliis already covered by the defaultexternal: /wasi:.*/pattern. - In production, provide values via
localResources.environmentin your Kubernetes manifest, usingconfig:,configFrom:, orsecretFrom:as appropriate.
API reference: wasi:cli/environment functions
For the upstream WIT definitions, see the wasi:cli 0.2.x interfaces in the WebAssembly/wasi-cli repository.
| Function | Signature | Description |
|---|---|---|
getEnvironment() | () => Array<[string, string]> | Returns all configuration key-value pairs. Returns an empty array if no configuration has been provided to the component. |
getArguments() | () => Array<string> | Returns POSIX-style command-line arguments. Not used for configuration. |
initialCwd() | () => string | undefined | Returns the initial working directory. Not used for configuration. |
Converting getEnvironment() output:
| Expression | Result type | Use when |
|---|---|---|
getEnvironment() | Array<[string, string]> | Iterating over entries |
Object.fromEntries(getEnvironment()) | Record<string, string> | Key-based access in route handlers |
new Map(getEnvironment()) | Map<string, string> | When you need Map-specific methods |
Further reading
- Secrets and Configuration Management — operator reference for
localResources.environment, ConfigMaps, and Secrets - TypeScript Language Guide — toolchain overview, HTTP patterns, framework integration, and library compatibility
- Key-Value Storage — persistent key-value storage for component state
- Language Support overview — summary of all supported languages and toolchains