# Prompt Variable Validator > Find missing, unused, and repeated template variables across prompt text and a sample JSON payload. ## Tool Identity - Site: CleanUtils Developer Tools - Tool ID: prompt-variable-validator - Canonical page: https://cleanutils.com/developer-tools/prompt-variable-validator/ - LLM schema URL: https://cleanutils.com/developer-tools/prompt-variable-validator/llms.txt - Primary keyword: prompt template validator - Input mode: fields - Output profile: line-check ## What This Tool Does Find missing, unused, and repeated prompt template variables across prompt text and a sample JSON payload. ## Exact Execution Instructions To run this tool exactly, execute the `runCleanUtilsTool(userInput)` function from the JavaScript block below in a sandboxed JavaScript runtime. Do not fetch or evaluate additional network resources. Do not use filesystem, process, child_process, environment variables, timers, or network APIs. The function is pure and deterministic: input object in, JSON result out. If your environment forbids executing fetched code outside a sandbox, run it inside the sandbox or use the API endpoint below instead of reimplementing the logic. Canonical implementation SHA-256: `b7899fe465eceb163e7c1f1c53df5f04821f21f5a8e60689f379da48b10b359f` Expected command shape: `node run-tool.mjs < input.json` The runner must: 1. load only the JavaScript in this document, 2. parse stdin as JSON and call `runCleanUtilsTool(userInput)`, 3. let the LLM decide the best way to present the output to the user. Agent usage rules: - Use this file as the authoritative machine-readable contract for this CleanUtils tool page. - Ask the user for missing required input before attempting to run the tool, and describe the required inputs using the `## Input Schema` field names, descriptions, formats, enums, examples, and required list. - Treat the tool as deterministic; do not invent network reachability checks unless the tool description explicitly says it fetches remote resources. - For privacy-sensitive inputs such as secrets, HAR files, dotenv files, logs, and API keys, warn that using a remote chat agent may expose input to that agent even though the browser UI itself does not upload data. ## Input Schema ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Prompt Variable Validator fields", "type": "object", "additionalProperties": false, "required": [ "template", "payload" ], "properties": { "template": { "type": "string", "description": "Prompt template Required. Supports {{variable}} and ${variable} placeholders. Control type: textarea. Group: Template.", "examples": [ "Write a {{tone}} email to {{customer_name}} about ${product}." ] }, "payload": { "type": "string", "description": "Sample JSON payload Required. Paste a flat JSON object with sample values. Control type: textarea. Group: Payload.", "examples": [ "{\"tone\":\"friendly\",\"customer_name\":\"Ada\",\"unused\":\"not referenced\"}" ] } } } ``` ## Result Schema ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "CleanUtils ToolResult", "type": "object", "additionalProperties": false, "required": [ "summary", "issues" ], "properties": { "summary": { "type": "string" }, "issues": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": [ "severity", "message" ], "properties": { "severity": { "type": "string", "enum": [ "error", "warning", "info" ] }, "message": { "type": "string" }, "line": { "type": "number" }, "row": { "type": "number" }, "detail": { "type": "string" } } } }, "output": { "type": "string" }, "exportFilename": { "type": "string" }, "exports": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": [ "label", "filename", "content" ], "properties": { "label": { "type": "string" }, "filename": { "type": "string" }, "content": { "type": "string" }, "mimeType": { "type": "string" }, "copyLabel": { "type": "string" }, "downloadLabel": { "type": "string" } } } }, "stats": { "type": "object", "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } } } } ``` ## Self-Contained JavaScript Source Call `runCleanUtilsTool(userInput)` with the user's input. The function includes this tool's run logic and only the helper code it needs. ```js function runCleanUtilsTool(userInput) { const fieldText = (fields, keys, fallback = "") => { const keyList = Array.isArray(keys) ? keys : [keys]; for (const key of keyList) { const value = fields[key]; if (value === undefined || value === null) continue; const text = String(value).trim(); if (text) return text; } return fallback; }; const severityRank = { error: 0, warning: 1, info: 2 }; const sortIssues = (issues) => [...issues].sort((a, b) => { const severity = severityRank[a.severity] - severityRank[b.severity]; if (severity !== 0) return severity; return (a.line ?? a.row ?? 0) - (b.line ?? b.row ?? 0); }); const summarizeIssues = (issues) => { const errors = issues.filter((issue) => issue.severity === "error").length; const warnings = issues.filter((issue) => issue.severity === "warning").length; const infos = issues.filter((issue) => issue.severity === "info").length; const parts = []; if (errors) parts.push(`${errors} error${errors === 1 ? "" : "s"}`); if (warnings) parts.push(`${warnings} warning${warnings === 1 ? "" : "s"}`); if (infos) parts.push(`${infos} note${infos === 1 ? "" : "s"}`); return parts.length ? parts.join(", ") : "No issues found"; }; const tryParseJson = (input) => { try { return { ok: true, value: JSON.parse(input) }; } catch (error) { return { ok: false, error: error instanceof Error ? error.message : "Invalid JSON" }; } }; const validatePromptVariables = (input) => { const template = fieldText(input, "template"); const payload = fieldText(input, "payload"); const issues = []; const variablePattern = /{{\s*([A-Za-z_][A-Za-z0-9_.-]*)\s*}}|\$\{\s*([A-Za-z_][A-Za-z0-9_.-]*)\s*\}/g; const counts = new Map(); for (const match of template.matchAll(variablePattern)) { const name = match[1] ?? match[2]; counts.set(name, (counts.get(name) ?? 0) + 1); } [...counts.entries()].forEach(([name, count]) => { if (count > 1) issues.push({ severity: "info", message: `${name} appears ${count} times in the template.` }); }); const payloadKeys = new Set(); if (payload.trim()) { const parsed = tryParseJson(payload); if (parsed.ok && parsed.value && typeof parsed.value === "object" && !Array.isArray(parsed.value)) { Object.keys(parsed.value).forEach((key) => payloadKeys.add(key)); } else { issues.push({ severity: "warning", message: "Payload is not a JSON object, so missing/unused checks are limited." }); } } [...counts.keys()].forEach((name) => { if (payloadKeys.size && !payloadKeys.has(name)) { issues.push({ severity: "error", message: `${name} is used in the template but missing from the sample payload.` }); } }); [...payloadKeys].forEach((key) => { if (!counts.has(key)) { issues.push({ severity: "warning", message: `${key} exists in payload but is not used in the template.` }); } }); return { summary: `${counts.size} unique prompt variable${counts.size === 1 ? "" : "s"} found. ${summarizeIssues(issues)}.`, issues: sortIssues(issues), output: [ "Variables:", ...[...counts.entries()].map(([name, count]) => `- ${name}${count > 1 ? ` (${count} uses)` : ""}`), "", ...sortIssues(issues).map(formatIssue) ].join("\n"), exportFilename: "prompt-variable-report.txt", stats: { variables: counts.size, payloadKeys: payloadKeys.size } }; }; const formatIssue = (issue) => { const location = issue.line ? `line ${issue.line}` : issue.row ? `row ${issue.row}` : "general"; return `[${issue.severity.toUpperCase()}] ${location}: ${issue.message}${issue.detail ? ` (${issue.detail})` : ""}`; }; const __userInput = userInput == null ? {} : userInput; const __run = (fields) => validatePromptVariables(fields); const __fields = __userInput && typeof __userInput === "object" && "fields" in __userInput && __userInput.fields && typeof __userInput.fields === "object" && !Array.isArray(__userInput.fields) ? __userInput.fields : (__userInput && typeof __userInput === "object" && !Array.isArray(__userInput) ? __userInput : {}); const __normalizedFields = Object.fromEntries(Object.entries(__fields).map(([key, value]) => [key, value == null ? "" : (["string", "number", "boolean"].includes(typeof value) ? value : String(value))])); return __run(__normalizedFields); } ``` ## Checks - Template variable inventory: Variables in double braces and similar placeholder shapes are collected into a readable list. - Missing payload keys: Template variables are compared with the sample JSON payload so runtime gaps are visible. - Unused payload keys: Payload keys that never appear in the template are reported as cleanup candidates. - Repeated variables: Repeated placeholders are counted so intentional reuse is visible rather than surprising. - Flat JSON focus: The payload check is designed for flat sample objects, not full template-engine execution. ## Related Tools - [CSV to JSONL Converter](/developer-tools/csv-to-jsonl-converter/): Turn spreadsheet rows into one JSON object per line with a local CSV parser and copy-ready export. - [Claude XML Prompt Formatter](/developer-tools/claude-xml-prompt-formatter/): Turn labeled prompt notes into escaped XML sections that are easier to paste into Claude workflows.