Parameter Store data types are no longer just for AMI IDs. Since November 5, 2026, they also cover everyday configuration values: numbers, switches, email addresses, URLs and JSON. Sounds minor. For me it is one of the more useful AWS updates of the week, because it closes a gap I keep tripping over in serverless projects.

The gap looks like this: someone changes a value in Parameter Store, types “48 hours” instead of “48”, and nobody notices. Things only break when the next Lambda function cold-starts and reads the value. In the best case your validation catches it. In the worst case the code keeps calculating with NaN. With data types, Parameter Store rejects such values on write. In this article I show you how that works, where it still falls short today (spoiler: CloudFormation) and how you can still use it cleanly with AWS CDK and TypeScript.

In short

Parameter Store now validates values on write against 13 new data types such as integer, boolean, email or json. Configuration typos show up in the pipeline instead of in production. The biggest catch: CloudFormation, and therefore CDK, does not know the new types yet, and url, json and jsonc require paid advanced parameters.

What you will learn in this article

  • Which 13 new data types exist and what exactly they check
  • How to create parameters with a data type via CLI and filter by it
  • How to set the types with CDK despite missing CloudFormation support
  • How a TypeScript script safely converts existing parameters
  • Where the cost traps with json and url are

What are Parameter Store data types?

Parameter Store is part of AWS Systems Manager and stores configuration as key-value pairs under a path, for example /fahrradverleih/prod/max-ausleihdauer-stunden (our fictional bike rental, “Fahrradverleih” in German). If this is new to you, start with my introduction Parameter Store in Lambda: loading configuration the right way. It covers caching, the Lambda extension and when to read values at deploy time versus at runtime.

Every parameter has a type (String, StringList or SecureString) and, in addition, a data type. The type describes how the value is stored, for example encrypted or as a comma-separated list. The data type, on the other hand, describes what the content may look like. Until now there was practically only text (no validation) and aws:ec2:image for AMI IDs. Now 13 more have arrived, and Parameter Store validates the value on every PutParameter. If it does not match, it is never stored.

The new data types at a glance

Data type Checks Valid Rejected Tier
integer whole number, signed 32-bit 443, -42 007, +7, 7.0 Standard
float floating-point number (64-bit) 3.14, 1e5 NaN, Infinity Standard
boolean truth value true, FALSE, 1 tRue, yes Standard
binary only zeros and ones 0011 Base64 Standard
aws:region existing Region eu-central-1 eu-middle-1 Standard
aws:account:id 12-digit account ID (format) 111122223333 1111-2222-3333 Standard
hostname host name, no IP api.example.com _dmarc.example.com Standard
email bare address help@example.com Help <help@example.com> Standard
ipv4-address one IPv4 address 192.0.2.1 10.0.0.0/16 Standard
ipv6-address one IPv6 address 2001:db8::1 CIDR, ports Standard
url URL with scheme and host https://example.com mailto:…, file:/// Advanced only
json JSON object or array {"port": 443} 42, duplicate keys Advanced only
jsonc like json, with comments {"port": 443 /* https */} trailing commas Advanced only

A few details from the documentation matter in practice. For a StringList, every item is checked on its own, and a single bad entry makes the whole call fail. SecureString supports almost all types, and validation works the same as for String. You can also combine a data type with the familiar AllowedPattern; the value then has to pass both. By the way, aws:account:id only checks the format, not whether the account exists.

Very important: nothing changes on read. GetParameter still returns a string, so 443 comes back as "443". Converting it is still your code’s job.

Why validate values on write?

Configuration is code without a compiler. A typo in TypeScript shows up at the latest when you run tsc. A typo in Parameter Store only shows up when someone reads the value. With Lambda, that is usually the next cold start: at some random time, often at night and often in production.

That is why I validate configuration in every Lambda function on start, for example with Zod (see Validate environment variables: Lambda configuration with Zod). This protects against wrong behavior, but it does not prevent the outage. The function simply refuses to start. Data types move the check to the place where the error is made: the change itself. Anyone writing an invalid value in the console or the pipeline gets an error right away, and the old, working value stays active.

Diagram: without a data type Lambda fails in prod; with data type integer PutParameter rejects the value on write

Without a data type, the error shows up in the Lambda function. With one, it is rejected on write.

Still, data types should not replace your runtime checks. They validate the format, not business limits. A maximum rental time of -5 is a valid integer but makes no sense. For that you still need AllowedPattern or a check in code.

Using Parameter Store data types with the CLI

The fastest way to try the feature is the AWS CLI. Our example is a fictional bike rental that keeps a few settings in Parameter Store.

Prerequisites

  • an AWS account with permissions for ssm:PutParameter, ssm:GetParameter and ssm:DescribeParameters
  • a current AWS CLI v2 (older versions already know the --data-type option and simply pass the value through)
  • Node.js 22 or later for the TypeScript examples below

Step 1: Create a parameter with a data type

aws ssm put-parameter \ --name "/fahrradverleih/prod/max-ausleihdauer-stunden" \ --value "48" \ --type String \ --data-type integer 

As usual, the response contains Version and Tier. From now on, the data type is checked on every change.

Step 2: Write a wrong value

aws ssm put-parameter \ --name "/fahrradverleih/prod/max-ausleihdauer-stunden" \ --value "48 hours" \ --type String \ --data-type integer \ --overwrite 

The call fails with an error, and get-parameter still returns 48. That is exactly what we want. The API documentation does not name the exact error yet. So do not check for a specific error name in your code; treat any error from PutParameter as “value not applied”.

Step 3: JSON configuration as an advanced parameter

For url, json and jsonc you have to specify the advanced tier. Without --tier Advanced, Parameter Store rejects the call.

aws ssm put-parameter \ --name "/fahrradverleih/prod/tarife" \ --value '{"stadtrad": 2.5, "ebike": 6}' \ --type String \ --data-type json \ --tier Advanced 

Step 4: Filter parameters by data type

With describe-parameters you find all parameters of a data type. That is handy for spotting where plain text without validation is still in use:

aws ssm describe-parameters \ --parameter-filters "Key=Path,Option=Recursive,Values=/fahrradverleih/prod" \ "Key=DataType,Values=text" \ --query "Parameters[].Name" 

The DataType filter only works with DescribeParameters. According to the API reference, GetParametersByPath does not accept it.

Setting Parameter Store data types with AWS CDK

Now for the catch. On November 6, 2026, I queried the resource schema of AWS::SSM::Parameter in the CloudFormation registry: the DataType property still only allows text and aws:ec2:image. The description in the CloudFormation reference already mentions json, but the allowed values do not. So you cannot reliably set the new types with ssm.StringParameter in CDK or directly in a serverless.yml under resources. AWS will surely catch up, but until then you need a workaround.

The workaround is called AwsCustomResource. This CDK construct creates a small Lambda function that runs any AWS SDK call during deployment. We let it call PutParameter with a data type. If Parameter Store rejects the value, the deployment fails and CloudFormation rolls back. That is exactly the behavior we want.

Step 1: Create the project

mkdir fahrradverleih-config && cd fahrradverleih-config npm init -y npm install aws-cdk-lib@2.275.0 constructs@10.8.1 @aws-sdk/client-ssm@3.1163.0 npm install -D aws-cdk@2.1148.0 typescript@7.0.2 @types/node@22 

In package.json, set "type": "module". The tsconfig.json uses "module": "NodeNext" and writes to dist, and cdk.json points to the compiled program with "app": "node dist/bin/app.js". All other files are shown in full in this article.

Step 2: Keep the allowed data types in one place

I keep the list of parameters and their data types in a separate file. Stack and script share it, so there is only one source of truth.

// config/parameters.ts // Single source of truth: which parameter should have which data type. export type DataType = | 'text' | 'aws:region' | 'aws:account:id' | 'boolean' | 'integer' | 'float' | 'binary' | 'hostname' | 'email' | 'ipv4-address' | 'ipv6-address' | 'url' | 'json' | 'jsonc'; export interface ParameterSpec { name: string; dataType: DataType; } export const PREFIX = '/fahrradverleih/prod'; export const parameterSpecs: ParameterSpec[] = [ { name: `${PREFIX}/max-ausleihdauer-stunden`, dataType: 'integer' }, { name: `${PREFIX}/preis-pro-stunde`, dataType: 'float' }, { name: `${PREFIX}/wartungsmodus`, dataType: 'boolean' }, { name: `${PREFIX}/support-mail`, dataType: 'email' }, { name: `${PREFIX}/zahlungsdienst-url`, dataType: 'url' }, ]; // These types only work with advanced parameters (cost: see article). export const ADVANCED_ONLY: ReadonlySet<DataType> = new Set(['url', 'json', 'jsonc']); 

Step 3: A construct for typed parameters

// lib/config-stack.ts import { Stack, type StackProps, Arn } from 'aws-cdk-lib'; import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId, } from 'aws-cdk-lib/custom-resources'; import { Construct } from 'constructs'; import { ADVANCED_ONLY, type DataType } from '../config/parameters.js'; interface TypedParameterProps { name: string; value: string; dataType: DataType; } // CloudFormation (AWS::SSM::Parameter) currently only accepts text and aws:ec2:image. // That is why we call PutParameter directly via a custom resource. export class TypedParameter extends Construct { constructor(scope: Construct, id: string, props: TypedParameterProps) { super(scope, id); const put = { service: 'SSM', action: 'putParameter', parameters: { Name: props.name, Value: props.value, Type: 'String', DataType: props.dataType, Tier: ADVANCED_ONLY.has(props.dataType) ? 'Advanced' : 'Standard', Overwrite: true, }, physicalResourceId: PhysicalResourceId.of(props.name), }; const parameterArn = Arn.format( { service: 'ssm', resource: 'parameter', resourceName: props.name.replace(/^\//, '') }, Stack.of(this), ); new AwsCustomResource(this, 'Resource', { onCreate: put, onUpdate: put, onDelete: { service: 'SSM', action: 'deleteParameter', parameters: { Name: props.name } }, policy: AwsCustomResourcePolicy.fromSdkCalls({ resources: [parameterArn] }), installLatestAwsSdk: false, }); } } export class FahrradverleihConfigStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); new TypedParameter(this, 'MaxDauer', { name: '/fahrradverleih/prod/max-ausleihdauer-stunden', value: '48', dataType: 'integer', }); new TypedParameter(this, 'Wartungsmodus', { name: '/fahrradverleih/prod/wartungsmodus', value: 'false', dataType: 'boolean', }); new TypedParameter(this, 'SupportMail', { name: '/fahrradverleih/prod/support-mail', value: 'hilfe@fahrradverleih.example', dataType: 'email', }); } } 

Some decisions in there are deliberate. Overwrite: true makes sure an update replaces the value instead of failing with “already exists”. The IAM policy allows the SDK calls for exactly this one parameter. And installLatestAwsSdk: false skips downloading the SDK during deployment, because DataType is a plain string in the SDK that the SDK bundled with the Lambda runtime passes through as well.

Step 4: Create the app, synthesize and deploy

// bin/app.ts import { App } from 'aws-cdk-lib'; import { FahrradverleihConfigStack } from '../lib/config-stack.js'; const app = new App(); new FahrradverleihConfigStack(app, 'FahrradverleihConfig'); 
npx tsc npx cdk synth npx cdk deploy FahrradverleihConfig 

In the synthesized template you will see one resource of type Custom::AWS per parameter, with "DataType":"integer" in its call. After deployment, the console shows the data type for the parameter. As a test, change the value of MaxDauer to '48h' and deploy again: CloudFormation reports an error for the custom resource and rolls the stack back. The old value stays in place.

🎬 Flow: a CDK deployment writes a typed parameter – this animated diagram only runs on my site: watch it on sascha-ms.de

Converting existing parameters to data types

New parameters are easy. The more interesting question is how to convert the hundred existing parameters that are all text today. There is no plain “change data type” call: you have to write the parameter again with PutParameter and Overwrite, using the current value and the new data type. This creates a new version. If the current value does not match the type, Parameter Store rejects it, and you have found a bug along the way.

I split this into two parts: a pure function that calculates a plan from the target and current state, and a script that shows the plan and executes it on request. The pure function can be tested without AWS access.

Step 1: Calculate the plan

// src/plan.ts import { ADVANCED_ONLY, type ParameterSpec } from '../config/parameters.js'; export interface CurrentParameter { name: string; type: 'String' | 'StringList' | 'SecureString'; dataType: string; tier: 'Standard' | 'Advanced' | 'Intelligent-Tiering'; } export type Step = | { action: 'ok'; name: string } | { action: 'change'; name: string; from: string; to: string; needsAdvanced: boolean } | { action: 'skip'; name: string; reason: string }; export function planTypeChanges(specs: ParameterSpec[], current: CurrentParameter[]): Step[] { const byName = new Map(current.map((p) => [p.name, p])); return specs.map((spec): Step => { const p = byName.get(spec.name); if (!p) return { action: 'skip', name: spec.name, reason: 'parameter does not exist' }; if (p.dataType === spec.dataType) return { action: 'ok', name: spec.name }; if (p.dataType === 'aws:ec2:image' || p.dataType === 'aws:ssm:integration') { return { action: 'skip', name: spec.name, reason: `data type ${p.dataType} cannot be changed` }; } if (p.type === 'SecureString') { return { action: 'skip', name: spec.name, reason: 'SecureString: change manually with the right KMS key' }; } return { action: 'change', name: spec.name, from: p.dataType, to: spec.dataType, needsAdvanced: ADVANCED_ONLY.has(spec.dataType) && p.tier === 'Standard', }; }); } 

I deliberately leave out SecureStrings. When overwriting, you would have to pass the right KMS key again, otherwise the value might end up under the default key aws/ssm. That is too risky for a bulk script. According to the docs, aws:ec2:image and aws:ssm:integration cannot be converted to other types at all.

Step 2: The script with a dry run

// src/apply-types.ts // Usage: node dist/src/apply-types.js (plan only) // node dist/src/apply-types.js --apply (write changes) import { SSMClient, DescribeParametersCommand, GetParameterCommand, PutParameterCommand, type ParameterMetadata, } from '@aws-sdk/client-ssm'; import { PREFIX, parameterSpecs } from '../config/parameters.js'; import { planTypeChanges, type CurrentParameter } from './plan.js'; const ssm = new SSMClient({}); const apply = process.argv.includes('--apply'); async function loadCurrent(): Promise<CurrentParameter[]> { const result: CurrentParameter[] = []; let NextToken: string | undefined; do { const page = await ssm.send(new DescribeParametersCommand({ ParameterFilters: [{ Key: 'Path', Option: 'Recursive', Values: [PREFIX] }], NextToken, })); for (const p of page.Parameters ?? []) result.push(toCurrent(p)); NextToken = page.NextToken; } while (NextToken); return result; } function toCurrent(p: ParameterMetadata): CurrentParameter { return { name: p.Name!, type: p.Type as CurrentParameter['type'], dataType: p.DataType Source:https://dev.to/sascha-ms/parameter-store-data-types-validate-values-on-write-4mp3

Related posts