Coupa Toolbox
Free tools for Coupa admins
Small browser tools from DataMap's Coupa team. The first one gives Coupa's rich-text editor the HTML source view and menus that Coupa leaves out, so your home page is easier to maintain.
Available now
Coupa Home Page Content Editor
Adds an HTML source view and a full-featured editor to Coupa's rich-text fields, including Setup > Home Page Content.
Install itComing soon
More tools coming soon
We're adding more free tools for Coupa admins. Tell us which task you'd like one for.
Suggest a toolUse it
- 1.Open a Coupa page with a rich-text editor, such as Setup > Home Page Content > Edit.
- 2.Click DataMap Coupa Editor in your bookmarks bar. The full editor opens with the field's current content.
- 3.Edit on the page, or choose View > Source code to work in the raw HTML.
- 4.Click Apply to Coupa to write your changes back into Coupa's editor.
- 5.Click Coupa's Publish or Save button to save the change.
Before you publish
- Try it in your Coupa test instance before you use it in production.
- Apply to Coupa, and Save in the source dialog, only update the editor on the page. Coupa stores nothing until you click its own Publish or Save.
- Ctrl+Z (Cmd+Z on a Mac) in Coupa's editor undoes an applied change.
- After an apply, the tool marks the editor as saved, so your browser won't warn you about unsaved changes if you leave. Publish before you navigate away.
- Cancel closes the full editor and discards what you changed in it.
View the source code
This is the complete source. The bookmark runs the same code, minified.
(function () {
const t = window.tinymce;
const eds = t && t.get ? t.get().filter(e => !e.dmFull) : [];
if (!eds.length) { alert('No TinyMCE editor found on this page.'); return; }
const flash = (btn, label, txt) => { if (btn) { btn.textContent = txt; setTimeout(() => { btn.textContent = label; }, 1500); } };
const copyText = (txt, btn, label, fallbackEl) => {
const ok = () => flash(btn, label, 'Copied!');
const fb = () => { if (fallbackEl) { fallbackEl.focus(); fallbackEl.select(); } document.execCommand('copy'); ok(); };
navigator.clipboard && window.isSecureContext ? navigator.clipboard.writeText(txt).then(ok, fb) : fb();
};
const apply = (ed, html) => { ed.undoManager.transact(() => ed.setContent(html)); ed.setDirty(true); ed.save(); ed.nodeChanged(); };
const setup = ed => {
// ---- Source code dialog ----
const openSource = () => ed.windowManager.open({
title: 'DataMap Coupa Toolbox · Source code', size: 'large',
body: { type: 'panel', items: [{ type: 'textarea', name: 'code' }] },
initialData: { code: ed.getContent({ source_view: true }) },
buttons: [{ type: 'custom', name: 'copy', text: 'Copy', align: 'start' }, { type: 'cancel', text: 'Cancel' }, { type: 'submit', text: 'Save', primary: true }],
onAction: (api, d) => {
if (d.name !== 'copy') return;
const btn = [...document.querySelectorAll('.tox-dialog .tox-button')].find(x => x.textContent.trim() === 'Copy');
copyText(api.getData().code, btn, 'Copy', document.querySelector('.tox-dialog textarea'));
},
onSubmit: api => { apply(ed, api.getData().code); api.close(); }
});
// ---- Full-featured editor overlay ----
const openFull = () => {
if (document.getElementById('dm-full')) return;
const ov = document.createElement('div');
ov.id = 'dm-full';
ov.style.cssText = 'position:fixed;inset:0;z-index:1250;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center';
const box = document.createElement('div');
box.style.cssText = 'background:#fff;width:96vw;height:94vh;border-radius:8px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 10px 40px rgba(0,0,0,.4);font-family:-apple-system,Segoe UI,Roboto,sans-serif';
const bar = document.createElement('div');
bar.style.cssText = 'display:flex;align-items:center;gap:8px;padding:10px 14px;border-bottom:1px solid #ddd;background:#f7f7f7';
const mk = (txt, primary) => {
const b = document.createElement('button');
b.type = 'button'; b.textContent = txt;
b.style.cssText = 'padding:7px 14px;border-radius:5px;font-size:13px;font-weight:600;cursor:pointer;border:1px solid ' + (primary ? '#207ab7;background:#207ab7;color:#fff' : '#ccc;background:#fff;color:#222');
return b;
};
const brand = document.createElement('div');
brand.style.cssText = 'display:flex;align-items:center;gap:12px;margin-right:auto';
const logo = document.createElement('img');
logo.src = 'https://www.datamap.ai/brand/logo/datamap-wordmark--color.svg';
logo.alt = 'DataMap';
logo.style.cssText = 'height:24px;width:auto;display:block';
logo.onerror = () => logo.remove();
const title = document.createElement('span');
title.innerHTML = 'DataMap Coupa Toolbox <span style="font-weight:400;color:#666">· Full Editor</span>';
title.style.cssText = 'font-weight:700;font-size:15px;color:#222;padding-left:12px;border-left:1px solid #ccc';
brand.append(logo, title);
const bCopy = mk('Copy HTML'), bCancel = mk('Cancel'), bApply = mk('Apply to Coupa', true);
bar.append(brand, bCopy, bCancel, bApply);
const wrap = document.createElement('div');
wrap.style.cssText = 'flex:1;min-height:0';
const ta = document.createElement('textarea');
wrap.appendChild(ta);
box.append(bar, wrap);
ov.appendChild(box);
document.body.appendChild(ov);
// carry over the source editor's content rules and styling so the round trip stays faithful
const keys = ['content_css', 'content_style', 'font_family_formats', 'font_size_formats', 'valid_elements', 'extended_valid_elements', 'invalid_elements', 'valid_styles', 'invalid_styles', 'valid_children', 'valid_classes', 'convert_urls', 'relative_urls', 'remove_script_host', 'document_base_url', 'entity_encoding', 'entities', 'templates', 'color_map', 'table_default_styles', 'table_default_attributes'];
const cfg = {};
keys.forEach(k => { try { if (ed.options.isRegistered(k) && ed.options.isSet(k)) cfg[k] = ed.options.get(k); } catch (e) {} });
let full;
const close = () => { if (full) full.remove(); ov.remove(); };
bCancel.onclick = close;
bCopy.onclick = () => full && copyText(full.getContent(), bCopy, 'Copy HTML');
bApply.onclick = () => { if (full) apply(ed, full.getContent()); close(); };
t.init(Object.assign(cfg, {
target: ta,
height: wrap.clientHeight || window.innerHeight * 0.85,
plugins: 'advlist anchor autolink charmap code codesample directionality emoticons fullscreen help image importcss insertdatetime link lists media nonbreaking pagebreak preview searchreplace table template visualblocks visualchars wordcount',
menubar: 'file edit view insert format tools table help',
toolbar: 'undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | table link image media | charmap emoticons hr | removeformat | code visualblocks preview searchreplace fullscreen',
toolbar_mode: 'sliding',
promotion: false,
branding: false,
setup: e2 => {
e2.dmFull = true;
e2.on('init', () => { full = e2; e2.setContent(ed.getContent()); e2.undoManager.clear(); e2.focus(); });
}
}));
};
ed.addCommand('mceCodeEditor', openSource);
ed.addCommand('dmFullEditor', openFull);
ed.addShortcut('meta+shift+u', 'Source code', 'mceCodeEditor');
ed.addShortcut('meta+shift+e', 'Full editor', 'dmFullEditor');
const g = ed.getContainer().querySelector('.tox-toolbar__group:last-child');
if (g && !g.querySelector('.dm-src')) {
const btn = (cls, tip, label, fn) => {
const b = document.createElement('button');
b.type = 'button'; b.className = 'tox-tbtn ' + cls; b.title = tip; b.setAttribute('aria-label', tip);
b.innerHTML = '<span class="tox-tbtn__select-label" style="font-family:monospace;font-weight:700;width:auto;margin:0 4px">' + label + '</span>';
b.onclick = fn; g.appendChild(b);
};
btn('dm-src', 'Source code', '</>', openSource);
btn('dm-full-btn', 'Full editor', '⛶', openFull);
}
return openFull;
};
const opens = eds.map(setup);
const i = Math.max(0, eds.indexOf(t.activeEditor));
opens[i]();
})();FAQ
Frequently asked questions
The Coupa Toolbox is free and provided as is, without warranty. Coupa is a trademark of Coupa Software Inc. These tools are made by DataMap, not by Coupa.