docusealco/docuseal · error · Templates::ModifyDocuments::InvalidLayout
Templates::ModifyDocuments::InvalidLayout
Error message
Templates::ModifyDocuments::InvalidLayout
What it means
Raised by Templates::ModifyDocuments.validate_layout! when the documents_layout argument passed to Templates::ModifyDocuments.call(template, documents_layout) is blank (nil or an empty array). The layout is the authoritative description of the template's new document structure, so 'nothing' is treated as a malformed request rather than 'no change'.
Source
Thrown at lib/templates/modify_documents.rb:93
schema_item.except('google_drive_file_id').merge('attachment_uuid' => document.uuid)
end
end
ensure
sources.each_value(&:close)
end
end
def add_page_mapping(mapping, ref, target)
mapping[[ref['attachment_uuid'], ref['page']]] = target
replaced = ref['replaced_page']
mapping[[replaced['attachment_uuid'], replaced['page']]] = target if replaced
end
def validate_layout!(template, documents_layout, attachments_index)
raise InvalidLayout if documents_layout.blank?
raise InvalidLayout if documents_layout.all? { |entry| entry['pages'].blank? }
dynamic_uuids = template.schema.select { |item| item['dynamic'] }.pluck('attachment_uuid')
non_dynamic_uuids = template.schema.pluck('attachment_uuid') - dynamic_uuids
layout_uuids = documents_layout.pluck('attachment_uuid')
raise InvalidLayout if layout_uuids.uniq.size != layout_uuids.size
raise InvalidLayout if (non_dynamic_uuids - layout_uuids).any?
raise InvalidLayout if layout_uuids.intersect?(dynamic_uuids)
raise InvalidLayout if layout_uuids.any? { |uuid| attachments_index[uuid].nil? }
refs = documents_layout.flat_map { |entry| entry['pages'].to_a }
refs.each { |ref| validate_ref!(ref, attachments_index) }
ref_keys = refs.map { |ref| [ref['attachment_uuid'], ref['page']] }
raise InvalidLayout if ref_keys.uniq.size != ref_keys.sizeView on GitHub (pinned to 004a22c1c8)
Solutions
- Skip the ModifyDocuments call entirely when the layout is blank — blank means there is nothing to apply
- Guard at the call site: return template unless documents_layout.is_a?(Array) && documents_layout.present?
- Fix the client to always send the full layout array whenever it requests a document modification
Example fix
# before Templates::ModifyDocuments.call(template, params[:documents_layout].presence) # nil -> InvalidLayout # after layout = params[:documents_layout] Templates::ModifyDocuments.call(template, layout) if layout.is_a?(Array) && layout.present?
Defensive patterns
Strategy: validation
Validate before calling
layout.is_a?(Array) && layout.present?
Type guard
layout.is_a?(Array) && !layout.empty?
Try / catch
begin
Templates::ModifyDocuments.call(template, documents_layout)
rescue Templates::ModifyDocuments::InvalidLayout => e
render json: { error: 'Invalid document layout' }, status: :unprocessable_entity
end Prevention
- Treat an absent layout as 'no-op' and short-circuit before calling the modifier
- Never use .presence when forwarding layout params — it silently converts [] to nil
- Add a JSON-schema check on documents_layout at the API boundary (non-empty array of objects)
When it happens
Trigger: Calling Templates::ModifyDocuments.call(template, nil) or (template, []) — e.g. an API client omits documents_layout from the request body, or upstream code passes params[:documents_layout].presence, which collapses an empty array to nil.
Common situations: A document-editor 'apply changes' request sent with an empty body; serialization that drops empty arrays; a guard elsewhere using .presence turning [] into nil; automated jobs running the modifier for every template including ones with no pending edits.
Related errors
AI-assisted analysis of docusealco/docuseal@004a22c1c8 (2026-08-21).
Data as JSON: /api/errors/684ce16449656252.
Report an issue: GitHub.