Environment
- @aws-amplify/backend: 1.23.0
- @aws-amplify/backend-cli: 1.8.3
- @aws-amplify/backend-data: 1.7.0
- @aws-amplify/data-construct: 1.17.7
- @aws-amplify/graphql-model-transformer: 3.5.0 (nested under data-construct)
- Node.js: 24.x locally (also reproduced on the Amplify Hosting build image, Node 20)
- Deployment: branch deployment via
ampx pipeline-deploy (Gen 1 → Gen 2 migration flow)
Description
When migrating a Gen 1 backend to Gen 2 using migratedAmplifyGen1DynamoDbTableMappings, any model whose schema declares a custom @primaryKey (i.e. the partition key is not id) fails deployment. The Custom::ImportedAmplifyDynamoDBTable resource is synthesized with an expected key schema of id/HASH, ignoring the @primaryKey directive, so the import validation rejects the (correct) live Gen 1 table:
Received response status [FAILED] from custom resource. Message returned:
Imported table properties did not match the expected table properties.
The whole branch stack then rolls back (ROLLBACK_COMPLETE), and because it is a failed CREATE, the stack must be deleted manually before any retry — a rough failure mode in the middle of a migration flow.
Reproduction
Minimal schema (Gen 1 and Gen 2 identical):
type AuthSessionToken
@model
@auth(rules: [{ allow: public, operations: [create, update] }]) {
userAuthenticating: ID! @primaryKey
sessionToken: String!
}
- Deploy with Gen 1 (transformer v2). The table is correctly created with partition key
userAuthenticating (HASH).
- Migrate to Gen 2 with the table mapped:
defineData({
migratedAmplifyGen1DynamoDbTableMappings: [{
branchName: "gen2-dev",
modelNameToTableNameMapping: {
AuthSessionToken: "AuthSessionToken-<gen1ApiId>-dev",
},
}],
schema, // same schema string
});
- Deploy the branch → the AuthSessionToken nested stack fails with the error above.
Evidence
The TableManager custom resource receives these expected properties (from its CloudWatch logs):
"attributeDefinitions": [{ "attributeType": "S", "attributeName": "id" }],
"keySchema": [{ "attributeName": "id", "keyType": "HASH" }],
"isImported": "true",
"tableName": "AuthSessionToken-<gen1ApiId>-dev"
while the live Gen 1 table is:
"KeySchema": [{ "AttributeName": "userAuthenticating", "KeyType": "HASH" }],
"AttributeDefinitions": [{ "AttributeName": "userAuthenticating", "AttributeType": "S" }]
Notably, @primaryKey is honored everywhere else in the same synth:
- The transformed GraphQL schema is correct:
getAuthSessionToken(userAuthenticating: ID!), no injected id field, and CreateAuthSessionTokenInput is keyed on userAuthenticating.
- Sandbox deployments of the same schema (no mapping → owned tables) create the table with the correct
userAuthenticating key.
- GSIs on imported tables are derived correctly from the model — in our 19-model schema, 18 tables (all with default
id keys, several with multiple GSIs) validated fine; the only model with a custom @primaryKey was the only failure.
Root cause (best guess)
@aws-amplify/graphql-model-transformer, lib/resources/amplify-dynamodb-table/amplify-dynamo-model-resource-generator.js (~L159): the AmplifyDynamoDBTable construct is instantiated with a hardcoded partition key:
partitionKey: {
name: 'id',
type: AttributeType.STRING,
},
...(isTableImported ? { isImported: true } : undefined),
For owned tables the @primaryKey transformer corrects the key schema downstream, but for imported tables the TableManager validation consumes these initial construct properties directly, so the correction never applies.
Workaround
CDK escape hatch overriding the imported table's expected properties to match reality:
const imported = backend.data.stack.node.findAll().filter(
(c): c is CfnResource =>
CfnResource.isCfnResource(c) &&
c.cfnResourceType === "Custom::ImportedAmplifyDynamoDBTable"
);
const t = imported.find((c) => c.node.path.includes("AuthSessionToken"));
t?.addPropertyOverride("keySchema", [
{ attributeName: "userAuthenticating", keyType: "HASH" },
]);
t?.addPropertyOverride("attributeDefinitions", [
{ attributeName: "userAuthenticating", attributeType: "S" },
]);
Expected behavior
The imported-table expected properties should derive the primary key from the model definition (as GSIs already are), so schemas with @primaryKey can migrate without manual overrides.
Environment
ampx pipeline-deploy(Gen 1 → Gen 2 migration flow)Description
When migrating a Gen 1 backend to Gen 2 using
migratedAmplifyGen1DynamoDbTableMappings, any model whose schema declares a custom@primaryKey(i.e. the partition key is notid) fails deployment. TheCustom::ImportedAmplifyDynamoDBTableresource is synthesized with an expected key schema ofid/HASH, ignoring the@primaryKeydirective, so the import validation rejects the (correct) live Gen 1 table:The whole branch stack then rolls back (
ROLLBACK_COMPLETE), and because it is a failed CREATE, the stack must be deleted manually before any retry — a rough failure mode in the middle of a migration flow.Reproduction
Minimal schema (Gen 1 and Gen 2 identical):
userAuthenticating(HASH).Evidence
The TableManager custom resource receives these expected properties (from its CloudWatch logs):
while the live Gen 1 table is:
Notably,
@primaryKeyis honored everywhere else in the same synth:getAuthSessionToken(userAuthenticating: ID!), no injectedidfield, andCreateAuthSessionTokenInputis keyed onuserAuthenticating.userAuthenticatingkey.idkeys, several with multiple GSIs) validated fine; the only model with a custom@primaryKeywas the only failure.Root cause (best guess)
@aws-amplify/graphql-model-transformer,lib/resources/amplify-dynamodb-table/amplify-dynamo-model-resource-generator.js(~L159): theAmplifyDynamoDBTableconstruct is instantiated with a hardcoded partition key:For owned tables the
@primaryKeytransformer corrects the key schema downstream, but for imported tables the TableManager validation consumes these initial construct properties directly, so the correction never applies.Workaround
CDK escape hatch overriding the imported table's expected properties to match reality:
Expected behavior
The imported-table expected properties should derive the primary key from the model definition (as GSIs already are), so schemas with
@primaryKeycan migrate without manual overrides.