AI Disclosure Statement Generator
This tool helps students disclose their use of generative AI in university assignments. It generates a plain-text declaration summarising which AI tools you used and how, ready to paste into your appendix. It is free to use, fully open source, and runs entirely in your browser, so nothing you enter is ever sent to a server. See the privacy note and disclaimer of responsibility at the end of this page for details.
Why use this tool?
AI tool use among students continues to grow. Some assignments prohibit generative AI entirely; others allow it but require disclosure, though clear guidance on how to disclose isn’t always available.
If that’s your situation, this tool is for you: a structured, task-by-task checklist for recording your AI use, adapted from the GAIDeT Declaration Generator (Suchikova et al. 2026).
Select every task a generative AI tool helped with, add a short description of what you asked it to do and how you used the output, and get a ready-to-copy declaration at the end of this page.
exampleDeclarationText = `AI Disclosure Declaration
AI tools used: ChatGPT (GPT-5.5), Claude Code (Sonnet 4.6), Grammarly
The core analysis, writing, and conclusions in this assignment are my own. Generative AI assisted with the following specific, limited tasks:
- Idea generation
Tool(s): ChatGPT (GPT-5.5)
Summary: I had a general research area in mind; I asked ChatGPT to suggest possible angles, then chose and developed the final research question myself.
- Code generation
Tool(s): Claude Code (Sonnet 4.6)
Summary: I wrote the core analysis logic myself; Claude Code was used only to draft repetitive boilerplate (e.g. file-loading functions), which I reviewed, tested, and adapted to my dataset.
- Proofreading
Tool(s): Grammarly
Summary: After completing my own final draft, I ran it through Grammarly for grammar and punctuation checks, and manually reviewed and accepted only the suggestions I agreed with.
Responsibility for the submitted assignment lies entirely with the author of this disclosure statement.
Generative AI tools cannot be listed as authors and do not bear responsibility for the final outcomes.
Declaration submitted by: Jane Doe`el("div", { class: "gaidet-example-wrap" }, [
el("button", {
class: "gaidet-example-toggle",
onclick: () => { mutable showExample = !showExample; }
}, showExample ? "Hide example declaration" : "Show example declaration"),
showExample ? el("pre", { class: "gaidet-example" }, [
el("strong", {}, "AI Disclosure Declaration"),
exampleDeclarationText.replace(/^AI Disclosure Declaration/, "")
]) : null
])Many universities do not have a single institution-wide policy on generative AI. Permission varies by Department, School, programme, and even individual module or assessment. Before using any AI tool, check your module descriptor and Learning Management System (LMS) course site, and ask your lecturer whether (and how) it may be used. Some assignments may prohibit generative AI entirely, or allow AI use only for some of the tasks listed below.
Disclosing a task or AI tool here does not constitute permission to use it, nor a means of circumventing any such prohibition imposed by your institution, School, programme, module, or assessment brief.
This declaration only covers disclosure; depending on your citation style, you may also need to formally cite AI tools in your reference list. You are ultimately responsible for complying with your institution’s and module’s policies; see the full disclaimer at the end of this page for details.
function el(tag, props = {}, children = []) {
const node = document.createElement(tag);
for (const [k, v] of Object.entries(props)) {
if (v === undefined || v === null) continue;
if (k.startsWith("on") && typeof v === "function") {
node.addEventListener(k.slice(2), v);
} else if (k === "checked" || k === "value") {
node[k] = v;
} else if (k === "class") {
node.className = v;
} else {
node.setAttribute(k, v);
}
}
for (const child of [].concat(children)) {
if (child === null || child === undefined) continue;
node.append(child instanceof Node ? child : document.createTextNode(child));
}
return node;
}
function taskIsComplete(sentence, tools, openEnded, customLabel) {
return !!(sentence && sentence.trim())
&& Object.keys(tools ?? {}).length > 0
&& (!openEnded || !!(customLabel && customLabel.trim()));
}
function autoGrow(textarea, attempts = 0) {
if (!textarea.isConnected) {
if (attempts < 30) requestAnimationFrame(() => autoGrow(textarea, attempts + 1));
return;
}
// Deferred to the next frame so the keystroke itself paints immediately;
// reading scrollHeight synchronously here would force a layout reflow
// before the browser can render the typed character.
if (textarea._autoGrowRaf) cancelAnimationFrame(textarea._autoGrowRaf);
textarea._autoGrowRaf = requestAnimationFrame(() => {
textarea.style.height = "auto";
textarea.style.height = `${textarea.scrollHeight}px`;
});
}
function debounce(fn, delay = 900) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
function preserveFocus(build) {
const active = document.activeElement;
const field = active?.dataset?.field;
const selStart = active?.selectionStart ?? null;
const selEnd = active?.selectionEnd ?? null;
const scrollY = window.scrollY;
const scrollX = window.scrollX;
const node = build();
if (field) {
let attempts = 0;
const refocus = () => {
attempts += 1;
const next = node.querySelector(`[data-field="${CSS.escape(field)}"]`);
if (!next) return;
if (!next.isConnected) {
if (attempts < 30) requestAnimationFrame(refocus);
return;
}
next.focus({ preventScroll: true });
if (selStart !== null && typeof next.setSelectionRange === "function") {
next.setSelectionRange(selStart, selEnd);
}
window.scrollTo(scrollX, scrollY);
};
requestAnimationFrame(refocus);
}
return node;
}{
document.addEventListener("click", (e) => {
for (const { input, closeList } of autocompletePickers.values()) {
if (!input.parentElement?.contains(e.target)) closeList();
}
});
}{
const hasData = studentName.trim().length > 0
|| Object.keys(selections).length > 0;
window.onbeforeunload = hasData ? (e) => { e.preventDefault(); e.returnValue = ""; } : null;
}noVersionTools = new Set([
"Otter.ai",
"Grammarly",
"QuillBot",
"Notion AI",
"Canva",
"NotebookLM",
"Consensus",
"Elicit",
"Scite"
])promptsToggle = el("div", { class: "gaidet-prompts-question" }, [
el("p", { class: "gaidet-tool-label" }, "Provide prompts used for each task?"),
el("p", { class: "gaidet-tool-instructions" }, "If you choose \"Yes\", an extra box will appear under each ticked task where you can copy-paste the exact prompt(s) you used with the AI tool, for extra transparency. This is optional and off by default, but some lecturers or assignment briefs may require you to include the full prompts."),
el("div", { class: "gaidet-tool-grid" }, [
el("label", { class: "gaidet-tool-option" }, [
el("input", {
type: "radio",
name: "gaidet-provide-prompts",
checked: providePrompts === true,
onchange: (e) => { if (e.target.checked) mutable providePrompts = true; }
}),
" Yes"
]),
el("label", { class: "gaidet-tool-option" }, [
el("input", {
type: "radio",
name: "gaidet-provide-prompts",
checked: providePrompts === false,
onchange: (e) => { if (e.target.checked) mutable providePrompts = false; }
}),
" No"
])
])
])form = {
// A working copy of `selections`, shared by every commit function created
// during this render. `selections` itself is frozen for the lifetime of this
// render (it won't reflect a commit made moments ago by this same render,
// even though the commit did trigger a future re-render), so committing from
// it directly risks one task's update clobbering another's when several
// fields across tasks are edited/committed in quick succession. Every commit
// below reads and writes `pendingSelections` instead, so they chain off each
// other correctly within this render, and only publish to the reactive
// `selections` (starting the next render) as their last step.
let pendingSelections = selections;
return preserveFocus(() => el("div", {}, data.categories.map(cat => {
const categoryEl = el("div", { class: "gaidet-category" }, [
el("h3", {}, cat.label),
...cat.items.map(item => {
const key = `${cat.id}::${item.id}`;
const checked = key in selections;
const sel = selections[key];
const isComplete = checked && taskIsComplete(sel.sentence, sel.tools, item.openEnded, sel.customLabel);
// Tracks this task's tools independently of `selections`, which is frozen
// for the lifetime of this render: a commit updates `selections` and
// schedules a full re-render, but doesn't retroactively unfreeze it for
// code (like markCompleteIfReady, below) still running in this same render.
let currentTools = sel?.tools ?? {};
function buildToolPicker() {
const toolIndex = data.aiToolGroups.flatMap(group =>
group.tools.map(tool => ({ name: tool, category: group.label, needsVersion: !noVersionTools.has(tool) }))
);
function currentSelected() {
return Object.entries(currentTools ?? {}).map(([name, val]) => ({
name,
needsVersion: typeof val === "object",
version: (val && typeof val === "object" && val.version) || ""
}));
}
function isSelected(name) {
return currentSelected().some(t => t.name.toLowerCase() === name.toLowerCase());
}
function commit(nextSelected) {
const next = { ...pendingSelections };
if (!next[key]) return;
const toolsMap = {};
nextSelected.forEach(t => {
toolsMap[t.name] = t.needsVersion ? { version: t.version || "" } : true;
});
currentTools = toolsMap;
next[key] = { ...next[key], tools: toolsMap };
pendingSelections = next;
mutable selections = next;
}
function escapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function highlight(name, query) {
if (!query) return name;
const re = new RegExp(`(${escapeRegExp(query)})`, "ig");
return name.replace(re, "<mark>$1</mark>");
}
const hasSelection = currentSelected().length > 0;
const input = el("input", {
type: "text",
class: hasSelection ? "gaidet-autocomplete-input gaidet-autocomplete-input-more" : "gaidet-autocomplete-input",
"data-field": `toolSearch::${key}`,
"aria-label": hasSelection ? "Add another AI tool used for this task" : "Search for an AI tool",
placeholder: hasSelection ? "+ Add another tool" : "Start typing to add a tool…",
autocomplete: "off"
});
const list = el("ul", { class: "gaidet-autocomplete-list" }, []);
list.style.display = "none";
const selectedWrap = el("div", { class: "gaidet-autocomplete-selected" }, []);
let activeIndex = -1;
let currentMatches = [];
function closeList() {
list.style.display = "none";
list.innerHTML = "";
activeIndex = -1;
currentMatches = [];
}
function selectTool(entry) {
if (!isSelected(entry.name)) {
commit([...currentSelected(), { name: entry.name, needsVersion: entry.needsVersion, version: "" }]);
}
input.value = "";
closeList();
input.focus();
}
function renderList(query) {
const q = query.trim();
if (!q) { closeList(); return; }
const ql = q.toLowerCase();
const matches = toolIndex.filter(t => t.name.toLowerCase().includes(ql) && !isSelected(t.name));
list.innerHTML = "";
if (matches.length === 0) {
currentMatches = [{ name: q, needsVersion: true, isCustom: true }];
const li = el("li", { class: "gaidet-autocomplete-item gaidet-autocomplete-add-custom" }, []);
li.append("Add “", Object.assign(document.createElement("strong"), { textContent: q }), "” as a tool");
li.addEventListener("mousedown", (e) => { e.preventDefault(); selectTool(currentMatches[0]); });
list.append(li);
list.style.display = "block";
activeIndex = -1;
return;
}
currentMatches = matches;
matches.forEach(entry => {
const li = el("li", { class: "gaidet-autocomplete-item" }, []);
const nameSpan = el("span", {}, []);
nameSpan.innerHTML = highlight(entry.name, q);
const catSpan = el("span", { class: "gaidet-autocomplete-item-category" }, entry.category);
li.append(nameSpan, catSpan);
li.addEventListener("mousedown", (e) => { e.preventDefault(); selectTool(entry); });
list.append(li);
});
activeIndex = -1;
list.style.display = "block";
}
function setActive(i) {
const items = list.querySelectorAll(".gaidet-autocomplete-item");
items.forEach((node, idx) => node.classList.toggle("active", idx === i));
if (items[i]) items[i].scrollIntoView({ block: "nearest" });
}
input.addEventListener("input", (e) => renderList(e.target.value));
input.addEventListener("keydown", (e) => {
const items = list.querySelectorAll(".gaidet-autocomplete-item");
if (e.key === "ArrowDown") {
e.preventDefault();
if (!items.length) return;
activeIndex = (activeIndex + 1) % items.length;
setActive(activeIndex);
} else if (e.key === "ArrowUp") {
e.preventDefault();
if (!items.length) return;
activeIndex = (activeIndex - 1 + items.length) % items.length;
setActive(activeIndex);
} else if (e.key === "Enter") {
e.preventDefault();
if (activeIndex >= 0 && currentMatches[activeIndex]) {
selectTool(currentMatches[activeIndex]);
} else if (currentMatches.length === 1) {
selectTool(currentMatches[0]);
}
} else if (e.key === "Escape") {
closeList();
}
});
autocompletePickers.set(key, { input, closeList });
function renderSelected() {
selectedWrap.innerHTML = "";
currentSelected().forEach(entry => {
const row = el("div", { class: "gaidet-autocomplete-chip" }, []);
const header = el("div", { class: "gaidet-autocomplete-chip-header" }, [
el("span", { class: "gaidet-autocomplete-chip-name" }, entry.name),
el("button", {
type: "button",
class: "gaidet-autocomplete-remove",
"aria-label": `Remove ${entry.name}`,
onclick: () => commit(currentSelected().filter(t => t.name !== entry.name))
}, "×")
]);
row.append(header);
if (entry.needsVersion) {
const suggestedVersion = (data.commonVersions ?? {})[entry.name];
row.append(el("div", { class: "gaidet-autocomplete-version-wrap" }, [
el("label", { class: "gaidet-autocomplete-version-label" }, `Model/version of ${entry.name}`),
el("input", {
type: "text",
class: "gaidet-autocomplete-version-input",
"data-field": `toolVersion::${key}::${entry.name}`,
"aria-label": `${entry.name} model or version`,
placeholder: suggestedVersion ? `e.g., ${suggestedVersion}` : "Model/version",
value: entry.version,
onblur: (e) => {
commit(currentSelected().map(t => t.name === entry.name ? { ...t, version: e.target.value } : t));
}
})
]));
}
selectedWrap.append(row);
});
}
renderSelected();
return el("div", { class: "gaidet-autocomplete" }, [
el("p", { class: "gaidet-tool-label" }, "AI tool(s) used for this task:"),
el("p", { class: "gaidet-tool-instructions" }, "Press Enter or click a suggestion to add a tool. If yours isn't listed, type its name and add it as a custom tool."),
el("div", { class: "gaidet-autocomplete-wrap" }, [input, list]),
selectedWrap
]);
}
const checkboxLabel = el("label", {}, [
el("input", {
type: "checkbox",
checked,
onchange: (e) => {
const next = { ...pendingSelections };
if (e.target.checked) {
next[key] = {
label: item.label,
openEnded: item.openEnded ?? false,
customLabel: next[key]?.customLabel ?? "",
sentence: next[key]?.sentence ?? "",
prompts: next[key]?.prompts ?? "",
tools: next[key]?.tools ?? {},
proceeded: next[key]?.proceeded ?? false
};
} else {
delete next[key];
}
pendingSelections = next;
mutable selections = next;
}
}),
el("span", { class: "gaidet-item-label" }, ` ${item.label}`),
isComplete ? el("span", { class: "gaidet-item-complete", "aria-label": "Sentence and tool provided" }, " ✓") : null
]);
const sentenceTextarea = el("textarea", {
class: "gaidet-sentence",
"data-field": `sentence::${key}`,
"aria-label": "Description of what you asked the AI to do and how you used the output",
placeholder: "What did you ask the AI to do, and how did you use the output?",
maxlength: "3000",
value: sel?.sentence ?? "",
onblur: (e) => {
const next = { ...pendingSelections };
if (next[key]) {
next[key] = { ...next[key], sentence: e.target.value };
pendingSelections = next;
mutable selections = next;
}
}
});
function markCompleteIfReady() {
// Read sentence/tools/customLabel from live sources (the textarea/input's
// own DOM value, and the `currentTools` tracker) rather than `selections`,
// which is frozen for this render and can be missing an edit that was
// just committed a moment ago in this same render.
const currentCustomLabel = customLabelInput ? customLabelInput.value : (pendingSelections[key]?.customLabel ?? "");
const taskComplete = taskIsComplete(sentenceTextarea.value, currentTools, item.openEnded, currentCustomLabel);
if (!taskComplete) return false;
detailsDiv.classList.add("gaidet-item-details-complete");
const next = { ...pendingSelections };
if (next[key]) {
next[key] = {
...next[key],
tools: currentTools,
sentence: sentenceTextarea.value,
customLabel: currentCustomLabel,
proceeded: true
};
pendingSelections = next;
mutable selections = next;
}
return true;
}
const promptsTextarea = providePrompts ? el("textarea", {
class: "gaidet-sentence",
"data-field": `prompts::${key}`,
"aria-label": "Prompts used for this task",
placeholder: "If applicable, please copy-paste the prompts you have used.",
value: sel?.prompts ?? "",
onblur: (e) => {
const next = { ...pendingSelections };
if (next[key]) {
next[key] = { ...next[key], prompts: e.target.value };
pendingSelections = next;
mutable selections = next;
}
}
}) : null;
const customLabelInput = item.openEnded ? el("input", {
type: "text",
"data-field": `customLabel::${key}`,
"aria-label": "Task or component description",
style: "width:100%;max-width:500px;",
placeholder: "What was this task or component? (short description)",
value: sel?.customLabel ?? "",
onblur: (e) => {
const next = { ...pendingSelections };
if (next[key]) {
next[key] = { ...next[key], customLabel: e.target.value };
pendingSelections = next;
mutable selections = next;
}
}
}) : null;
const detailsDiv = checked ? el("div", { class: "gaidet-item-details" }, [
customLabelInput,
buildToolPicker(),
el("div", { class: "gaidet-sentence-wrap" }, [sentenceTextarea]),
providePrompts ? promptsTextarea : null
]) : null;
if (detailsDiv && isComplete && sel.proceeded) {
detailsDiv.classList.add("gaidet-item-details-complete");
}
if (detailsDiv) {
autoGrow(sentenceTextarea);
sentenceTextarea.addEventListener("input", (e) => autoGrow(e.target));
// "Moved to the next section": once focus leaves this task's fields
// (tabbing/clicking to the next task, or down to the declaration),
// mark it complete if it qualifies.
detailsDiv.addEventListener("focusout", () => markCompleteIfReady());
}
if (detailsDiv && providePrompts) {
autoGrow(promptsTextarea);
promptsTextarea.addEventListener("input", (e) => autoGrow(e.target));
}
const details = detailsDiv;
return el("div", { class: "gaidet-item" }, [checkboxLabel, details]);
})
]);
return categoryEl;
})));
}Detailed task breakdown
Tick every task where you delegated work to AI, then add the tool(s) you used for it. Give the exact model or version where you can (e.g. GPT-5.5), since capability varies a lot between versions.
declarationText = {
const entries = Object.values(selections);
function formatToolWithVersion([name, val]) {
const version = (val && typeof val === "object" && val.version) ? val.version.trim() : "";
return version ? `${name} (${version})` : name;
}
const overallToolNames = [...new Set(entries.flatMap(e =>
Object.entries(e.tools ?? {}).map(formatToolWithVersion)
))].sort((a, b) => a.localeCompare(b));
const overallToolsPart = overallToolNames.length > 0 ? overallToolNames.join(", ") : "[no tools ticked]";
const heading = `AI Disclosure Declaration\n\nAI tools used: ${overallToolsPart}\n`;
const signedBy = studentName.trim() || "[ADD NAME]";
const closing = `\n\nResponsibility for the submitted assignment lies entirely with the author of this disclosure statement.\n\nGenerative AI tools cannot be listed as authors and do not bear responsibility for the final outcomes.\n\nDeclaration submitted by: ${signedBy}`;
if (entries.length === 0) {
return `${heading}\nNo tasks have been ticked yet.${closing}`;
}
const lines = entries.map(e => {
const labelPart = (e.customLabel && e.customLabel.trim()) ? e.customLabel.trim() : e.label;
const itemToolNames = Object.entries(e.tools ?? {}).map(formatToolWithVersion);
const itemToolsPart = itemToolNames.length > 0 ? itemToolNames.join(", ") : "[no tool specified]";
const toolLabel = itemToolNames.length === 1 ? "Tool" : "Tools";
const sentencePart = e.sentence.trim() || "[please add a short explainer]";
const promptsPart = providePrompts && e.prompts && e.prompts.trim() ? `\n Prompts: ${e.prompts.trim()}` : "";
return `- ${labelPart}\n ${toolLabel}: ${itemToolsPart}\n Summary: ${sentencePart}${promptsPart}`;
});
return `${heading}\nThe following generative AI tasks were delegated as part of this assignment:\n\n${lines.join("\n\n")}${closing}`;
}nameField = preserveFocus(() => el("div", { class: "gaidet-name-field" }, [
el("label", {}, [
"Your name",
el("input", {
type: "text",
"data-field": "studentName",
"aria-label": "Your name",
style: "display:block;width:100%;max-width:300px;margin-top:0.5rem;",
placeholder: "e.g. Jane Doe",
value: studentName,
oninput: debounce((e) => { mutable studentName = e.target.value; })
})
]),
el("p", { class: "gaidet-tool-instructions" }, "Optional. Used to complete the “Declaration submitted by” line at the end of the generated disclaimer.")
]))outputBox = {
const entries = Object.values(selections);
const noTasks = entries.length === 0;
const incompleteEntries = entries.filter(sel =>
!(sel.sentence && sel.sentence.trim())
|| Object.keys(sel.tools ?? {}).length === 0
|| (sel.openEnded && !(sel.customLabel && sel.customLabel.trim()))
);
const incomplete = noTasks || incompleteEntries.length > 0;
const warningContent = noTasks
? "Please tick at least one task below before creating the declaration."
: incompleteEntries.length > 0
? [
"Please add a short description and at least one tool before creating the declaration, for:",
el("ul", { class: "gaidet-warning-list" },
incompleteEntries.map(sel => el("li", {},
(sel.customLabel && sel.customLabel.trim()) ? sel.customLabel.trim() : sel.label
))
)
]
: null;
const warning = (incomplete && (copyWarning || declarationConfirmed) && warningContent)
? el("div", { class: "gaidet-warning" }, warningContent)
: null;
const showDeclaration = declarationConfirmed && !incomplete;
const successText = copySuccess === "download"
? "Declaration downloaded as a text file. You can open it, paste it into your assignment, and make manual edits if needed."
: "Declaration copied to clipboard. You can paste it into your assignment and make manual edits if needed.";
const success = (showDeclaration && copySuccess && declarationText === copiedText)
? el("div", { class: "gaidet-success" }, successText)
: null;
const placeholderText = incomplete
? "Your declaration will appear here once every ticked task has a tool and a short description."
: declarationConfirmed
? "Your declaration will appear here."
: "Everything looks ready. Click “Create declaration” below to generate it.";
const confirmBox = showConfirmPrompt ? el("div", { class: "gaidet-confirm" }, [
el("p", {}, "Did you disclose all use of generative AI in this assignment?"),
el("div", { class: "gaidet-confirm-actions" }, [
el("button", {
class: "gaidet-btn-primary",
onclick: () => {
mutable declarationConfirmed = true;
mutable showConfirmPrompt = false;
mutable copySuccess = false;
}
}, "Yes, generate declaration"),
el("button", {
class: "ga <idet-btn-secondary",
onclick: () => { mutable showConfirmPrompt = false; }
}, "No, let me edit")
])
]) : null;
function onCreateClick() {
if (incomplete) {
mutable copyWarning = true;
return;
}
mutable copyWarning = false;
// Every entry is already complete at this point (the `incomplete` check
// above guarantees it), so generating the declaration also marks any
// not-yet-proceeded task as done, turning its box green.
const next = { ...selections };
let anyNewlyProceeded = false;
for (const k of Object.keys(next)) {
if (!next[k].proceeded) {
next[k] = { ...next[k], proceeded: true };
anyNewlyProceeded = true;
}
}
if (anyNewlyProceeded) mutable selections = next;
if (declarationConfirmed) {
mutable copySuccess = false;
mutable justUpdated = true;
setTimeout(() => { mutable justUpdated = false; }, 1800);
} else {
mutable showConfirmPrompt = true;
}
}
return el("div", { class: "gaidet-output", id: "gaidet-output-section" }, [
el("h3", {}, "Generated declaration"),
warning,
confirmBox,
success,
showConfirmPrompt
? null
: showDeclaration
? el("pre", { id: "gaidet-output-text", class: justUpdated ? "gaidet-output-flash" : "" }, [
el("strong", {}, "AI Disclosure Declaration"),
declarationText.replace(/^AI Disclosure Declaration/, "")
])
: (() => {
const icon = el("div", { class: "gaidet-output-placeholder-icon" }, []);
icon.innerHTML = `<svg width="34" height="34" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l1.9 5.6L19.5 9.5l-5.6 1.9L12 17l-1.9-5.6L4.5 9.5l5.6-1.9L12 2z"/><path d="M19 14.5l0.9 2.6l2.6 0.9l-2.6 0.9L19 21.5l-0.9-2.6l-2.6-0.9l2.6-0.9L19 14.5z" opacity="0.65"/></svg>`;
return el("div", { class: "gaidet-output-placeholder" }, [
icon,
el("p", {}, placeholderText)
]);
})(),
el("div", { class: "gaidet-output-actions" }, [
showConfirmPrompt ? null : el("button", {
class: "gaidet-btn-primary",
onclick: onCreateClick
}, declarationConfirmed ? "Update declaration" : "Create declaration"),
showDeclaration ? el("button", {
class: "gaidet-btn-secondary",
onclick: () => {
navigator.clipboard.writeText(declarationText);
mutable copySuccess = "copy";
mutable copiedText = declarationText;
}
}, "Copy to clipboard") : null,
showDeclaration ? el("button", {
class: "gaidet-btn-secondary",
onclick: () => {
const blob = new Blob([declarationText], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "ai-disclosure-declaration.txt";
a.click();
URL.revokeObjectURL(url);
mutable copySuccess = "download";
mutable copiedText = declarationText;
}
}, "Download as txt file") : null
])
]);
}
This declaration is a starting point. I recommend reviewing and adjusting the generated wording to fit your own voice, your module’s requirements, and your institution’s referencing style before submitting. The source code for this website is available on GitHub.
Save or resume your progress
Nothing is saved automatically. If you want to continue later, click “Save progress” to save a JSON file to your computer, then use “Load progress” to pick it up again on your next visit. This file is saved directly to your device only; nothing is sent to a server.
progressBox = {
const fileInput = el("input", {
type: "file",
accept: "application/json",
style: "display:none;",
onchange: (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
try {
const data = JSON.parse(reader.result);
mutable selections = data.selections ?? {};
mutable studentName = data.studentName ?? "";
mutable providePrompts = data.providePrompts ?? false;
} catch (err) {
alert("Could not read this file. Please make sure it's a progress file saved from this tool.");
}
e.target.value = "";
};
reader.readAsText(file);
}
});
return el("div", { class: "gaidet-item" }, [
el("div", { class: "gaidet-output-actions" }, [
el("button", {
type: "button",
class: "gaidet-btn-secondary",
onclick: () => {
const payload = { version: 1, studentName, providePrompts, selections };
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "ai-disclosure-progress.json";
a.click();
URL.revokeObjectURL(url);
}
}, "Save progress"),
el("button", {
type: "button",
class: "gaidet-btn-secondary",
onclick: () => fileInput.click()
}, "Load progress"),
fileInput
])
]);
}
About the maintainer
This tool was built by Stefan Müller, with substantial help from Claude Code, which handled most of the coding and drafting under the maintainer’s direction.
I’d love to hear how you’re using this tool and any suggestions for improving it. Please share your thoughts via this short feedback form.
How to cite this tool
If you use this tool in your teaching, you can cite it as:
Müller, Stefan (2026). AI Disclosure Statement Generator. https://disclose-ai.app
Related reading
Learn more about generative AI declaration generators, the inspiration behind this project:
Suchikova, Yana, Tsybuliak, Natalia, Teixeira da Silva, Jaime A., & Nazarovets, Serhii (2026). “GAIDeT (Generative AI Delegation Taxonomy): A Taxonomy for Humans to Delegate Tasks to Generative Artificial Intelligence in Scientific Research and Publishing“. Accountability in Research, 33(3). 10.1080/08989621.2025.2544331
Disclaimer of responsibility
This tool is provided purely as a free, unofficial aid to help you structure your own AI-use disclosure. It is not legal, academic, or official institutional guidance, and using it does not constitute compliance with any university, School, programme, or module policy.
The author, Stefan Müller, and any contributors accept no responsibility or liability whatsoever for how this tool is used, for whether the tasks and tools listed by the user constitute a complete account of all generative AI use in connection with the relevant work, for whether such use is declared honestly and in good faith, or for any academic, disciplinary, legal, or other consequences arising from its use or misuse.
You are solely responsible for checking your own university’s and module’s specific generative AI policies and citation requirements, and for confirming directly with your lecturer or module coordinator whether, and how, AI tools may be used before you submit any work. The author cannot be held legally or otherwise accountable for any reliance placed on this tool.
Note on privacy
This tool is free to use and fully open source, so you can verify these claims yourself. It does not store, transmit, or save any data you enter, anywhere. Everything runs entirely in your own browser and disappears the moment you close or refresh the page. The generated text is provided only so you can copy it into your own assignment appendix.