Generates a single onload.js from an annotated HTML template. Author your ADFS
login page customizations as a normal HTML file — open it in a browser to preview,
then run the generator to produce the file you deploy to ADFS.
ADFS lets you inject a custom onload.js into the login page. This tool reads
an HTML template you write, finds every data-adfs-* annotation, and compiles
them into a self-contained IIFE that rewrites the ADFS DOM at runtime.
The key principle: ADFS owns the form mechanics, you own the visuals. The generator never recreates ADFS elements — it moves them into your layout so event handlers, hidden inputs, and form POST continue to work.
npm installnode generator.js <template.html> [options]| Option | Description |
|---|---|
<template.html> |
Path to your annotated HTML template (required) |
--base-url <url> |
Rewrite local ./asset paths to this URL |
--output, -o <file> |
Output path (default: dist/onload.js) |
--empty-body |
Empty document.body before adding elements |
node generator.js src/template.html --base-url https://cdn.example.com/adfs/ -o dist/onload.jsOr via npm:
npm run generateAfter generating, validate the output before deploying:
node validate.js <template.html> <onload.js>npm run validateThe validator checks that onload.js is valid JavaScript, then builds a mock
DOM from your template's target selectors, runs the script against it, and
confirms each action had its expected effect. Non-zero exit code on failure.
generator.js The compiler
validate.js Post-build validator
src/
template.html Your authored page — open in browser to preview
styles/ CSS files referenced in template.html
scripts/ JS files referenced in template.html
images/ Images referenced in template.html
dist/
onload.js Generated output — deploy this to ADFS
examples/
login/ Username/password login page (move pattern)
hrd/ Home Realm Discovery with federated providers
src/template.html is a full HTML page. Annotate elements with data-adfs-*
attributes to tell the generator what to do on the real ADFS page.
Link CSS files normally. They are read from disk and inlined into onload.js.
<link rel="stylesheet" href="./styles/main.css">External URLs (e.g. Google Fonts) are skipped.
Reference images with relative ./ paths. The generator rewrites them to
--base-url + the relative path in the output.
<img src="./images/logo.png" alt="Logo">Script files are read from disk and inlined. Inline <script> blocks are
included as-is. External URLs are skipped.
<script src="./scripts/custom.js"></script>
<script>
document.title = 'Acme Corp – Sign in';
</script>Each data-adfs-* attribute is an instruction to the generator. Actions are
processed in document order.
Replace the target element with this one. The element is created with its original tag name and attributes.
<header data-adfs-replace="#loginHeader" class="company-header">
<img src="./images/logo.png" alt="Acme Corp">
</header>Append this element as the last child of the target.
<div data-adfs-append-to="body" class="shell" id="customShell">
<div class="login-card">...</div>
</div>When the target is body or head, the generated code uses document.body /
document.head directly.
Insert this element as the first child of the target.
<div data-adfs-prepend-to="#loginArea" class="notice-banner">
<p>Scheduled maintenance Saturday 22:00–24:00</p>
</div>Remove the target element from the DOM entirely.
<span data-adfs-remove="#forgotPasswordLink"></span>Hide the target element (display: none) while keeping it in the DOM. Use this
for elements ADFS needs functionally but that shouldn't be visible — like
#loginForm when you've moved its inputs into a custom shell.
<div data-adfs-hide="#loginForm"></div>Move an existing DOM element into a new container. The element is moved, not cloned — event handlers, hidden inputs, and ADFS bindings are preserved.
<div data-adfs-move-to="#userNameInput" data-adfs-into="#slot-username"></div>
<div data-adfs-move-to="#passwordInput" data-adfs-into="#slot-password"></div>
<div data-adfs-move-to="#submitButton" data-adfs-into="#slot-submit"></div>
<div data-adfs-move-to="#errorText" data-adfs-into="#slot-error"></div>Replace the target's text content. Useful for language fixes.
<span data-adfs-set-text="#userNameLabel">Email address</span>
<span data-adfs-set-text="#submitButton">Sign in</span>Replace the target's innerHTML.
<span data-adfs-set-html="#loginDescription">
Sign in with your <strong>company account</strong>.
</span>Set an attribute on the target element.
<meta data-adfs-set-attr="#userNameInput" data-adfs-attr="placeholder" data-adfs-value="your@email.com">Using <meta> keeps these invisible in the browser preview.
Add to any action element to make it conditional. The action only runs if the selector matches an element in the DOM. Useful for HRD vs password page detection.
<!-- Only runs on the HRD page -->
<div data-adfs-move-to="#hrdArea" data-adfs-into="#slot-hrd" data-adfs-if="#hrdArea"></div>
<!-- Only runs on the password page -->
<div data-adfs-move-to="#userNameInput" data-adfs-into="#slot-username" data-adfs-if="#loginArea"></div>These are the critical DOM elements ADFS depends on. Never remove or recreate
them — use move-to and hide instead.
| Element | Why it matters |
|---|---|
#loginForm |
The actual POST form — hide it, don't remove it |
input[type="hidden"] |
CSRF token, AuthenticationMethod, client-request-id |
#userNameInput |
Username field |
#passwordInput |
Password field — ADFS may change its type dynamically |
#submitButton |
ADFS attaches its own click handler |
#errorText |
Server-side error messages (bad password, locked account) |
#kmsiInput |
"Keep me signed in" checkbox |
#hrdArea |
Home Realm Discovery provider list (federated setups) |
#loginArea |
The login area wrapper (absent on HRD-only pages) |
examples/login/ — Builds a custom card shell, moves ADFS
inputs into styled slots, hides the original form.
node generator.js examples/login/template.html -o examples/login/dist/onload.jsexamples/hrd/ — Branded provider picker for federated
identity setups. Moves #hrdArea into a styled card.
node generator.js examples/hrd/template.html -o examples/hrd/dist/onload.jsThe generator wraps everything in an IIFE:
// Generated by adfs-login-generator — DO NOT EDIT
(function () {
// ── CSS ──────────────────────────────────────────────────────
var _s = document.createElement('style');
_s.textContent = `...`;
document.head.appendChild(_s);
// ── append-to: body ──────────────────────────────────────────
(function () {
var _t = document.body;
if (!_t) return;
var _el = document.createElement("div");
_el.setAttribute("class", "shell");
_el.setAttribute("id", "customShell");
_el.innerHTML = `...`;
_t.appendChild(_el);
}());
// ── move-to: #userNameInput ──────────────────────────────────
(function () {
var _src = document.querySelector("#userNameInput");
var _dst = document.querySelector("#slot-username");
if (_src && _dst) _dst.appendChild(_src);
}());
// ── hide: #loginForm ─────────────────────────────────────────
(function () {
var _t = document.querySelector("#loginForm");
if (_t) _t.style.display = 'none';
}());
}());