Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ba6478b
feat(parameters): add support for AppConfig Agent
svozza Jul 7, 2026
f0f8a1d
test(parameters): strengthen getConfig type-level tests
svozza Jul 7, 2026
8e86d69
test(parameters): cover getConfig timeout abort path
svozza Jul 7, 2026
5fe12e8
test(parameters): assert on invocation payload in AppConfig Agent e2e
svozza Jul 7, 2026
841232a
refactor(parameters): read AppConfig Agent response body once
svozza Jul 7, 2026
f788783
chore(parameters): address code review findings for getConfig
svozza Jul 7, 2026
9b7707f
refactor(parameters): simplify getConfig transform option types
svozza Jul 7, 2026
95e8005
feat(parameters): throw ParameterNotFoundError when configuration is …
svozza Jul 7, 2026
094cdf8
Merge branch 'main' into feat/parameters-appconfig-agent
svozza Jul 8, 2026
f56d6c5
fix(tests): grant IAM access to missing profile in AppConfig Agent e2e
svozza Jul 8, 2026
09c0c1f
Merge branch 'feat/parameters-appconfig-agent' of github.com:aws-powe…
svozza Jul 8, 2026
bda05e9
Merge branch 'main' into feat/parameters-appconfig-agent
svozza Jul 13, 2026
6d4d418
chore(tests): use async helper instead of Promise.then in e2e fixture
svozza Jul 13, 2026
6be3322
Merge branch 'feat/parameters-appconfig-agent' of github.com:aws-powe…
svozza Jul 13, 2026
113fc08
feat(parameters): support throwOnMissing and local return value in ge…
svozza Jul 14, 2026
9db350a
refactor(parameters): reduce getConfig cognitive complexity
svozza Jul 14, 2026
4811605
refactor(parameters): extract nested ternary in getConfig
svozza Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions docs/features/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ This utility requires additional permissions to work as expected.
| DynamoDB | **`DynamoDBProvider.get`** | **`dynamodb:GetItem`** |
| DynamoDB | **`DynamoDBProvider.getMultiple`** | **`dynamodb:Query`** |
| AppConfig | **`getAppConfig`**, **`AppConfigProvider.getAppConfig`** | **`appconfig:GetLatestConfiguration`** and **`appconfig:StartConfigurationSession`** |
| AppConfig | **`getConfig`** (AppConfig Agent) | **`appconfig:GetLatestConfiguration`** and **`appconfig:StartConfigurationSession`** |

???+ note
When using `getConfig`, the permissions are used by the [AWS AppConfig Agent Lambda extension](https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-integration-lambda-extensions.html) via the function role.

### Fetching parameters

Expand Down Expand Up @@ -139,6 +143,29 @@ The following will retrieve the latest version and store it in the cache.

When using `getAppConfig`, the [underlying provider](#appconfigprovider) is cached. To fetch from different applications or environments, create separate `AppConfigProvider` instances for each application/environment combination.

### Fetching app configurations via AppConfig Agent

If you use the [AWS AppConfig Agent Lambda extension](https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-integration-lambda-extensions.html), you can fetch application configurations from the agent's local HTTP endpoint using `getConfig`.

```typescript hl_lines="1 4-8" title="Fetching a config from the AppConfig Agent"
--8<-- "examples/snippets/parameters/getConfigAppConfigAgent.ts"
```

???+ note
To use `getConfig` you must [add the AWS AppConfig Agent Lambda extension layer](https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-integration-lambda-extensions-versions.html) to your function.

When `getConfig` detects that it's not running in AWS Lambda (the `AWS_LAMBDA_INITIALIZATION_TYPE` environment variable is not set), or when `POWERTOOLS_DEV` is enabled, it returns `undefined` without making any request - this is helpful for unit testing.
Local emulators that replicate the Lambda runtime environment (e.g. AWS SAM CLI) set the Lambda environment variables, so in those environments `getConfig` will attempt to call the agent.

Unlike `getAppConfig`, which uses the AWS SDK, the agent handles caching, polling, and prefetching for you, so `getConfig` doesn't support the `maxAge` and `forceFetch` options and always fetches from the agent's local endpoint.

When the requested configuration doesn't exist, `getConfig` returns `undefined` - you can use the nullish coalescing operator (`??`) to provide a fallback value, or set the [`throwOnMissing` option](#throwing-on-missing-values) to throw a `ParameterNotFoundError` instead. Any other failure to reach the agent or retrieve the value throws a `GetParameterError`.

You can configure the agent's behavior using [the environment variables it exposes](https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-integration-lambda-extensions-config.html).
For example, you can reduce cold start latency by setting `AWS_APPCONFIG_EXTENSION_PREFETCH_LIST` to the path of your configuration (i.e. `/applications/my-app/environments/my-env/configurations/my-configuration`), so that the agent starts fetching it before your handler runs.

If you configured the agent to run on a port other than the default `2772` using the `AWS_APPCONFIG_EXTENSION_HTTP_PORT` environment variable, `getConfig` will use that port. You can also pass a `timeout` option (in milliseconds, default `3000`) to control how long to wait for the agent to respond.

## Advanced

### Adjusting cache TTL
Expand Down Expand Up @@ -503,6 +530,22 @@ For when you want to mock the AWS SDK v3 client directly, we recommend using the
--8<-- "examples/snippets/parameters/testingYourCodeClientHandler.ts"
```

### Testing getConfig (AppConfig Agent)

When not running in AWS Lambda, the `getConfig` function doesn't make any request and returns `undefined`, so your tests never need to mock the agent's HTTP endpoint.

If you want `getConfig` to return a value instead, set the `POWERTOOLS_APPCONFIG_AGENT_RETURN_VALUE` environment variable. Its value is treated as the agent response, so it goes through the same `transform` handling as a real response - for example, a JSON string works with `transform: 'json'`.

=== "handler.test.ts"
```typescript hl_lines="6-9"
--8<-- "examples/snippets/parameters/testingYourCodeAppConfigAgent.ts"
```

=== "handler.ts"
```typescript
--8<-- "examples/snippets/parameters/testingYourCodeAppConfigAgentHandler.ts"
```

### Clearing cache

Parameters utility caches all parameter values for performance and cost reasons. However, this can have unintended interference in tests using the same parameter name.
Expand Down
12 changes: 12 additions & 0 deletions examples/snippets/parameters/getConfigAppConfigAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getConfig } from '@aws-lambda-powertools/parameters/appconfig-agent';

// Retrieve a configuration from the AppConfig Agent Lambda extension
const config = await getConfig('my-configuration', {
environment: 'my-env',
application: 'my-app',
transform: 'json',
});

export const handler = async (): Promise<void> => {
console.log(config);
};
24 changes: 24 additions & 0 deletions examples/snippets/parameters/testingYourCodeAppConfigAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

describe('Function tests', () => {
beforeEach(() => {
vi.resetModules();
vi.stubEnv(
'POWERTOOLS_APPCONFIG_AGENT_RETURN_VALUE',
JSON.stringify({ featureX: true })
);
});

it('returns the expected response', async () => {
// Prepare
const { handler } = await import(
'./testingYourCodeAppConfigAgentHandler.js'
);

// Act
const response = await handler({}, {});

// Assess
expect(response).toEqual({ featureX: true });
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { getConfig } from '@aws-lambda-powertools/parameters/appconfig-agent';

const config = await getConfig('my-configuration', {
application: 'my-app',
environment: 'my-env',
transform: 'json',
});

export const handler = async (
_event: unknown,
_context: unknown
): Promise<unknown> => {
return config;
};
22 changes: 22 additions & 0 deletions packages/parameters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ You can use the package in both TypeScript and JavaScript code bases.
- [Getting secrets from Amazon Secrets Manager](#getting-secrets-from-amazon-secrets-manager)
- [Retrieving values from Amazon DynamoDB](#retrieving-values-from-amazon-dynamodb)
- [Fetching configs from AWS AppConfig](#fetching-configs-from-aws-appconfig)
- [Fetching configs from the AWS AppConfig Agent](#fetching-configs-from-the-aws-appconfig-agent)
- [Contribute](#contribute)
- [Roadmap](#roadmap)
- [Connect](#connect)
Expand Down Expand Up @@ -195,6 +196,27 @@ export const handler = async (): Promise<void> => {

Check the [docs](https://docs.aws.amazon.com/powertools/typescript/latest/features/parameters/#fetching-app-configurations) for more examples, and [the advanced section](https://docs.aws.amazon.com/powertools/typescript/latest/features/parameters/#advanced) for details about caching, transforms, customizing the underlying SDK, and more.

### Fetching configs from the AWS AppConfig Agent

If you use the [AWS AppConfig Agent Lambda extension](https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-integration-lambda-extensions.html), you can fetch application configurations from the agent's local HTTP endpoint using the `getConfig` function:

```ts
import { getConfig } from '@aws-lambda-powertools/parameters/appconfig-agent';

// Retrieve a configuration from the AppConfig Agent Lambda extension
const config = await getConfig('my-configuration', {
environment: 'my-env',
application: 'my-app',
transform: 'json',
});

export const handler = async (): Promise<void> => {
console.log(config);
};
```

Since caching, polling, and prefetching are handled by the agent, `getConfig` always fetches from the agent's local endpoint and doesn't require the AWS SDK. Check the [docs](https://docs.aws.amazon.com/powertools/typescript/latest/features/parameters/#fetching-app-configurations-via-appconfig-agent) for more details.

## Contribute

If you are interested in contributing to this project, please refer to our [Contributing Guidelines](https://github.com/aws-powertools/powertools-lambda-typescript/blob/main/CONTRIBUTING.md).
Expand Down
16 changes: 16 additions & 0 deletions packages/parameters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@
"import": "./lib/esm/appconfig/index.js",
"require": "./lib/cjs/appconfig/index.js"
},
"./appconfig-agent/types": {
"import": "./lib/esm/types/AppConfigAgent.js",
"require": "./lib/cjs/types/AppConfigAgent.js"
},
"./appconfig-agent": {
"import": "./lib/esm/appconfig-agent/index.js",
"require": "./lib/cjs/appconfig-agent/index.js"
},
"./errors": {
"import": "./lib/esm/errors.js",
"require": "./lib/cjs/errors.js"
Expand Down Expand Up @@ -128,6 +136,14 @@
"lib/cjs/appconfig/index.d.ts",
"lib/esm/appconfig/index.d.ts"
],
"appconfig-agent/types": [
"./lib/cjs/types/AppConfigAgent.d.ts",
"./lib/esm/types/AppConfigAgent.d.ts"
],
"appconfig-agent": [
"lib/cjs/appconfig-agent/index.d.ts",
"lib/esm/appconfig-agent/index.d.ts"
],
"errors": [
"lib/cjs/errors.d.ts",
"lib/esm/errors.d.ts"
Expand Down
188 changes: 188 additions & 0 deletions packages/parameters/src/appconfig-agent/getConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import {
getServiceName,
getStringFromEnv,
isRunningInLambda,
} from '@aws-lambda-powertools/commons/utils/env';
import { transformValue } from '../base/transformValue.js';
import { GetParameterError, ParameterNotFoundError } from '../errors.js';
import type {
AppConfigAgentGetConfigOutput,
GetConfigOptions,
} from '../types/AppConfigAgent.js';

/**
* Retrieve a configuration from the AWS AppConfig Agent Lambda extension.
*
* The [AWS AppConfig Agent](https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-integration-lambda-extensions.html)
* runs as a Lambda extension inside your execution environment and exposes a local HTTP endpoint that this function calls to
* retrieve configurations. To use this function you must add the AWS AppConfig Agent Lambda extension layer to your function.
*
* Unlike the SDK-based `getAppConfig` function, caching, polling, and prefetching are handled entirely by the agent, so this
* function always fetches from the agent's local endpoint and doesn't support the `maxAge` and `forceFetch` options. You can
* configure the agent's behavior, including caching and prefetching, using the [environment variables exposed by the agent](https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-integration-lambda-extensions-config.html).
*
* When the requested configuration does not exist, the function returns `undefined`. You can use the `throwOnMissing`
* option to throw a `ParameterNotFoundError` instead, or use the nullish coalescing operator (`??`) to provide a fallback value.
*
* When the function detects that it's not running in AWS Lambda (the `AWS_LAMBDA_INITIALIZATION_TYPE` environment variable
* is not set), or when running with `POWERTOOLS_DEV` enabled, it doesn't make any request. In this case, if the
* `POWERTOOLS_APPCONFIG_AGENT_RETURN_VALUE` environment variable is set, its value is treated as the agent response and goes
* through the same transform handling; otherwise the function returns `undefined`. This is helpful for unit testing and local
* development. Note that local emulators that replicate the Lambda runtime environment (e.g., AWS SAM CLI) set the Lambda
* environment variables, so in those environments the function will attempt to call the agent.
*
* **Basic usage**
*
* @example
* ```typescript
* import { getConfig } from '@aws-lambda-powertools/parameters/appconfig-agent';
*
* const config = await getConfig('my-configuration', {
* application: 'my-app',
* environment: 'my-env',
* });
*
* export const handler = async (): Promise<void> => {
* // Use the config variable as needed
* console.log(config);
* };
* ```
*
* **Transformations**
*
* For configurations stored as freeform JSON or feature flags, you can use the `transform` argument set to `json` for deserialization.
* This will return a JavaScript object instead of a string.
*
* @example
* ```typescript
* import { getConfig } from '@aws-lambda-powertools/parameters/appconfig-agent';
*
* const config = await getConfig('my-configuration', {
* application: 'my-app',
* environment: 'my-env',
* transform: 'json',
* });
*
* export const handler = async (): Promise<void> => {
* // Use the config variable as needed
* console.log(config);
* };
* ```
*
* For configurations stored as base64-encoded binary data, you can use the `transform` argument set to `binary` for decoding.
* This will return a decoded string.
*
* @example
* ```typescript
* import { getConfig } from '@aws-lambda-powertools/parameters/appconfig-agent';
*
* const config = await getConfig('my-configuration', {
* application: 'my-app',
* environment: 'my-env',
* transform: 'binary',
* });
*
* export const handler = async (): Promise<void> => {
* // Use the config variable as needed
* console.log(config);
* };
* ```
*
* By default, the function calls the agent on port `2772`. If you configured the agent to run on a different port using the
* `AWS_APPCONFIG_EXTENSION_HTTP_PORT` environment variable, the function will use that port instead.
*
* @see https://docs.aws.amazon.com/powertools/typescript/latest/features/parameters/
*
* @param name - The name of the configuration profile or the configuration profile ID
* @param options - Options to configure the retrieval
* @param options.environment - The environment ID or the environment name
* @param options.application - The application ID or the application name, if not provided it will be inferred from the service name in the environment (`POWERTOOLS_SERVICE_NAME`)
* @param options.transform - Optional transform to be applied, can be `json` or `binary`
* @param options.timeout - Optional timeout in milliseconds for the request to the AWS AppConfig Agent (default: `3000`)
* @param options.throwOnMissing - Optional flag to throw a `ParameterNotFoundError` when the configuration does not exist (default: `false`)
*/
/**
* Fetch a configuration from the AWS AppConfig Agent local HTTP endpoint.
*
* Returns the raw response body, or `undefined` when the configuration does not
* exist and `throwOnMissing` is not set.
*
* @param name - The name of the configuration profile or the configuration profile ID
* @param options - Options to configure the retrieval, see {@link getConfig | `getConfig()`}
*/
const fetchConfigFromAgent = async (
name: string,
options: GetConfigOptions
): Promise<string | undefined> => {
const application = options.application ?? getServiceName();
if (application.trim().length === 0) {
throw new GetParameterError(
'Application name is not defined or POWERTOOLS_SERVICE_NAME is not set'
);
}

const port = getStringFromEnv({
key: 'AWS_APPCONFIG_EXTENSION_HTTP_PORT',
defaultValue: '2772',
});

try {
const res = await fetch(
`http://localhost:${port}/applications/${encodeURIComponent(application)}/environments/${encodeURIComponent(options.environment)}/configurations/${encodeURIComponent(name)}`,
{
signal: AbortSignal.timeout(options.timeout ?? 3000),
}
);
const value = await res.text();
if (res.status === 404) {
if (options.throwOnMissing) {
throw new ParameterNotFoundError(`Configuration ${name} not found`);
}
return undefined;
}
if (!res.ok) {
throw new GetParameterError(
`Failed to retrieve configuration from AppConfig Agent: ${res.status} ${value}`
);
}
return value;
} catch (error) {
if (error instanceof GetParameterError) throw error;
throw new GetParameterError((error as Error).message, { cause: error });
}
};

const getConfig = async <
ExplicitUserProvidedType = undefined,
InferredFromOptionsType extends GetConfigOptions = GetConfigOptions,
>(
name: string,
options: InferredFromOptionsType & GetConfigOptions
): Promise<
AppConfigAgentGetConfigOutput<
ExplicitUserProvidedType,
InferredFromOptionsType
>
> => {
let value: string | undefined;
if (isRunningInLambda()) {
value = await fetchConfigFromAgent(name, options);
} else {
const localValue = getStringFromEnv({
key: 'POWERTOOLS_APPCONFIG_AGENT_RETURN_VALUE',
defaultValue: '',
});
value = localValue === '' ? undefined : localValue;
}

return (
value !== undefined && options.transform
? transformValue(value, options.transform, true, name)
: value
) as AppConfigAgentGetConfigOutput<
ExplicitUserProvidedType,
InferredFromOptionsType
>;
};

export { getConfig };
1 change: 1 addition & 0 deletions packages/parameters/src/appconfig-agent/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { getConfig } from './getConfig.js';
Loading
Loading