- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
I want to add a "Generate Document” button on the Incident form that creates an editable MS Word file (DOCX preferred) with prefilled Incident details and automatically attaches it to the record. I’m looking for a free, fully OOTB method without paid plugins or external services.
Key requirements include:
• Prefill Incident fields (number, short description, priority, assignment group, etc.)
• Editable Word output
• Must attach the generated file to the Incident.
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago - last edited 3 weeks ago
1. Create an RTF Generator Script Include
Build helper functions to dynamically create RTF content — such as:
- Headings
- Paragraphs
- Field values
- Tables
This utility will handle the actual text and formatting generation.
2. Create a Word Generator Utility
This Script Include is responsible for:
- Constructing the overall template structure
- Calling the RTF helper methods
- Packaging the output as a
.doc(RTF) file - Attaching the final document to the Incident (or any record)
Create a UI Action (button) on the form—like "Generate Document"—and call your Word Generator utility from there.
Notes
- This method is completely free and works well for standard documents.
- However, RTF is limited, so complex formatting like advanced tables, images, or rich styling may not look perfect compared to the paid plugin.
- For many use cases (letters, summaries, formatted business documents), the custom RTF approach is more than sufficient.
1. Script Include: RTFGeneratorUtil
var RTFGeneratorUtil = Class.create();
RTFGeneratorUtil.prototype = {
initialize: function() {},
// ─── Helper: Escape special RTF characters ────────────────────────────────
rtfEscape: function(input) {
// Force to JS primitive string — prevents Rhino "Cannot find default value" error
var str = String(input || '');
var result = '';
var len = str.length;
for (var i = 0; i < len; i++) {
var c = str.charAt(i);
var code = str.charCodeAt(i);
if (c === '\\') {
result += '\\\\';
} else if (c === '{') {
result += '\\{';
} else if (c === '}') {
result += '\\}';
} else if (c === '\r') {
// skip bare carriage returns
} else if (c === '\n') {
result += '\\line ';
} else if (code > 127) {
result += '\\u' + code + '?';
} else {
result += c;
}
}
return result;
},
// ─── Helper: Section heading ──────────────────────────────────────────────
rtfHeading: function(text) {
return '{\\pard\\sb280\\sa120\\b\\fs26\\cf1 ' + this.rtfEscape(text) + '\\par}\n';
},
// ─── Helper: Label + Value row ────────────────────────────────────────────
rtfField: function(label, value) {
return '{\\pard\\sb80\\sa60\\b ' + this.rtfEscape(label) + ':\\b0 ' +
this.rtfEscape(value || 'N/A') + '\\par}\n';
},
// ─── Helper: Body paragraph ───────────────────────────────────────────────
rtfPara: function(text) {
return '{\\pard\\sb80\\sa120 ' + this.rtfEscape(text || '(none)') + '\\par}\n';
},
// ─── Helper: Horizontal rule ──────────────────────────────────────────────
rtfRule: function() {
return '{\\pard\\sb200\\sa200\\brdrb\\brdrs\\brdrw10\\brsp20 \\par}\n';
},
// ─── Helper: Horizontal row ──────────────────────────────────────────────
cellBorders: function() {
// Common border style for each cell
return "\\clbrdrt\\brdrs\\brdrw15\\brdrcf1" +
"\\clbrdrl\\brdrs\\brdrw15\\brdrcf1" +
"\\clbrdrb\\brdrs\\brdrw15\\brdrcf1" +
"\\clbrdrr\\brdrs\\brdrw15\\brdrcf1";
},
rtfRow(label, value) {
return "{\\trowd\\trgaph108" +
this.cellBorders() + "\\cellx3000" +
this.cellBorders() + "\\cellx9000" +
"\\intbl " + label + " \\cell" +
"\\intbl " + value + "\\cell" +
"\\row}";
},
type: 'RTFGeneratorUtil'
};2. Script Include: WordDocumentUtil
var WordDocumentUtil = Class.create();
WordDocumentUtil.prototype = Object.extendsObject(AbstractAjaxProcessor, {
generate: function(incNumber) {
var incGr = this._getIncident(incNumber);
if (incGr) {
return this._generateDoc(incGr);
}
},
_getIncident: function(incNumber) {
var incGr = new GlideRecord('incident');
incGr.addQuery('number', incNumber);
incGr.query();
if (!incGr.next()) {
gs.error('[WordDoc] Incident not found: ' + incNumber);
return false;
}
return incGr;
},
_generateDoc: function(incGr) {
// CRITICAL: Wrap every field in String() to force JS primitive strings
// GlideRecord fields return Java objects — Rhino can't iterate them directly
var incNumber = String(incGr.number);
var incShortDesc = String(incGr.short_description);
var incState = String(incGr.state.getDisplayValue());
var incPriority = String(incGr.priority.getDisplayValue());
var incDesc = String(incGr.description);
var title = incNumber ;
var shortDesc = incShortDesc;
var subTitle = "Generated : "+now;
var rtfUtil = new RTFGeneratorUtil();
// ─── 2. Build RTF Document ────────────────────────────────────────────────
var now = String(new GlideDateTime().getDisplayValue());
var rtf =
'{\\rtf1\\ansi\\ansicpg1252\\deff0\n' +
// Font table
'{\\fonttbl\n' +
'{\\f0\\froman\\fcharset0 Arial;}\n' +
'{\\f1\\fmodern\\fcharset0 Courier New;}\n' +
'}\n' +
// Color table
'{\\colortbl;\n' +
'\\red0\\green112\\blue187;\n' + // cf1 — blue
'\\red80\\green80\\blue80;\n' + // cf2 — grey
'}\n' +
'\\widowctrl\\hyphauto\n' +
'\\margl1800\\margr1800\\margt1440\\margb1440\n' +
'\\f0\\fs20\n' +
// Title
'{\\pard\\sb400\\sa200\\qc\\b\\fs40\\cf1 ' + rtfUtil.rtfEscape(title) + '\\par}\n' +
'{\\pard\\sb0\\sa120\\qc\\b\\fs26 ' + rtfUtil.rtfEscape(shortDesc) + '\\par}\n' +
'{\\pard\\sb0\\sa400\\qc\\fs18\\cf2 ' + rtfUtil.rtfEscape(subTitle) + '\\par}\n' +
rtfUtil.rtfHeading('1. Document Control') +
"{\\rtf1\\ansi{\\colortbl;\\red0\\green0\\blue0;}" +
rtfUtil.rtfRow("ServiceNow Incident ID", incNumber) +
rtfUtil.rtfRow("Short Description", incShortDesc) +
rtfUtil.rtfRow("Priority", incPriority) +
"}" +
// Incident Details
rtfUtil.rtfHeading('2. Incident Summary') +
rtfUtil.rtfField('Number', incNumber) +
rtfUtil.rtfField('Short Description', incShortDesc) +
rtfUtil.rtfField('State', incState) +
rtfUtil.rtfField('Priority', incPriority) +
rtfUtil.rtfRule() +
// Assignment
rtfUtil.rtfHeading('Assignment') +
rtfUtil.rtfField('Caller', incCaller) +
rtfUtil.rtfField('Assigned To', incAssignee) +
rtfUtil.rtfField('Assignment Group', incAssignGrp) +
rtfUtil.rtfRule() +
// Description
rtfUtil.rtfHeading('Description') +
rtfUtil.rtfPara(incDesc) +
// Comments
(incComments ? (rtfUtil.rtfRule() + rtfUtil.rtfHeading('Additional Comments') + rtfUtil.rtfPara(incComments)) : '') +
rtfUtil.rtfRule() +
// Footer
'{\\pard\\sb200\\sa0\\qc\\fs16\\cf2 Auto-generated by ServiceNow\\par}\n' +
'{\\pard\\sb40\\sa0\\qc\\fs16\\cf2 ' + rtfUtil.rtfEscape(now) + '\\par}\n' +
'}';
this._attachToIncident(incGr, rtf);
},
_attachToIncident: function(incGr, document) {
var gdt = new GlideDateTime();
var display = gdt.getDisplayValue() + "";
var formatted = display.replace(/:/g, "-").replace(/\s+/g, "_");
var fileName = 'Incident_Report_' + incGr.getValue('number') + '_' + formatted + '.doc';
var contentType = 'application/msword';
var attachment = new GlideSysAttachment();
attachment.write(incGr, fileName, contentType, document);
gs.addInfoMessage("Document " + fileName + " successfully created and attached to " + incGr.getValue('number'));
return true;
},
type: 'WordDocumentUtil'
});3. Ui Action: Generate Document
var incNumber = current.number;
var generateFile = new WordDocumentUtil().generate(incNumber);
action.setRedirectURL(current);Output FIle
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
I don't think anything is available free for this requirement.
There is a paid app I believe
You will have to rely on external javascript libraries for this if you want to go with free version
💡 If my response helped, please mark it as correct ✅ and close the thread 🔒— this helps future readers find the solution faster! 🙏
Ankur
✨ Certified Technical Architect || ✨ 10x ServiceNow MVP || ✨ ServiceNow Community Leader
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago - last edited 3 weeks ago
1. Create an RTF Generator Script Include
Build helper functions to dynamically create RTF content — such as:
- Headings
- Paragraphs
- Field values
- Tables
This utility will handle the actual text and formatting generation.
2. Create a Word Generator Utility
This Script Include is responsible for:
- Constructing the overall template structure
- Calling the RTF helper methods
- Packaging the output as a
.doc(RTF) file - Attaching the final document to the Incident (or any record)
Create a UI Action (button) on the form—like "Generate Document"—and call your Word Generator utility from there.
Notes
- This method is completely free and works well for standard documents.
- However, RTF is limited, so complex formatting like advanced tables, images, or rich styling may not look perfect compared to the paid plugin.
- For many use cases (letters, summaries, formatted business documents), the custom RTF approach is more than sufficient.
1. Script Include: RTFGeneratorUtil
var RTFGeneratorUtil = Class.create();
RTFGeneratorUtil.prototype = {
initialize: function() {},
// ─── Helper: Escape special RTF characters ────────────────────────────────
rtfEscape: function(input) {
// Force to JS primitive string — prevents Rhino "Cannot find default value" error
var str = String(input || '');
var result = '';
var len = str.length;
for (var i = 0; i < len; i++) {
var c = str.charAt(i);
var code = str.charCodeAt(i);
if (c === '\\') {
result += '\\\\';
} else if (c === '{') {
result += '\\{';
} else if (c === '}') {
result += '\\}';
} else if (c === '\r') {
// skip bare carriage returns
} else if (c === '\n') {
result += '\\line ';
} else if (code > 127) {
result += '\\u' + code + '?';
} else {
result += c;
}
}
return result;
},
// ─── Helper: Section heading ──────────────────────────────────────────────
rtfHeading: function(text) {
return '{\\pard\\sb280\\sa120\\b\\fs26\\cf1 ' + this.rtfEscape(text) + '\\par}\n';
},
// ─── Helper: Label + Value row ────────────────────────────────────────────
rtfField: function(label, value) {
return '{\\pard\\sb80\\sa60\\b ' + this.rtfEscape(label) + ':\\b0 ' +
this.rtfEscape(value || 'N/A') + '\\par}\n';
},
// ─── Helper: Body paragraph ───────────────────────────────────────────────
rtfPara: function(text) {
return '{\\pard\\sb80\\sa120 ' + this.rtfEscape(text || '(none)') + '\\par}\n';
},
// ─── Helper: Horizontal rule ──────────────────────────────────────────────
rtfRule: function() {
return '{\\pard\\sb200\\sa200\\brdrb\\brdrs\\brdrw10\\brsp20 \\par}\n';
},
// ─── Helper: Horizontal row ──────────────────────────────────────────────
cellBorders: function() {
// Common border style for each cell
return "\\clbrdrt\\brdrs\\brdrw15\\brdrcf1" +
"\\clbrdrl\\brdrs\\brdrw15\\brdrcf1" +
"\\clbrdrb\\brdrs\\brdrw15\\brdrcf1" +
"\\clbrdrr\\brdrs\\brdrw15\\brdrcf1";
},
rtfRow(label, value) {
return "{\\trowd\\trgaph108" +
this.cellBorders() + "\\cellx3000" +
this.cellBorders() + "\\cellx9000" +
"\\intbl " + label + " \\cell" +
"\\intbl " + value + "\\cell" +
"\\row}";
},
type: 'RTFGeneratorUtil'
};2. Script Include: WordDocumentUtil
var WordDocumentUtil = Class.create();
WordDocumentUtil.prototype = Object.extendsObject(AbstractAjaxProcessor, {
generate: function(incNumber) {
var incGr = this._getIncident(incNumber);
if (incGr) {
return this._generateDoc(incGr);
}
},
_getIncident: function(incNumber) {
var incGr = new GlideRecord('incident');
incGr.addQuery('number', incNumber);
incGr.query();
if (!incGr.next()) {
gs.error('[WordDoc] Incident not found: ' + incNumber);
return false;
}
return incGr;
},
_generateDoc: function(incGr) {
// CRITICAL: Wrap every field in String() to force JS primitive strings
// GlideRecord fields return Java objects — Rhino can't iterate them directly
var incNumber = String(incGr.number);
var incShortDesc = String(incGr.short_description);
var incState = String(incGr.state.getDisplayValue());
var incPriority = String(incGr.priority.getDisplayValue());
var incDesc = String(incGr.description);
var title = incNumber ;
var shortDesc = incShortDesc;
var subTitle = "Generated : "+now;
var rtfUtil = new RTFGeneratorUtil();
// ─── 2. Build RTF Document ────────────────────────────────────────────────
var now = String(new GlideDateTime().getDisplayValue());
var rtf =
'{\\rtf1\\ansi\\ansicpg1252\\deff0\n' +
// Font table
'{\\fonttbl\n' +
'{\\f0\\froman\\fcharset0 Arial;}\n' +
'{\\f1\\fmodern\\fcharset0 Courier New;}\n' +
'}\n' +
// Color table
'{\\colortbl;\n' +
'\\red0\\green112\\blue187;\n' + // cf1 — blue
'\\red80\\green80\\blue80;\n' + // cf2 — grey
'}\n' +
'\\widowctrl\\hyphauto\n' +
'\\margl1800\\margr1800\\margt1440\\margb1440\n' +
'\\f0\\fs20\n' +
// Title
'{\\pard\\sb400\\sa200\\qc\\b\\fs40\\cf1 ' + rtfUtil.rtfEscape(title) + '\\par}\n' +
'{\\pard\\sb0\\sa120\\qc\\b\\fs26 ' + rtfUtil.rtfEscape(shortDesc) + '\\par}\n' +
'{\\pard\\sb0\\sa400\\qc\\fs18\\cf2 ' + rtfUtil.rtfEscape(subTitle) + '\\par}\n' +
rtfUtil.rtfHeading('1. Document Control') +
"{\\rtf1\\ansi{\\colortbl;\\red0\\green0\\blue0;}" +
rtfUtil.rtfRow("ServiceNow Incident ID", incNumber) +
rtfUtil.rtfRow("Short Description", incShortDesc) +
rtfUtil.rtfRow("Priority", incPriority) +
"}" +
// Incident Details
rtfUtil.rtfHeading('2. Incident Summary') +
rtfUtil.rtfField('Number', incNumber) +
rtfUtil.rtfField('Short Description', incShortDesc) +
rtfUtil.rtfField('State', incState) +
rtfUtil.rtfField('Priority', incPriority) +
rtfUtil.rtfRule() +
// Assignment
rtfUtil.rtfHeading('Assignment') +
rtfUtil.rtfField('Caller', incCaller) +
rtfUtil.rtfField('Assigned To', incAssignee) +
rtfUtil.rtfField('Assignment Group', incAssignGrp) +
rtfUtil.rtfRule() +
// Description
rtfUtil.rtfHeading('Description') +
rtfUtil.rtfPara(incDesc) +
// Comments
(incComments ? (rtfUtil.rtfRule() + rtfUtil.rtfHeading('Additional Comments') + rtfUtil.rtfPara(incComments)) : '') +
rtfUtil.rtfRule() +
// Footer
'{\\pard\\sb200\\sa0\\qc\\fs16\\cf2 Auto-generated by ServiceNow\\par}\n' +
'{\\pard\\sb40\\sa0\\qc\\fs16\\cf2 ' + rtfUtil.rtfEscape(now) + '\\par}\n' +
'}';
this._attachToIncident(incGr, rtf);
},
_attachToIncident: function(incGr, document) {
var gdt = new GlideDateTime();
var display = gdt.getDisplayValue() + "";
var formatted = display.replace(/:/g, "-").replace(/\s+/g, "_");
var fileName = 'Incident_Report_' + incGr.getValue('number') + '_' + formatted + '.doc';
var contentType = 'application/msword';
var attachment = new GlideSysAttachment();
attachment.write(incGr, fileName, contentType, document);
gs.addInfoMessage("Document " + fileName + " successfully created and attached to " + incGr.getValue('number'));
return true;
},
type: 'WordDocumentUtil'
});3. Ui Action: Generate Document
var incNumber = current.number;
var generateFile = new WordDocumentUtil().generate(incNumber);
action.setRedirectURL(current);Output FIle
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2 weeks ago
Thanks for the explanation. Really helpful!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2 weeks ago
Thanks for providing the solution — it worked successfully.
