# Docuseal Template Model Structure: Understanding Fields, Schema, and Submitters

> Understand the Docuseal Template model structure. Learn how fields, schema, and submitters are defined using JSON-serialized nested arrays for form inputs, attachments, and signers.

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

---

**The Template model in docusealco/docuseal stores `fields`, `schema`, and `submitters` as JSON-serialized text columns, using nested array-of-hash structures to define form inputs, document attachments, and signing parties.**

The `Template` model serves as the core schema definition for multi-party document workflows in the Docuseal open-source repository. It persists three distinct JSON structures within single database rows, enabling flexible, complex form configurations without rigid relational schemas. Understanding how these serialized attributes interact is essential for customizing templates via the API or Ruby SDK.

## Database Storage and JSON Serialization

In [`app/models/template.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/template.rb), the three core attributes are defined as string columns with JSON serialization. This design choice allows arbitrary nesting while maintaining relational database compatibility.

```ruby
attribute :fields,    :string, default: -> { [] }
attribute :schema,    :string, default: -> { [] }
attribute :submitters,:string, default: -> { [{ name: I18n.t(:first_party), uuid: SecureRandom.uuid }] }

serialize :fields,    coder: JSON
serialize :schema,    coder: JSON
serialize :submitters,coder: JSON

```

*Source: [[`app/models/template.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/template.rb)](https://github.com/docusealco/docuseal/blob/master/app/models/template.rb#L52-L63)*

All three columns use `text` database types with non-null constraints. The **submitters** attribute defaults to a single "First Party" entry containing a localized name and generated UUID, ensuring every template initializes with at least one signing role.

## The Three Core Structures

The Template model organizes document logic into three distinct domains: input fields, file attachments, and participant definitions.

### Fields: Form Input Definitions

The **fields** array contains hashes representing individual form inputs placed on the document canvas. Each field hash requires specific keys to function within the Docuseal rendering engine:

- **Identifiers**: `uuid`, `submitter_uuid` (links to the signing party), `name`
- **Type Configuration**: `type` (text, date, signature, etc.), `required`, `readonly`, `default_value`
- **Display**: `title`, `description`, `prefillable`
- **Validation Rules**: Nested `validation` hash with `message`, `pattern`, `min`, `max`, `step`
- **Layout**: `areas` array specifying placement via `x`, `y`, `w`, `h`, `page`, and `attachment_uuid`
- **Logic**: `conditions` array for conditional visibility, `options` array for multi-select fields

Fields reference a `submitter_uuid` to determine which party completes the input during the signing workflow.

### Schema: Document Attachments

The **schema** array defines static and dynamic documents attached to the template. Each entry represents a file that parties will review or sign:

- **File Sources**: `attachment_uuid` (internal reference), `google_drive_file_id` (external integration)
- **Metadata**: `name`, `dynamic` (boolean indicating generated vs. static content)
- **Conditional Logic**: `conditions` array controlling document visibility based on field values

Schema entries are referenced by field `areas` when placing input fields on specific document pages.

### Submitters: Signing Parties

The **submitters** array defines the sequence and roles of participants:

- **Identity**: `name`, `uuid` (primary identifier)
- **Workflow**: `order` (integer for sequencing), `is_requester` (boolean flag)
- **Linking**: `linked_to_uuid`, `invite_via_field_uuid` (for complex multi-party routing)
- **Invitations**: `email`, `invite_by_uuid`, `optional_invite_by_uuid`

The default initializer creates one submitter with `I18n.t(:first_party)` to support immediate single-party use cases.

## Strong Parameters and API Structure

The exact structure expected by controllers is enforced in [`app/controllers/templates_controller.rb`](https://github.com/docusealco/docuseal/blob/main/app/controllers/templates_controller.rb) through a comprehensive strong-parameter whitelist:

```ruby
params.require(:template).permit(
  :name,
  { schema: [[:attachment_uuid, :google_drive_file_id, :name, :dynamic,
              { conditions: [%i[field_uuid value action operation]] }]],
    submitters: [%i[name uuid is_requester linked_to_uuid invite_via_field_uuid
                  invite_by_uuid optional_invite_by_uuid email order]],
    fields: [[:uuid, :submitter_uuid, :name, :type,
              :required, :readonly, :default_value,
              :title, :description, :prefillable,
              { preferences: {},
                default_value: [],
                conditions: [%i[field_uuid value action operation]],
                options: [%i[value uuid]],
                validation: %i[message pattern min max step],
                areas: [%i[uuid x y w h cell_w attachment_uuid option_uuid page]] }]] }
)

```

*Source: [[`app/controllers/templates_controller.rb`](https://github.com/docusealco/docuseal/blob/main/app/controllers/templates_controller.rb)](https://github.com/docusealco/docuseal/blob/master/app/controllers/templates_controller.rb#L103-L118)*

This whitelist mirrors the JSON schema required for API requests, ensuring that nested hashes for validation rules, conditional logic, and geometric areas pass through to the model.

## Practical Implementation Examples

### Creating Templates via Ruby SDK

When building templates programmatically, match the structure defined in the controller whitelist:

```ruby
template = Docuseal::Template.create!(
  name: "Employee Agreement",
  fields: [
    {
      uuid: SecureRandom.uuid,
      submitter_uuid: "party-1-uuid",
      name: "start_date",
      type: "date",
      required: true,
      readonly: false,
      default_value: "",
      title: "Employment Start Date",
      description: "First day of employment",
      prefillable: true,
      preferences: {},
      validation: { pattern: "^\\d{4}-\\d{2}-\\d{2}$", message: "Use YYYY-MM-DD format" },
      areas: [
        { uuid: SecureRandom.uuid, x: 100, y: 200, w: 150, h: 25, page: 1, attachment_uuid: "doc-uuid" }
      ]
    }
  ],
  schema: [
    {
      attachment_uuid: "doc-uuid",
      name: "Contract PDF",
      dynamic: false,
      conditions: []
    }
  ],
  submitters: [
    { name: "HR Representative", uuid: "party-1-uuid", email: "hr@company.com", order: 1 },
    { name: "New Employee", uuid: "party-2-uuid", email: "newhire@example.com", order: 2 }
  ]
)

```

### API JSON Payload Structure

For direct API integration, POST payloads follow the same nested structure:

```json
{
  "template": {
    "name": "Service Agreement",
    "fields": [
      {
        "uuid": "field-123",
        "submitter_uuid": "submitter-456",
        "name": "service_type",
        "type": "select",
        "required": true,
        "options": [
          { "value": "Consulting", "uuid": "opt-1" },
          { "value": "Development", "uuid": "opt-2" }
        ],
        "areas": [{ "x": 50, "y": 100, "w": 200, "h": 30, "page": 1 }]
      }
    ],
    "schema": [
      {
        "attachment_uuid": "att-789",
        "name": "Agreement Template",
        "dynamic": true,
        "conditions": [
          { "field_uuid": "field-123", "value": "Consulting", "action": "show", "operation": "is" }
        ]
      }
    ],
    "submitters": [
      {
        "name": "Service Provider",
        "uuid": "submitter-456",
        "email": "vendor@example.com",
        "is_requester": true
      }
    ]
  }
}

```

### Accessing Serialized Data

When working with existing templates in Ruby, access the deserialized structures directly:

```ruby
template = Template.find(42)

# Map fields to their assigned submitters

template.fields.each do |field|
  puts "Field '#{field['name']}' assigned to submitter #{field['submitter_uuid']}"
end

# Build submitter lookup by UUID

submitter_by_uuid = template.submitters.index_by { |s| s['uuid'] }
first_party = submitter_by_uuid[template.fields.first['submitter_uuid']]

```

## Summary

- The **Template** model uses JSON serialization to store flexible, nested data structures in `text` database columns.
- **Fields** define form inputs with validation, placement areas, and conditional logic, referencing specific submitters via UUID.
- **Schema** manages document attachments, supporting both static files and dynamic content generation with visibility conditions.
- **Submitters** maintain party metadata including sequencing, email routing, and inter-party linkage.
- The strong-parameter whitelist in [`templates_controller.rb`](https://github.com/docusealco/docuseal/blob/main/templates_controller.rb) documents the exact API contract for creating and updating templates.

## Frequently Asked Questions

### How does the Template model handle complex nested data without database migrations?

The Template model utilizes ActiveRecord's `serialize` macro with JSON coding, as defined in lines 59-63 of [`app/models/template.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/template.rb). This stores Ruby hashes and arrays as JSON text in the database, allowing the schema to evolve by updating the application code rather than running database migrations.

### What connects a field to a specific signing party?

Each field hash contains a `submitter_uuid` key that must match the `uuid` of an entry in the submitters array. When rendering the document, Docuseal filters fields by matching these UUIDs to display only the relevant inputs to each party during their signing step.

### Can schema attachments be dynamically generated rather than static files?

Yes. The **schema** array supports `dynamic: true` entries, which indicate documents generated at runtime rather than pre-uploaded PDFs. Dynamic schema entries can reference the same `attachment_uuid` used in field areas, allowing programmatic insertion of content before signing.

### Where is the complete structure for API requests documented?

The definitive reference for API JSON structures is the strong-parameter whitelist in [`app/controllers/templates_controller.rb`](https://github.com/docusealco/docuseal/blob/main/app/controllers/templates_controller.rb) lines 103-118. Additionally, the `docs/api/` directory in the repository contains TypeScript, Ruby, and Node.js examples showing practical implementations of the fields, schema, and submitters arrays.