Turning Work Notes Into Something Beautiful
New article articles in ServiceNow Community
·
Sep 23, 2026
·
article
Turning Work Notes Into Something Worth Reading
Anyone who has scrolled through a work notes history full of raw paragraph dumps, pasted logs, or five separate lines of "checked X, checked Y, still checking" knows the problem: the information is there, but nobody wants to read it. Agents skim past it, stakeholders on cross-functional cases can't tell status at a glance, and anything generated by automation just adds volume without adding clarity.
This Flow Action fixes that by rendering a clean, structured HTML card directly into the work notes journal field. Instead of a block of text, the record gets something with a header, an accent color tied to severity, and organized sections for whatever the automation actually found. It works on any table that extends Task (incidents, HR cases, CSM cases, custom apps included), because it takes the target table and record as inputs rather than being hardcoded to one.
What has to be supplied, and what doesn't
Two inputs are mandatory: table and sys ID. That's not a stylistic choice, without them the action has no record to write to, and no reasonable default exists. Every other input is optional.
The card is built from independent sections: a summary, a verdict/callout, a set of key metrics, status badges, links to related records, and most automations don't need all of them at once. A simple "here's what I found" note only needs a summary. A triage automation might add a verdict and metrics. A risk or compliance check might only need badges. Making every section optional means one action serves all of those cases, instead of forcing every caller to pass empty placeholders for sections they'll never use, or maintaining five near-identical actions for five levels of complexity. The card only renders the sections it's given. Furthermore, these inputs are just what are in this solution, you do not need to implement them. You can create your own inputs if needed and just switch out the script for your requirements.
A few examples of what this could be applied to:
- AI-driven incident triage. An automation added to an Agentic Workflow or Skill that summarizes the incident, states a likely cause with a confidence score, and links the change record it suspects; color-coded so a red accent means "needs attention" without anyone reading a word.
- HR case eligibility checks. A case gets a note that reads "Eligible — Tier 2 leave" as a green callout, with a link to the employee's prior related case, instead of a paragraph explaining the same thing.
- Change risk flags. Badges like "HIGH RISK" or "CAB REQUIRED" sitting at the top of the record, visible before anyone opens the change itself.
- SLA or escalation alerts. A footer with the responsible team and a timestamp, so it's clear at a glance who owns the next action and when it landed.
The common thread: any automation that currently posts a wall of text as a work note is a candidate. The card format doesn't just look better, it changes how quickly a human can act on what the automation found.
Where it can be called from
In practice, you should be able to call the Action from at least three places: directly from a Flow triggered on a record event, from a Subflow that resolves inputs (like related record links) before handing off to the card, or as a tool inside an AI Agent , where the agent populates the fields dynamically from whatever it's working on. Same rendering, different entry points depending on how deterministic or LLM-driven the calling automation is. If non-LLM, you must make sure to code a dynamic reference for the table and sys_id inputs, and if LLM driven, then instruct the LLM to pass that information to the Action so that the human Agent does not have to enter that manually.
Action configurations:
Script:
// ===================================================================== // WORK NOTE CARD — reusable Flow Action script template // Posts one styled HTML card into the work_notes journal field of any // task-extended record (incident, sn_hr_core_case, sn_customerservice_case...). // // ACTION INPUTS (all String): // table 'incident' | 'sn_hr_core_case' | ... // sysid sys_id of the target record // card_title header text // card_badge header badge text ('' to omit) // status critical | high | medium | low | info -> accent colour // footer_label footer text ('' to omit) // // ACTION OUTPUTS (String): success, error // // SECTION SCHEMA // {"label":"Summary","type":"text","value":"free text"} // {"label":"Verdict","type":"callout","value":"...","tone":"good|warn|bad|info"} // {"label":"Metrics","type":"pairs","pairs":[{"k":"Score","v":"82"}]} // {"label":"Flags","type":"badges","badges":[{"text":"BACKED OUT","tone":"good"}]} // {"label":"Related","type":"records","records":[// {"table":"change_request","sys_id":"...","number":"CHG0030001", // "meta":"New | High","text":"short description","tone":"warn"}]} // // PREREQUISITE // sys_property glide.ui.security.allow_codetag = true, else the card // renders as raw HTML text. No <script> is emitted, so // codetag.allow_script can stay false. // ===================================================================== (function execute(inputs, outputs) { // ---------- 1. THEME: the only block a customer normally edits ---------- var FONT = "-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif"; var C = { headerBg: '#000000', headerFg: '#63DF4E', border: '#d8dbe2', body: '#434655', muted: '#737686', panel: '#f4f5f8', link: '#0066cc' }; var TONE = { bad: { bg: '#ffdad6', fg: '#7d0000', bar: '#ba1a1a' }, warn: { bg: '#fef3c7', fg: '#92400e', bar: '#f59e0b' }, info: { bg: '#dbeafe', fg: '#1e40af', bar: '#3b82f6' }, good: { bg: '#e1f5ee', fg: '#0f6e56', bar: '#0f6e56' }, plain: { bg: '#f3f4f6', fg: '#374151', bar: '#9ca3af' } }; var STATUS_TONE = { critical: 'bad', high: 'warn', medium: 'info', low: 'good', info: 'plain' }; // Deep-link style: 'classic' (UI16) or 'sow' (Service Operations Workspace). var LINK_STYLE = gs.getProperty('worknote.card.link_style', 'classic'); // ---------- 2. STYLE STRINGS ---------- var S = { card: 'font-family:' + FONT + ';max-width:100%;border-radius:8px;overflow:hidden;border:1px solid ' + C.border + ';position:relative;background:#fff;', hdr: 'padding:10px 12px 10px 14px;background:' + C.headerBg + ';border-bottom:1px solid ' + C.border + ';display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;', hdrTtl: 'font-size:11px;font-weight:700;color:' + C.headerFg + ';text-transform:uppercase;letter-spacing:.05em;font-family:' + FONT + ';', sect: 'padding:8px 12px 8px 14px;border-bottom:1px solid ' + C.border + ';', lbl: 'font-size:10px;font-weight:700;color:' + C.muted + ';text-transform:uppercase;letter-spacing:.05em;margin:0 0 5px;font-family:' + FONT + ';', msg: 'font-size:11px;color:' + C.body + ';line-height:1.6;background:' + C.panel + ';border-radius:5px;padding:6px 9px;font-family:' + FONT + ';white-space:pre-wrap;word-break:break-word;', foot: 'padding:5px 12px;background:' + C.panel + ';display:flex;justify-content:space-between;', footLbl:'font-size:10px;color:' + C.muted + ';font-family:' + FONT + ';', link: 'color:' + C.link + ';text-decoration:none;font-weight:600;font-size:11px;font-family:' + FONT + ';' }; // ---------- 3. HELPERS ---------- function esc(v) { return String(v === undefined || v === null ? '' : v) .replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') .replace(/"/g, '"').replace(/'/g, '''); } function tone(name) { return TONE[name] || TONE.plain; } function recordUrl(t, id) { if (!t || !id) return '#'; return (LINK_STYLE === 'sow') ? '/now/sow/record/' + t + '/' + id : '/nav_to.do?uri=' + encodeURIComponent(t + '.do?sys_id=' + id); } function badge(text, toneName) { var t = tone(toneName); return '<span style="font-size:9px;font-weight:700;background:' + t.bg + ';color:' + t.fg + ';padding:2px 6px;border-radius:4px;font-family:' + FONT + ';">' + esc(text) + '</span>'; } function panel(html, toneName) { var t = tone(toneName); return '<div style="background:' + t.bg + ';border-radius:6px;border-left:3px solid ' + t.bar + ';padding:8px 10px;margin:4px 0;font-size:11px;line-height:1.5;color:' + t.fg + ';font-family:' + FONT + ';">' + html + '</div>'; } // ---------- 4. SECTION RENDERERS ---------- function renderSection(sec) { var h = '<div style="' + S.sect + '">'; if (sec.label) h += '<p style="' + S.lbl + '">' + esc(sec.label) + '</p>'; if (sec.type === 'callout') { h += panel(esc(sec.value), sec.tone); } else if (sec.type === 'pairs') { var pairs = sec.pairs || []; h += '<div style="font-size:11px;color:' + C.body + ';line-height:1.8;font-family:' + FONT + ';">'; for (var p = 0; p < pairs.length; p++) { if (p > 0) h += ' • '; h += esc(pairs[p].k) + ': <b>' + esc(pairs[p].v) + '</b>'; } h += '</div>'; } else if (sec.type === 'badges') { var bs = sec.badges || []; h += '<div style="display:flex;gap:4px;flex-wrap:wrap;">'; for (var b = 0; b < bs.length; b++) h += badge(bs[b].text, bs[b].tone); h += '</div>'; } else if (sec.type === 'records') { var recs = sec.records || []; for (var r = 0; r < recs.length; r++) { var rec = recs[r]; var t = tone(rec.tone); h += '<div style="background:' + t.bg + ';border-radius:6px;border-left:3px solid ' + t.bar + ';padding:8px 10px;margin:4px 0;">'; h += '<a href="' + recordUrl(rec.table, rec.sys_id) + '" style="' + S.link + '">' + esc(rec.number) + '</a>'; if (rec.meta) h += '<span style="font-size:10px;color:' + C.muted + ';margin-left:8px;font-family:' + FONT + ';">' + esc(rec.meta) + '</span>'; if (rec.text) h += '<div style="font-size:10px;color:' + t.fg + ';margin-top:3px;font-family:' + FONT + ';">' + esc(rec.text) + '</div>'; h += '</div>'; } } else { // 'text' — default h += '<div style="' + S.msg + '">' + esc(sec.value) + '</div>'; } h += '</div>'; return h; } // ---------- 5. BUILD AND POST ---------- try { var table = String(inputs.table || ''); var sysId = String(inputs.sysid || ''); if (!table || !sysId) throw new Error('table and sys_id are required'); var gr = new GlideRecord(table); if (!gr.get(sysId)) throw new Error('Record not found: ' + table + '/' + sysId); if (!gr.isValidField('work_notes')) throw new Error('No work_notes field on ' + table); var sections = []; if (inputs.summary) { sections.push({ label: 'Summary', type: 'text', value: String(inputs.summary) }); } if (inputs.verdict) { var vt = String(inputs.verdict_tone || 'info').toLowerCase(); if (['good', 'warn', 'bad', 'info'].indexOf(vt) === -1) vt = 'info'; sections.push({ label: 'Assessment', type: 'callout', value: String(inputs.verdict), tone: vt }); } // metrics: "Confidence=0.87, Similar cases=4" if (inputs.metrics) { var pairs = []; var mp = String(inputs.metrics).split(','); for (var m = 0; m < mp.length; m++) { var kv = mp[m].split('='); if (kv.length === 2 && kv[0].trim() && kv[1].trim()) { pairs.push({ k: kv[0].trim(), v: kv[1].trim() }); } } if (pairs.length) sections.push({ label: 'Metrics', type: 'pairs', pairs: pairs }); } if (inputs.related_numbers) { var recs = []; var nums = String(inputs.related_numbers).split(','); for (var n = 0; n < nums.length && recs.length < 5; n++) { var num = nums[n].trim(); if (!num) continue; var rg = new GlideRecord(table); if (rg.get('number', num)) { recs.push({ table: rg.getRecordClassName(), sys_id: rg.getUniqueValue(), number: rg.getValue('number'), meta: rg.getDisplayValue('state'), text: rg.getValue('short_description'), tone: 'plain' }); } } if (recs.length) sections.push({ label: 'Related Records', type: 'records', records: recs }); } var accent = tone(STATUS_TONE[String(inputs.status || 'info').toLowerCase()] || 'plain'); var h = '<div style="' + S.card + '">'; h += '<div style="position:absolute;left:0;top:0;width:3px;height:100%;background:' + accent.bar + ';"></div>'; h += '<div style="' + S.hdr + '">'; h += '<span style="' + S.hdrTtl + '"><b>' + esc(inputs.card_title || 'Automated Update') + '</b></span>'; if (inputs.card_badge) { h += '<span style="font-size:10px;font-weight:700;background:' + accent.bg + ';color:' + accent.fg + ';padding:2px 8px;border-radius:4px;font-family:' + FONT + ';">' + esc(inputs.card_badge) + '</span>'; } h += '</div>'; for (var i = 0; i < sections.length; i++) h += renderSection(sections[i]); if (inputs.footer_label) { h += '<div style="' + S.foot + '">'; h += '<span style="' + S.footLbl + '">' + esc(inputs.footer_label) + '</span>'; h += '<span style="' + S.footLbl + '">' + new GlideDateTime().getDisplayValue() + '</span>'; h += '</div>'; } h += '</div>'; gr.work_notes = '[code]' + h + '[/code]'; gr.update(); outputs.success = 'true'; outputs.error = ''; } catch (e) { outputs.success = 'false'; outputs.error = e.message; gs.error('Work Note Card error: ' + e.message); } })(inputs, outputs);
Please give this a thumbs up and post screenshots of what it looks like in your instance! Happy coding!
https://www.servicenow.com/community/developer-articles/turning-work-notes-into-something-beautiful/ta-p/3601549