How the Docuseal Multi-Submitter Workflow Handles Document Submissions

The Docuseal multi-submitter workflow identifies "undefined" submitters—those lacking email, invitation metadata, or role assignments—and restricts public shared links to templates containing exactly one such undefined submitter, ensuring unambiguous submission creation while supporting complex multi-party agreements through deterministic UUID assignment.

The multi-submitter workflow in Docuseal enables templates to support unlimited signers while maintaining deterministic behavior for public submissions. When sharing templates via public links, the platform must resolve which party the link represents without requiring prior email configuration. This workflow is implemented in the docusealco/docuseal repository through a validation system that filters undefined submitters and enforces single-owner semantics for shared access.

Identifying Undefined Submitters

The workflow hinges on distinguishing between defined and undefined submitters. A submitter is considered undefined when it lacks all of the following attributes: invite_by_uuid, optional_invite_by_uuid, invite_via_field_uuid, linked_to_uuid, is_requester, or email.

The Templates.filter_undefined_submitters method in lib/templates.rb (lines 74‑80) scans the template’s submitter list and returns only entries that meet this "blank" criteria. This helper is called early in the submission creation process to determine if a public link can safely generate a submission.

When a user submits via a public link, StartFormController#update orchestrates the validation. The controller first calls filter_undefined_submitters to count how many undefined submitters remain in the template.

If the count exceeds one, the request is rejected with 422 Unprocessable Content. The response includes a specific error message generated by multiple_submitters_error_message (lines 204‑210 in app/controllers/start_form_controller.rb), preventing ambiguous links that could accidentally assign the submission to the wrong party. This guard (lines 44‑48) ensures that public links only work when the submission owner is unambiguous.

Constructing the Submission

Once the guard passes—meaning exactly one undefined submitter exists—the controller proceeds to assign_submission_attributes (lines 152‑174). This method performs three critical operations:

  1. Selects the submitter UUID: It picks the UUID of the first undefined submitter (or the first submitter if none are undefined) to serve as the submission owner (@submitter.uuid).
  2. Creates the submission object: It instantiates a new Submission record containing the complete roster of template submitters (template_submitters: template.submitters).
  3. Copies defined submitters: It delegates to Submissions::AssignDefinedSubmitters (located in lib/submissions/assign_defined_submitters.rb) to copy any pre-filled submitters—those already possessing emails, roles, or other metadata—into the newly created submission.

This process ensures that while the shared link creates only one submitter record (the one filling out the form), the resulting Submission object maintains the complete multi-party chain for subsequent routing and signing.

UI Safeguards and Optional 2FA

The platform prevents users from creating invalid configurations through the Share Link settings page. In app/views/templates_share_link/show.html.erb (lines 39‑44), a conditional warning block checks if multiple undefined submitters exist; if so, it disables the shared link checkbox and displays an explanatory message.

For templates with shared_link_2fa enabled, the workflow includes an additional verification step. After the submitter record is created but before redirecting to the fill-in form, handle_require_2fa (lines 12‑28 in StartFormController) validates an OTP code. If the template preferences require 2FA and the submitted one_time_code is invalid or missing, the user is prompted for verification before proceeding.

Practical Implementation Examples

Creating a Multi-Submitter Template


# Rails console: Create a template for three parties

template = Template.create!(
  account: current_account,
  name: "Tripartite Agreement",
  submitter_count: 3,
  only_field_types: %w[text signature]
)

Behind the scenes, submitter_count generates three empty submitter hashes. When retrieved later, filter_undefined_submitters will recognize all three as undefined until emails or roles are assigned.

POST /api/v1/submissions
{
  "template_id": "c0a1e2f3-4b5c-6d7e-8f9a-bcdef0123456",
  "submitters": [
    { "email": "alice@example.com", "name": "Alice" }
  ]
}

Server processing:

  • StartFormController#update receives the payload
  • filter_undefined_submitters finds exactly one undefined submitter → passes guard
  • assign_submission_attributes assigns Alice to that UUID and builds the submission
  • Submissions::AssignDefinedSubmitters processes any additional pre-filled submitters

If the template had two undefined submitters remaining, the controller would return 422 with the message from multiple_submitters_error_message instead of creating the submission.

Enforcing Two-Factor Authentication

POST /api/v1/submissions
{
  "template_id": "c0a1e2f3-4b5c-6d7e-8f9a-bcdef0123456",
  "submitters": [{ "email": "bob@example.com" }],
  "one_time_code": "123456"
}

When template.preferences['shared_link_2fa'] is true, the controller executes handle_require_2fa. The submission proceeds only if the OTP matches the code sent to the submitter's email; otherwise, the response renders the OTP entry view.

Summary

  • Undefined submitters lack email, invite metadata, or role links, and are identified by Templates.filter_undefined_submitters in lib/templates.rb.
  • Shared link validation rejects requests when multiple undefined submitters exist to prevent ambiguous assignments, implemented in StartFormController#update.
  • Submission construction uses assign_submission_attributes to select a single UUID owner while preserving the full submitter roster in the Submission object.
  • Defined submitter propagation is handled by Submissions::AssignDefinedSubmitters to ensure pre-configured parties are included in the workflow.
  • UI safeguards in templates_share_link/show.html.erb prevent enabling public links for invalid template states.
  • Optional 2FA adds an OTP verification layer via handle_require_2fa before finalizing the submission.

Frequently Asked Questions

What qualifies as an undefined submitter in Docuseal?

An undefined submitter is any submitter hash that lacks invite_by_uuid, optional_invite_by_uuid, invite_via_field_uuid, linked_to_uuid, is_requester, and email attributes. The filter_undefined_submitters method in lib/templates.rb returns only submitters meeting this criteria, which the platform treats as "empty slots" available for public link assignment.

The shared link feature requires exactly one undefined submitter to ensure unambiguous ownership. If filter_undefined_submitters returns more than one entry, StartFormController#update would reject submission attempts with a 422 error. The UI reflects this constraint by disabling the shared link checkbox and displaying a warning in app/views/templates_share_link/show.html.erb when multiple undefined submitters are detected.

How does Docuseal handle pre-defined submitters when creating a submission?

When assign_submission_attributes creates the submission, it includes all template submitters in the template_submitters JSON field. Immediately after, Submissions::AssignDefinedSubmitters processes this list and copies any submitters that already contain emails or roles into separate submitter records associated with the new submission, ensuring that pre-configured parties are preserved while the public link user fills the remaining undefined slot.

When template.preferences['shared_link_2fa'] is true, the handle_require_2fa method in StartFormController intercepts the flow after submitter creation but before form access. The controller verifies the submitted one_time_code against the generated OTP; valid codes allow the redirect to the fill-in form, while missing or invalid codes render the OTP entry view, preventing unauthorized document access even with the public URL.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →