Documents

Render reusable document templates with placeholders, print, and barcode output.

Looking for forms?

The working forms module is backend/plugins/frontline_api/src/modules/form/; see Forms & Surveys. Core keeps only a legacy shell in modules/forms/ (the fieldsCombinedByContentType query plus fields/fieldsGroups helpers used by documents and properties).

The documents module

backend/core-api/src/modules/documents/:

graphql/schema.ts, queries.ts, mutations.ts
db/definitions/documents.ts, db/models/Documents.ts
customResolvers/document.ts
trpc/document.ts              # documents.find, findOne, print
routes.ts                     # GET /print
blocksToHtml.ts, barcode.ts, utils.ts

A Document (documents.ts) has contentType (<plugin>:<module> like core:product), subType, name, code, content (editor blocks), replacer (placeholder map), tagIds, createdUserId. The GraphQL type also resolves createdUser and approvalLockState.

OperationKey arguments
documentscontentType, subType, searchValue, dateFilters, userIds, tagIds, limit, cursor params; returns DocumentListResponse
documentsDetail_id!
documentsTypesnone; returns types across every plugin's meta.documents
documentsGetEditorAttributescontentType!; returns the placeholder list for the editor
documentsTotalCountsearchValue, contentType
documentsProcess_id, replacerIds, config; renders the template, returns an HTML string
documentsSave_id, contentType, subType, name!, content, replacer, code; upsert
documentsRemove_id!

blocksToHtml and barcode render printable output; routes.ts serves GET /print for browser printing and download flows.

How a plugin contributes document types

A plugin contributes three pieces, and core dispatches on the contentType prefix (<plugin>:<module>):

  • meta/documents.ts: documents.types entries { label, contentType, subTypes? }. documentsTypes aggregates them from every active plugin. Sales declares { label: 'Sales', contentType: 'sales:deal' }.
  • A documents.editorAttributes tRPC procedure: documentsGetEditorAttributes calls sendTRPCMessage({ pluginName, module: 'documents', action: 'editorAttributes' }) for non-core types. Sales implements it in modules/sales/trpc/document.ts, returning deal attributes like productsInfo, totalAmount, stageName, assignedUsers, plus schema fields and customFieldsData.<fieldId> entries.
  • A <module>.replaceContent tRPC procedure: documentsProcess routes to module: <moduleName>, action: 'replaceContent' for non-core types (sales exposes it on modules/sales/trpc/deal.ts). The procedure receives replacerIds, content, config, and contentType, and returns rendered blocks; config.dateFormat controls date rendering (core defaults to YYYY-MM-DD).

Core's own declaration (src/meta/documents.ts) registers core:contact.customer, core:contact.company, core:product, core:user, core:broadcast, keeps editorAttributes/replaceContent as direct functions in the meta object, and builds product barcodes through meta/document/productReplacer.ts.

Permissions

manageDocuments (create/edit), removeDocuments (delete); documentsRead is always: true. Owner scope own tracks createdUserId.

Example

mutation SaveTemplate {
  documentsSave(
    contentType: "core:product"
    name: "Product sheet"
    code: "PROD-SHEET"
    content: "<p>{{ name }} — {{ code }} — {{ unitPrice }}</p>"
  ) { _id }
}

query Render($id: String) {
  documentsProcess(_id: $id, replacerIds: ["<productId>"], config: { dateFormat: "YYYY-MM-DD" })
}
Was this helpful?