When two stacks need to share information (a VPC ID, a subnet, an IAM role ARN), CloudFormation offers three patterns: **Outputs/Exports + Fn::ImportValue**, **nested stacks**, or **passing values as parameters**. The first is the broadest — it works across stacks anywhere in the same account/region.
## The mechanism
Producer stack: declare an output with the `Export:` field, giving it a globally-unique name within the account/region:
```yaml
Outputs:
VPCId:
Description: ID of the shared VPC
Value: !Ref MyVPC
Export:
Name: shared-network-vpc-id
```
Consumer stack: read the value with `Fn::ImportValue`:
```yaml
Resources:
MyInstance:
Type: AWS::EC2::Instance
Properties:
SubnetId: !ImportValue shared-network-subnet-a
VpcId: !ImportValue shared-network-vpc-id
```
CFN resolves the import at create/update time — there's no real-time link, but the consumer stack will fail if the export doesn't exist.
## Hard restrictions
These are not soft preferences — they cause `CREATE_FAILED`:
1. **Same Region only** — no cross-region imports. Replicate exports per region if needed.
2. **Export names are unique within an account/region** — collision = stack creation failure.
3. **Export `Name:` cannot use `Ref` or `GetAtt` that depend on a resource** — the name must be resolvable from parameters/pseudo-params at template-validate time, not from another resource's runtime ID.
4. **`Fn::ImportValue` cannot use `Ref`/`GetAtt` that depend on a resource** — same reason. You can interpolate parameters/pseudo-params via `Sub`, but not resource attributes.
## The lock-in: exports become read-only once imported
> After another stack imports an output value, you can't delete the stack that is exporting the output value or modify the exported output value. All the imports must be removed before you can delete the exporting stack or modify the output value.
This is the operational gotcha. Once a downstream stack `!ImportValue`s `shared-network-vpc-id`:
- The producer stack cannot delete or rename that export
- The producer stack cannot delete the underlying resource (because it would invalidate the export)
- To unlock, every consumer must first remove its `!ImportValue` — which often requires its own redeploy cycle
In practice this means **once exports are in production use, they become semi-permanent**. Plan export names like a public API.
## Listing exports and imports
```bash
# All exports in a region
aws cloudformation list-exports --region us-east-1
# Which stacks import a given export
aws cloudformation list-imports --region us-east-1 \
--export-name shared-network-vpc-id
```
Use `list-imports` before changing or removing any export — it's the only way to see who depends on you.
## When to use Outputs/Exports vs alternatives
| Pattern | When | Tradeoff |
|---------|------|----------|
| **Outputs/Exports + `!ImportValue`** | Sharing across **independent** stacks (different teams, different deploy cadence) | Lock-in: exports are read-only once imported |
| **Nested stacks** (`AWS::CloudFormation::Stack`) | Sharing **within** a managed group of stacks deployed together | All-or-nothing lifecycle; harder to deploy children separately |
| **Pass as parameter** | One-shot value, no semantic relationship | No lifecycle linkage; consumer must know what to pass |
The User Guide's recommendation: nested stacks for "isolate information sharing within a nested-stack group", exports for "share information with other stacks (not just within the group)".
## Naming conventions for exports
Because export names are global within an account/region, naming matters. Common conventions:
- Prefix with stack name or domain: `network-prod-vpc-id`, `iam-shared-role-arn`
- Include the region for clarity in multi-region setups (even though scoped per-region)
- Treat as a public API — never rename without a deprecation cycle
A `!Sub` template makes naming hygienic:
```yaml
Outputs:
VPCId:
Value: !Ref MyVPC
Export:
Name: !Sub "${AWS::StackName}-vpc-id"
```
This way the export name is automatically tied to the stack name — easy to find, hard to collide.
## What you cannot do
- Cross-region exports (use replication tooling if needed)
- Cross-account exports (use Resource Access Manager or pass values via parameters)
- Programmatically delete an export that's currently imported
- Reference a resource attribute in the `Export.Name` field
## Related
- [[CFN Ref vs Fn-GetAtt]]
- [[CFN Template Structure Nine Sections]]
- [[CFN Failure Rollback Behavior]]