src/config/organization-structure.ts is where you define your entire AWS Organization hierarchy: which Organizational Units exist, which accounts belong to each OU, and which SCPs attach at the root, OU, or account level.
The OrganizationConstruct in the foundation package reads this object and creates or updates OUs, accounts, and SCP attachments on every deploy. It also publishes the resolved organization identifiers as CloudFormation stack outputs so later stacks can consume them as StackSet targets. OU IDs are published by default; account IDs are opt-in per account with publishAccountIdOutput.
Built-in OUs
The starter ships with six pre-configured OUs that match common AWS multi-account patterns:
| OU key | Default name | Purpose |
|---|---|---|
LogArchiveOU | log-ou | Holds the log archive account that receives the centralized organization CloudTrail logs |
SecurityOU | security-ou | Holds the security account, the delegated administrator for CloudTrail, GuardDuty, Security Hub, Inspector, and Macie |
InfrastructureOU | infrastructure-ou | Holds the landing zone account that deploys the landing-zone StackSets as the StackSets delegated admin |
DevelopmentOU | workload-dev-ou | Workload accounts for non-production environments, including the sandbox |
ProductionOU | workload-prod-ou | Workload accounts for production environments |
SuspendedOU | suspended-ou | Receives the lockdownSuspendedAccountsSCP and nests the CloseAccountOU |
SuspendedOU nests a CloseAccountOU (close-account-ou). The two model different offboarding states, covered in Suspended vs. close-account OU below.
You can rename, add, or remove OUs by editing the organizationalUnits map. The OU key (e.g. LogArchiveOU) becomes the stack output name (LogArchiveOUId) and the TypeScript property name on orgVars; changing a key is a compile-time-checked rename.
The SecurityAccount, LandingZoneAccount, and LogArchiveAccount keys are referenced by other stacks and delegated-admin config, so they set publishAccountIdOutput: true. Renaming those keys is a compile-time-checked change you will see reflected in delegated-admins.ts and the StackSet targeting.
Options
The OrganizationStructure type has a single required top-level key:
| Option | Type | Required | Description |
|---|---|---|---|
root | OrganizationRootStructure | Yes | The organization root node. Holds SCPs and the OU map. |
root.serviceControlPolicies | ServiceControlPolicy[] | No | SCPs attached at the organization root (apply to every account). Max 9 configured here when FullAWSAccess is attached; 10 total attachments. |
root.accounts | Record<string, AccountStructure> | No | Accounts attached directly to the organization root. |
root.organizationalUnits | Record<string, OrganizationalUnitStructure> | No | Map of OU key → OU definition. |
Each OrganizationalUnitStructure entry:
| Option | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name of the OU in AWS Organizations. |
serviceControlPolicies | ServiceControlPolicy[] | No | SCPs attached to this OU. Max 9 configured here when FullAWSAccess is attached; 10 total attachments. |
accounts | Record<string, AccountStructure> | No | Map of account key → account definition. |
organizationalUnits | Record<string, OrganizationalUnitStructure> | No | Nested child OUs. Nested OUs must stay within AWS Organizations depth limits. |
Each AccountStructure entry:
| Option | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name of the account in AWS Organizations. |
email | string | Yes | Root email address for the account. Must be globally unique across all of AWS. |
publishAccountIdOutput | boolean | No | Publish this account's ID as a stack output and typed orgVars.<Key>Id variable. Set it for accounts other stacks reference. Default false. |
serviceControlPolicies | ServiceControlPolicy[] | No | SCPs attached to this specific account. Max 9 configured here when FullAWSAccess is attached; 10 total attachments. |
Example
const mailDomain = landingZoneSettings.mailDomain.toLowerCase();
export const organizationStructure = {
root: {
serviceControlPolicies: [
criticalSecurityGuardrailsSCP,
protectTaggedCloudFormationStacksSCP,
denyAllOutsidePrimaryAndSecondaryRegionsSCP,
],
organizationalUnits: {
LogArchiveOU: {
name: 'log-ou',
accounts: {
LogArchiveAccount: {
name: 'logarchive-account',
email: `aws+log@${mailDomain}`,
publishAccountIdOutput: true,
},
},
},
SecurityOU: {
name: 'security-ou',
accounts: {
SecurityAccount: {
serviceControlPolicies: [protectSecurityHubConfigurationSCP],
name: 'security-account',
email: `aws+security@${mailDomain}`,
publishAccountIdOutput: true,
},
},
},
InfrastructureOU: {
name: 'infrastructure-ou',
accounts: {
LandingZoneAccount: {
name: 'landingzone-account',
email: `aws+landingzone@${mailDomain}`,
publishAccountIdOutput: true,
},
},
},
DevelopmentOU: {
name: 'workload-dev-ou',
accounts: {
SandboxAccount: {
name: 'Sandbox',
email: `sandbox@${mailDomain}`,
},
},
},
ProductionOU: {
name: 'workload-prod-ou',
accounts: {
WorkloadAlphaAccount: {
name: 'Workload Alpha',
email: `aws+workload-alpha@${mailDomain}`,
},
WorkloadBetaAccount: {
name: 'Workload Beta',
// Account emails don't have to use the organization mail domain;
// any unique, valid address works.
email: 'team-beta+aws@example.org',
},
},
},
SuspendedOU: {
name: 'suspended-ou',
serviceControlPolicies: [lockdownSuspendedAccountsSCP],
organizationalUnits: {
// Moving an account here triggers the close-account automation.
CloseAccountOU: {
name: 'close-account-ou',
},
},
},
},
},
} satisfies OrganizationStructure;
export type LandingZoneOrganizationStructure = typeof organizationStructure;
The OU and account name fields are the display names shown in the AWS Organizations console; they are plain strings, not derived from organizationName. The map keys (LogArchiveOU, SecurityAccount) are what become the typed orgVars.<Key>Id outputs.
Suspended vs. close-account OU
The structure distinguishes two offboarding states, and the CloseAccountOU nests inside SuspendedOU on purpose.
SuspendedOUisolates an account while keeping it. ThelockdownSuspendedAccountsSCPdenies every action except an allowlist of roles, and the OU is excluded from Security Hub CSPM, GuardDuty, and Macie member management. Use it to quarantine an account without deleting anything.CloseAccountOUpermanently closes an account. When this OU exists in the structure, the organization phase (LandingZoneOrganizationStack, in the management account) closes its active accounts every 8 hours. It inherits the lockdown SCP and security-service exclusion fromSuspendedOU. Accounts waiting for closure remain locked down; failed closures are retried on the next run.
Closed accounts remain visible in the OU with SUSPENDED status for the 90-day post-closure window. If you do not want the automation at all, remove the CloseAccountOU from the structure; suspended accounts then simply stay isolated.
How it's used
The OrganizationConstruct in the organization phase (LandingZoneOrganizationStack) reads this object and:
- Creates the AWS Organization if it does not exist
- Creates OUs and accounts as specified
- Attaches SCPs at root, OU, and account levels
- Publishes every OU ID as a
<Key>Idstack output, plus an account<Key>Idoutput for each account markedpublishAccountIdOutput: true
The landing-zone stacks run from the landing zone account and read those outputs cross-account at deploy time, exposed to them as typed orgVars properties. LandingZoneOrganizationStack just has to be deployed before the stacks that consume its outputs.
Things to know
- Account email addresses must be globally unique across all of AWS. The
aws+<alias>@<domain>pattern is a reliable way to generate unique addresses under a single domain. - AWS Organizations limits SCPs to 10 directly attached per attachment point (root, OU, or account), raised from 5 to 10 in May 2026. Inherited SCPs do not count against a child's own quota.
OrganizationConstructvalidates the structure at synth time: it fails when a node configures more than 10 SCPs or a policy document exceeds 10,240 characters. This check excludesFullAWSAccessand other policies attached outside the configuration. WithFullAWSAccessattached, configure at most 9 additional SCPs at that attachment point, and check existing attachments before deploying. - Account IDs are opt-in. OU IDs are always published, but an account only gets a
<Key>Idoutput (and anorgVars.<Key>Idproperty) when you setpublishAccountIdOutput: true. Reserve it for accounts other stacks reference by ID, such as the log archive and security accounts. A StackSet that targets a whole OU never needs one. Keep published outputs within the CloudFormation per-stack output limit. - Renaming an account or OU key (e.g.
WorkloadAlphaAccount→AppAlphaAccount) changes the stack output name and breaks every stack that referencesorgVars.WorkloadAlphaAccountId. The TypeScript typeLandingZoneOrganizationStructuremakes these renames compile-time errors, so you'll catch them before deploy. - Removing an account from the structure does not close it. AWS Organizations does not allow programmatic account deletion, and the structure only manages membership. To decommission an account, move it to
SuspendedOUto quarantine it, or toCloseAccountOUto have the close-account automation shut it down permanently.