# How Docuseal Template Folders Work: Organization and Hierarchy Management

> Learn how Docuseal template folders organize your documents with a hierarchical structure. Discover account scoping, default folders, and parent child naming conventions for efficient management.

- Repository: [DocuSeal/docuseal](https://github.com/docusealco/docuseal)
- Tags: how-to-guide
- Published: 2026-05-05

---

**Docuseal uses a self-referencing hierarchy of template folders scoped to accounts, with a default "Default" folder and a service module (`TemplateFolders`) that handles creation, nesting, and assignment via the "Parent / Child" naming convention.**

Docuseal organizes electronic document templates within a flexible folder system that supports arbitrary nesting levels. Each account maintains its own tree of **template folders**, with every template belonging to exactly one folder (falling back to a system-generated default). This article examines the database schema, service logic, and controller implementations that power folder management in the open-source Docuseal repository.

## Database Architecture and Relationships

The folder system rests on three core Active Record associations defined across [`app/models/account.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/account.rb), [`app/models/template_folder.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/template_folder.rb), and [`app/models/template.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/template.rb).

### Account-Level Folder Ownership

Each **Account** maintains its own folder namespace through the following associations:

- `has_many :template_folders` – Standard collection of all folders owned by the account.
- `has_one :default_template_folder` – A lazy-loaded special folder named *Default* (matching the constant `TemplateFolder::DEFAULT_NAME`) created automatically when first accessed.

The default folder is instantiated in `Account#default_template_folder` (lines 68-71 of [`app/models/account.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/account.rb)) and serves as the catch-all location for uncategorized templates.

### Self-Referencing Folder Hierarchy

**TemplateFolder** implements a tree structure through self-referential associations:

- `belongs_to :account` – Ensures folder isolation between accounts.
- `belongs_to :parent_folder, class_name: 'TemplateFolder', optional: true` – Enables arbitrary depth nesting.

Database migrations [`20230920202947_add_parent_folder_id_to_template_folders.rb`](https://github.com/docusealco/docuseal/blob/main/20230920202947_add_parent_folder_id_to_template_folders.rb) and [`20230922070555_add_folder_id_to_templates.rb`](https://github.com/docusealco/docuseal/blob/main/20230922070555_add_folder_id_to_templates.rb) established the foreign keys required for this hierarchy (`parent_folder_id` and `folder_id` respectively).

### Template-to-Folder Assignment

The **Template** model links to folders through `belongs_to :folder, class_name: 'TemplateFolder', optional: true` (line 94 of [`app/models/template.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/template.rb)). While the database column `folder_id` is nullable, initialization callbacks ensure a folder is always assigned:

```ruby
self.folder ||= account.default_template_folder

```

This guarantees every template resides in a folder, even if the user did not explicitly select one during creation.

## The TemplateFolders Service Module

All business logic for folder manipulation lives in [`lib/template_folders.rb`](https://github.com/docusealco/docuseal/blob/main/lib/template_folders.rb). This module provides query helpers, sorting routines, and the primary factory method for folder creation.

### Creating Nested Folders by Path

The `TemplateFolders.find_or_create_by_name` method accepts a user (author), an account, and a string name that may include a path separator (` / `):

```ruby
module TemplateFolders
  module_function

  def find_or_create_by_name(author, name)
    return author.account.default_template_folder if name.blank? || name == TemplateFolder::DEFAULT_NAME

    parent_name, child_name = name.to_s.split(' / ', 2).map(&:squish)

    parent_folder = if child_name.present?
                      author.account.template_folders.create_with(author:)
                                              .find_or_create_by(name: parent_name, parent_folder_id: nil)
                    end

    author.account.template_folders.create_with(author:)
                                    .find_or_create_by(name: child_name || parent_name, parent_folder: parent_folder)
  end
end

```

**Key behaviors:**
- **Blank or default names** immediately return the account's default folder.
- **Hierarchical parsing** splits strings like `"Marketing / Campaigns"` to locate or create the parent first, then the child.
- **Idempotency** uses `find_or_create_by` to prevent duplicate folders at the same level.

### Supporting Query Methods

The module also exposes `search` (case-insensitive folder matching), `sort` (ordering logic for UI display), and scopes like `active` used by controllers to render folder trees efficiently.

## Controller Actions and UI Integration

The folder interface is exposed through `TemplateFoldersController` and `TemplateFoldersAutocompleteController`, both delegating heavy lifting to the service module.

### Displaying Folder Contents

The `TemplateFoldersController#show` action (lines 11-22 of [`app/controllers/template_folders_controller.rb`](https://github.com/docusealco/docuseal/blob/main/app/controllers/template_folders_controller.rb)) handles folder display:

1. Loads the target folder via `TemplateFolder.find(params[:id])`.
2. Calls `TemplateFolders.search` and `TemplateFolders.sort` to filter templates based on UI query parameters.
3. Falls back to displaying only sub-folders when no templates match the current filter.

This design keeps the database queries centralized in the service layer while the controller manages pagination and view rendering.

### Renaming and Protection

The `update` action explicitly guards the default folder:

```ruby
def update
  @template_folder = current_account.template_folders.find(params[:id])
  return head(:forbidden) if @template_folder.default?

  @template_folder.update!(name: params[:name])
  # ...

end

```

Attempting to rename the *Default* folder returns HTTP 403, preserving the integrity of the fallback location.

### Live Search Endpoint

`TemplateFoldersAutocompleteController` provides JSON responses for type-ahead folder selection in the UI. It delegates to `TemplateFolders.search`, allowing users to locate deeply nested folders without traversing the entire tree manually.

## Template Assignment and Defaults

Templates acquire their folder assignments through multiple touchpoints, ensuring consistency across creation methods.

### Creation via API Tools

When templates are generated programmatically through [`lib/mcp/tools/create_template.rb`](https://github.com/docusealco/docuseal/blob/main/lib/mcp/tools/create_template.rb), the service explicitly passes `folder: account.default_template_folder` unless another folder object is supplied. This mirrors the model-level callback behavior.

### Cloning Operations

The clone service ([`lib/templates/clone.rb`](https://github.com/docusealco/docuseal/blob/main/lib/templates/clone.rb), lines 8-22) preserves the original folder unless the caller provides a `folder_name` parameter. When a new name is provided, it invokes `TemplateFolders.find_or_create_by_name` to resolve or instantiate the target hierarchy:

```ruby
folder = if folder_name.present?
           TemplateFolders.find_or_create_by_name(author, folder_name)
         else
           template.folder
         end

```

### Cascade Deletion

`TemplateFolder` associations include `dependent: :destroy` for both templates and subfolders. Deleting a parent folder recursively removes all nested content, preventing orphaned records.

## Code Examples

### Creating a Nested Folder Structure

```ruby

# Create "Marketing" with child "Campaigns"

folder = TemplateFolders.find_or_create_by_name(
  current_user,
  "Marketing / Campaigns"
)

puts folder.full_name

# => "Marketing / Campaigns"

puts folder.parent_folder.name

# => "Marketing"

```

### Moving a Template to a Different Folder

```ruby
template = Template.find(params[:id])
target_folder = TemplateFolders.find_or_create_by_name(
  current_user,
  "Legal / Contracts"
)

template.update!(folder: target_folder)

```

### Listing Active Folders for an Account

```ruby
current_user.account.template_folders.active.each do |folder|
  puts "#{folder.id}: #{folder.full_name}"
end

```

### Fetching Folders via Frontend Autocomplete

```javascript
fetch('/template_folders_autocomplete?term=Marketing')
  .then(response => response.json())
  .then(folders => {
    folders.forEach(folder => {
      console.log(folder.name, folder.id);
    });
  });

```

## Summary

- **Template folders** in Docuseal form a self-referencing tree structure scoped to individual accounts via `parent_folder_id`.
- **Every account** maintains a protected *Default* folder that catch-uncategorized templates and cannot be renamed or deleted.
- **The `TemplateFolders` service module** handles path parsing (using ` / ` as delimiter), creation, and querying, keeping controllers thin.
- **Templates** always belong to a folder due to initialization callbacks in [`app/models/template.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/template.rb) and defaults set in creation services.
- **Deletion cascades** through `dependent: :destroy` associations, ensuring subfolders and templates are cleaned up when a parent is removed.

## Frequently Asked Questions

### Can template folders be nested indefinitely?

Yes. The `TemplateFolder` model uses a self-referential `belongs_to :parent_folder` association with an optional foreign key (`parent_folder_id`). The database schema imposes no hard limit on nesting depth, allowing you to create arbitrary hierarchies like `"Client / 2024 / Q1 / Contracts"` through the `TemplateFolders.find_or_create_by_name` method.

### What happens if I don't specify a folder when creating a template?

The template automatically assigns to the account's *Default* folder. This occurs in two places: the `CreateTemplate` service ([`lib/mcp/tools/create_template.rb`](https://github.com/docusealco/docuseal/blob/main/lib/mcp/tools/create_template.rb)) explicitly passes `account.default_template_folder`, and the `Template` model callback ([`app/models/template.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/template.rb)) executes `self.folder ||= account.default_template_folder` during initialization.

### Can I rename or delete the Default folder?

No. The `TemplateFoldersController#update` action checks `@template_folder.default?` and returns HTTP 403 Forbidden if you attempt to rename it. Similarly, the default folder is protected from deletion because the `TemplateFolder::DEFAULT_NAME` constant is used as a fallback throughout the codebase when folder names are blank.

### What happens to templates when their folder is deleted?

All templates within the deleted folder are destroyed along with any nested subfolders. The `TemplateFolder` model defines `has_many :templates, dependent: :destroy` and `has_many :subfolders, class_name: 'TemplateFolder', dependent: :destroy`. This cascade ensures that removing a parent folder purges its entire contents, preventing orphaned template records.