Manages the lifecycle of a single Fabric folder (
fabric_folder) nested under a workspace or another folder, targeting themicrosoft/fabricprovider~> 1.12.0.
- Creates and manages exactly one Fabric folder (
fabric_folder.this) nested under a workspace, or under another folder viaparent_folder_id. - Defaults to workspace-root placement (
parent_folder_id = null) — the secure/least-surprise default. - Changing
parent_folder_idon an existing folder moves it in place rather than replacing it. - Emits the folder
idthat downstream item modules (lakehouse, notebook, warehouse, variable-library, etc.) consume asfolder_id, or that anotherfabric_folderinstance consumes asparent_folder_idto build nested hierarchies.
💡 Why it matters: since this provider has no
tagsargument anywhere onfabric_folder, folder hierarchy is the primary governance grouping mechanism this library has for Fabric items — araw/+curated/split under a medallion-architecture workspace is a real access-boundary and discovery aid, not just cosmetic organization.
⚠️ fabric_folderis a PREVIEW resource in the live v1.12.0 schema (confirmed via the terraform-registry MCP, the live provider schema, provider_doc_id12766471). The caller's rootprovider "fabric" { preview = true }must be set, or every apply of this module fails/no-ops against a provider that silently doesn't expose this resource. This module never setspreviewitself — provider-level toggles are strictly the caller's concern per this module suite's code-standard convention.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart TB
Workspace["tf-mod-fabric-workspace"]:::keystone
Folder["tf-mod-fabric-folder\n(this module)"]:::thisModule
Lakehouse["tf-mod-fabric-lakehouse"]:::sibling
Notebook["tf-mod-fabric-notebook"]:::sibling
Warehouse["tf-mod-fabric-warehouse"]:::sibling
VarLib["tf-mod-fabric-variable-library"]:::sibling
Workspace -->|"workspace_id"| Folder
Folder -.->|"parent_folder_id (nested subfolder)"| Folder
Folder -->|"folder_id"| Lakehouse
Folder -->|"folder_id"| Notebook
Folder -->|"folder_id"| Warehouse
Folder -->|"folder_id"| VarLib
classDef thisModule fill:#0F6CBD,color:#FFFFFF,stroke:#0F6CBD
classDef keystone fill:#143551,color:#FFFFFF,stroke:#143551
classDef sibling fill:#E8EAED,color:#1A1A1A,stroke:#B0B7BF
This module requires workspace_id from tf-mod-fabric-workspace and can reference another instance of
itself via parent_folder_id to build nested folder hierarchies (up to the provider's 10-level nesting
limit). Item-type modules that accept an optional folder_id consume this module's id output.
flowchart TB
subgraph Inputs["Inputs"]
WorkspaceId["workspace_id"]
DisplayName["display_name"]
ParentFolderId["parent_folder_id"]
Timeouts["timeouts"]
end
Keystone["fabric_folder.this"]:::thisModule
WorkspaceId --> Keystone
DisplayName --> Keystone
ParentFolderId --> Keystone
Timeouts --> Keystone
Keystone -->|"id"| OutId["id output"]
Keystone -->|"echoed"| OutWs["workspace_id output"]
Keystone -->|"echoed"| OutParent["parent_folder_id output"]
classDef thisModule fill:#0F6CBD,color:#FFFFFF,stroke:#0F6CBD
Resource inventory: 1 resource — fabric_folder.this (single instance, the sole keystone).
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
| Provider | microsoft/fabric ~> 1.12.0 |
| Provider configuration | None in this module — the caller configures provider "fabric" {} (auth, tenant, preview = true) at the root |
Schema notes that bite (confirmed against the live v1.12.0 schema via the terraform-registry MCP,
the live provider schema, provider_doc_id 12766471):
- The
fabric_folderresource itself is in preview, not just a recursive-listing data source. Every apply requires the caller's root provider block to setpreview = true; a caller running under SPN-only auth without that flag will see this resource fail or silently no-op, not a clean permission error.<VERIFY: reconfirm GA/preview status against the live schema before every authoring session — the provider ships roughly every two weeks and this resource could have reached GA since this pass>. - Folder names must meet Fabric's folder name requirements: no leading/trailing spaces, no C0/C1 control
codes, excludes the characters
~"#.&*:<>?/{|}, must not be one of the reserved names$recycle.bin,recycled, orrecycler(case-insensitive), and must be unique among sibling folders under the same parent (that uniqueness constraint is enforced by the Fabric API at apply time, not by this module's variable validation). parent_folder_idis not ForceNew — changing it on an existing folder moves the folder to a different parent in place. This is explicitly demonstrated as a supported update in the provider's own Example Usage, confirmed directly frommain.tf/the live schema captured during authoring — do not treat it as immutable.workspace_idis ForceNew — changing it replaces the folder.timeoutsis a Terraform Plugin Framework single nested Attributes object (consistent with the pattern confirmed onfabric_workspace), assigned directly with=inmain.tf, never adynamic "timeouts"block.- The provider enforces a maximum folder-nesting depth (10 levels) at apply time — this module has no variable-level guard against exceeding it.
(sourced from this module's lightweight SCOPE.md — do not let this drift independently)
- Grounded via Microsoft Learn ("Create folders in workspaces" → Permission model): folders inherit
workspace permissions. Admin, Member, and Contributor workspace roles can create, modify, and delete
folders; Viewer can only view the folder hierarchy — Viewer cannot call this module's
applysuccessfully. - Entra: the calling Service Principal (or Managed Identity) must be included in the tenant's allowed security group under the "Service principals can call Fabric public APIs" Developer setting (see Microsoft Fabric Prerequisites below) — this is universal across every Fabric module in this library.
- Preview mode must be enabled on the caller's root
provider "fabric" { preview = true }block — this is a provider-level, caller-only concern this module can never set for the caller. - The tenant-level "Service principals can call Fabric public APIs" Developer setting must be enabled
and scoped to include this module's caller SPN/MSI — a Fabric Admin Portal setting entirely outside
Terraform's control, flipped once per tenant, before any non-interactive
applywill succeed. - No Fabric trial/license prerequisite beyond the workspace itself already existing and being reachable by the calling principal at the role level described above.
tf-mod-fabric-folder/
├── providers.tf # required_providers (fabric ~> 1.12.0), no provider {} block
├── variables.tf # display_name, workspace_id, parent_folder_id, timeouts
├── main.tf # fabric_folder.this — the sole keystone
├── outputs.tf # id, display_name, workspace_id, parent_folder_id
├── README.md # this file
├── SCOPE.md # lightweight cross-module contract (standalone)
└── examples/
├── basic/ # workspace-root folder (parent_folder_id = null)
└── complete/ # medallion raw/ + curated/ split with a nested bronze/ subfolder
# Caller's root module configures the provider — never this module. `preview = true` is REQUIRED for
# fabric_folder or every apply fails/no-ops.
provider "fabric" {
preview = true
tenant_id = var.tenant_id
client_id = var.client_id
client_secret = var.client_secret # sourced from Key Vault / pipeline secret, never literal
}
module "folder_raw" {
source = "git::https://github.com/microsoftexpert/tf-mod-fabric-folder.git?ref=v1.0.0"
workspace_id = module.workspace.id # tf-mod-fabric-workspace's `id` output
display_name = "raw"
}Consumes
| Input | Type | Source module |
|---|---|---|
workspace_id |
string |
tf-mod-fabric-workspace's id output |
parent_folder_id |
string (optional) |
A sibling fabric_folder instance's id output (this same module, invoked again) |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Folder GUID | Downstream item modules' folder_id input; another fabric_folder instance's parent_folder_id |
display_name |
Folder display name, echoed | Informational / cross-referencing |
workspace_id |
Workspace GUID, echoed | Downstream chaining |
parent_folder_id |
Parent folder GUID, echoed (null at workspace root) | Informational |
⚠️ Every example below assumes the caller's rootprovider "fabric" { preview = true }is already set —fabric_folderis a preview-only resource and every apply fails/no-ops without it.
1 · Workspace-root folder
The secure default shape — parent_folder_id omitted, so the folder lands at the workspace root.
module "folder_raw" {
source = "../../"
workspace_id = module.workspace.id
display_name = "raw"
}2 · One level of nesting
module "folder_raw" {
source = "../../"
workspace_id = module.workspace.id
display_name = "raw"
}
module "folder_raw_bronze" {
source = "../../"
workspace_id = module.workspace.id
display_name = "bronze"
parent_folder_id = module.folder_raw.id
}3 · Full medallion raw/ + curated/ split
💡 Folder hierarchy is the primary governance grouping mechanism here — this provider has no
tagsargument onfabric_folder.
module "folder_raw" {
source = "../../"
workspace_id = module.workspace.id
display_name = "raw"
}
module "folder_curated" {
source = "../../"
workspace_id = module.workspace.id
display_name = "curated"
}4 · Nested subfolders under both raw/ and curated/
module "folder_raw_bronze" {
source = "../../"
workspace_id = module.workspace.id
display_name = "bronze"
parent_folder_id = module.folder_raw.id
}
module "folder_curated_gold" {
source = "../../"
workspace_id = module.workspace.id
display_name = "gold"
parent_folder_id = module.folder_curated.id
}5 · Moving a folder by changing parent_folder_id
ℹ️ This is an in-place move, not a ForceNew replacement — confirmed against the live schema's Example Usage. The folder's
idstays stable across the move.
# Before: nested under "raw"
module "folder_staging" {
source = "../../"
workspace_id = module.workspace.id
display_name = "staging"
parent_folder_id = module.folder_raw.id
}
# After: re-point parent_folder_id to "curated" — Terraform moves the folder in place, no replacement.
# module "folder_staging" {
# source = "../../"
#
# workspace_id = module.workspace.id
# display_name = "staging"
# parent_folder_id = module.folder_curated.id
# }6 · Environment-qualified folder naming
module "folder_raw_dev" {
source = "../../"
workspace_id = module.workspace_dev.id
display_name = "raw-dev"
}7 · Naming edge case — maximum length (255 chars)
module "folder_long_name" {
source = "../../"
workspace_id = module.workspace.id
display_name = "curated-finance-quarterly-close-reporting-extract-staging-area-for-downstream-consumption-by-the-analytics-team-and-external-auditors-pending-final-sign-off-from-the-controller-group-before-publication-to-the-executive-dashboard-suite-v2"
}8 · Naming edge case — reserved-name avoidance
🔒
display_namevalues of$recycle.bin,recycled, orrecycler(case-insensitive) are rejected atterraform validatetime by this module's ownvalidation {}block — before any call ever reaches the Fabric API.
module "folder_archive" {
source = "../../"
workspace_id = module.workspace.id
display_name = "archive" # NOT "recycled" — that value fails validation intentionally
}9 · Custom timeouts
module "folder_raw" {
source = "../../"
workspace_id = module.workspace.id
display_name = "raw"
timeouts = {
create = "10m"
}
}10 · Multiple sibling folders via caller-side for_each
locals {
domain_folders = ["raw", "curated", "sandbox"]
}
module "domain_folders" {
source = "../../"
for_each = toset(local.domain_folders)
workspace_id = module.workspace.id
display_name = each.value
}11 · Three-level nesting (raw / bronze / vendor-a)
module "folder_raw" {
source = "../../"
workspace_id = module.workspace.id
display_name = "raw"
}
module "folder_raw_bronze" {
source = "../../"
workspace_id = module.workspace.id
display_name = "bronze"
parent_folder_id = module.folder_raw.id
}
module "folder_raw_bronze_vendor_a" {
source = "../../"
workspace_id = module.workspace.id
display_name = "vendor-a"
parent_folder_id = module.folder_raw_bronze.id
}12 · Sandbox folder isolated from governed hierarchy
module "folder_sandbox" {
source = "../../"
workspace_id = module.workspace.id
display_name = "sandbox-experiments"
# parent_folder_id intentionally omitted — kept flat at workspace root, distinct from the governed
# raw/curated hierarchy.
}13 · Per-team subfolder under a shared curated/ folder
module "folder_curated_finance" {
source = "../../"
workspace_id = module.workspace.id
display_name = "finance"
parent_folder_id = module.folder_curated.id
}
module "folder_curated_claims" {
source = "../../"
workspace_id = module.workspace.id
display_name = "claims"
parent_folder_id = module.folder_curated.id
}14 · Renaming a folder in place
ℹ️
display_nameis not ForceNew per the live schema — changing it renames the folder without replacement, subject to the same name-uniqueness-among-siblings constraint enforced by the Fabric API.
module "folder_raw" {
source = "../../"
workspace_id = module.workspace.id
display_name = "raw-v2" # renamed from "raw"
}15 · 🏗️ End-to-end composition
Wires tf-mod-fabric-workspace into this module's workspace_id, builds a medallion raw/ folder, and
feeds this module's id into tf-mod-fabric-lakehouse's folder_id.
provider "fabric" {
preview = true
tenant_id = var.tenant_id
client_id = var.client_id
client_secret = var.client_secret
}
module "workspace" {
source = "git::https://github.com/microsoftexpert/tf-mod-fabric-workspace.git?ref=v1.0.0"
display_name = "ws-core-bronze-prod"
description = "Bronze-layer medallion workspace, core domain — prod."
}
module "folder_raw" {
source = "git::https://github.com/microsoftexpert/tf-mod-fabric-folder.git?ref=v1.0.0"
workspace_id = module.workspace.id
display_name = "raw"
}
module "lakehouse_raw" {
source = "git::https://github.com/microsoftexpert/tf-mod-fabric-lakehouse.git?ref=v1.0.0"
workspace_id = module.workspace.id
folder_id = module.folder_raw.id
display_name = "lh-claims-raw-prod"
}| Variable | Type | Default | Notes |
|---|---|---|---|
display_name |
string |
— (required) | Max 255 chars; excluded characters and reserved names enforced via validation {} |
workspace_id |
string |
— (required) | ForceNew; ties this folder to a workspace |
parent_folder_id |
string |
null |
Not ForceNew — changing it moves the folder |
timeouts |
object({...}) |
null |
Applies to fabric_folder.this |
Full object schema
variable "timeouts" {
type = object({
create = optional(string)
read = optional(string)
update = optional(string)
delete = optional(string)
})
default = null
}| Output | Description | Sensitive |
|---|---|---|
id |
Folder GUID | — |
display_name |
Folder display name, echoed | — |
workspace_id |
Workspace GUID, echoed | — |
parent_folder_id |
Parent folder GUID, echoed (null at workspace root) | — |
fabric_folderis gated entirely behind the caller's root providerpreview = truetoggle — this module has no way to detect or enforce that at plan time; a caller who forgets it will see either an apply-time failure or a silent no-op, not a clean, actionable error.parent_folder_idchanges are in-place moves, not replacements — this is a deliberate divergence from how many other hierarchical resources behave, confirmed directly against the live provider schema's own Example Usage rather than assumed from convention.workspace_idis ForceNew — moving a folder to a different workspace is not supported; it must be destroyed and recreated under the new workspace.- The provider enforces a maximum folder-nesting depth of 10 levels and folder-name uniqueness among
siblings at apply time — neither constraint is (or can be) fully expressed in this module's
validation {}blocks, which catch only the syntactic name-format rules (length, excluded characters, reserved names, leading/trailing whitespace). timeoutsis assigned directly with=(Plugin Framework Attributes object), consistent with the same pattern confirmed onfabric_workspace.
| Concern | Safe default | Opt-out |
|---|---|---|
| Folder placement | parent_folder_id = null — new folders land at the workspace root |
Caller supplies parent_folder_id to nest under a governed folder hierarchy |
| Preview-only resource | Module never assumes preview mode is enabled — states the requirement prominently rather than silently failing | N/A — caller's root provider block must set preview = true |
cd C:\GitHubCode\newfabricmodules\tf-mod-fabric-folder
terraform init -backend=false
terraform validate
terraform fmt -checkPin consumers to ?ref=v1.0.0 — never a branch. This module is plan-only; a human applies from a reviewed,
approved CI pipeline.
terraform validatecatches: adisplay_namethat's empty, too long, has leading/trailing whitespace, contains an excluded character, or matches a reserved name; a malformedtimeoutstype.terraform fmt -checkcatches formatting drift without silently rewriting files.- Not caught offline: whether the caller's provider block actually has
preview = trueset, whether the calling principal has Admin/Member/Contributor role on the target workspace, whether the tenant's "Service principals can call Fabric public APIs" setting is enabled, folder-name uniqueness among siblings, and the 10-level nesting-depth limit. These require a liveplan/applyagainst a real tenant with preview mode enabled.
module.folder_raw.id = "3c9e1f2a-7b4d-4e6a-9f1c-2d5e8a7b9c0d"
module.folder_raw.display_name = "raw"
module.folder_raw.workspace_id = "8f4e2c1a-9b3d-4a7e-b2f1-6c5d8e9f0a1b"
module.folder_raw.parent_folder_id = null
| Symptom | Cause | Fix |
|---|---|---|
apply fails / resource not found |
Preview mode not enabled on caller's root provider block | Set provider "fabric" { preview = true } at the root |
apply fails with a 403/permission error |
Calling principal has only Viewer role on the workspace | Grant Admin, Member, or Contributor role on the workspace |
apply fails with a naming conflict |
Two sibling folders under the same parent share a display_name |
Rename one; the Fabric API enforces uniqueness among siblings, not this module |
terraform validate rejects display_name |
Value contains an excluded character, a reserved name, or leading/trailing whitespace | Adjust the name per the folder name requirements documented in Provider/Versions |
Folder unexpectedly moved on apply |
parent_folder_id was changed intentionally or by drift |
Confirm this is expected — it's an in-place move, not a bug; revert parent_folder_id if unintended |
| Entire module fails on every resource, every environment | Tenant-level "Service principals can call Fabric public APIs" setting not enabled | Have a Fabric administrator flip the Developer setting in the Fabric Admin Portal |
fabric_folderprovider resource docs- Microsoft Learn — Create folders in workspaces
- Sibling module:
tf-mod-fabric-workspace - This module's
SCOPE.md