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.
Validating Shared Link Requests
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:
- 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). - Creates the submission object: It instantiates a new
Submissionrecord containing the complete roster of template submitters (template_submitters: template.submitters). - Copies defined submitters: It delegates to
Submissions::AssignDefinedSubmitters(located inlib/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.
Submitting via Public Link
POST /api/v1/submissions
{
"template_id": "c0a1e2f3-4b5c-6d7e-8f9a-bcdef0123456",
"submitters": [
{ "email": "alice@example.com", "name": "Alice" }
]
}
Server processing:
StartFormController#updatereceives the payloadfilter_undefined_submittersfinds exactly one undefined submitter → passes guardassign_submission_attributesassigns Alice to that UUID and builds the submissionSubmissions::AssignDefinedSubmittersprocesses 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_submittersinlib/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_attributesto select a single UUID owner while preserving the full submitter roster in theSubmissionobject. - Defined submitter propagation is handled by
Submissions::AssignDefinedSubmittersto ensure pre-configured parties are included in the workflow. - UI safeguards in
templates_share_link/show.html.erbprevent enabling public links for invalid template states. - Optional 2FA adds an OTP verification layer via
handle_require_2fabefore 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.
Why can't I enable a shared link for my multi-signer template?
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.
What happens if two-factor authentication is enabled on a shared link?
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →