From Variable Extraction to Smart Filling: Building a Prompt Template Manager
From Variable Extraction to Smart Filling: Building a Prompt Template Manager#
Recently, while using AI tools like ChatGPT and Claude, I noticed many prompts are reusable. For scenarios like code review, bug fixing, and documentation generation, the prompt structure stays similar—only the parameters change. So I built a template management tool and documented the implementation approach.
The Variable Extraction Algorithm#
The core question: how do we extract {{variable}} placeholders from text?
The most direct approach uses regular expressions:
function extractVariables(template: string): string[] {
const matches = template.match(/\{\{(\w+)\}\}/g)
if (!matches) return []
return Array.from(new Set(matches.map(m => m.replace(/\{\{|\}\}/g, ''))))
}
Key details:
- Regex explanation:
\{\{(\w+)\}\}matches{{at the start and}}at the end, with(\w+)capturing the variable name - Deduplication: The same variable may appear multiple times, so we use
Setto remove duplicates - Variable naming convention:
\wonly matches letters, numbers, and underscores, avoiding unintended matches
For example, given:
Review this {{language}} code:
{{code}}
Focus areas: {{focusAreas}}
Output: ['language', 'code', 'focusAreas']
Template Filling: Variable Replacement#
After extracting variables, the next step is filling values. What seems like simple string replacement has several pitfalls:
function fillTemplate(template: string, variables: Record<string, string>): string {
let result = template
for (const [key, value] of Object.entries(variables)) {
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value || `{{${key}}}`)
}
return result
}
Key points:
- Global replacement: Use the
'g'flag to replace all matches, not just the first - Escape handling: In regex,
{and}need to be escaped as\\{and\\} - Empty value fallback: If a variable isn’t filled, keep the original placeholder rather than an empty string
Example:
const template = 'Language: {{lang}}, Code: {{code}}'
const result = fillTemplate(template, {
lang: 'JavaScript',
code: 'console.log("hello")'
})
// Output: Language: JavaScript, Code: console.log("hello")
LocalStorage Persistence: Custom Template Storage#
The tool supports user-created custom templates, requiring persistent storage. Browser LocalStorage is the simplest solution:
// Save to LocalStorage
const saveTemplates = (templates: PromptTemplate[]) => {
try {
localStorage.setItem('prompt-template-custom', JSON.stringify(templates))
} catch (e) {
console.error('Save failed:', e)
}
}
// Load from LocalStorage
const loadTemplates = (): PromptTemplate[] => {
try {
const saved = localStorage.getItem('prompt-template-custom')
return saved ? JSON.parse(saved) : []
} catch (e) {
console.error('Load failed:', e)
return []
}
}
Why use try-catch?#
LocalStorage operations can fail:
- Quota exceeded: Browsers typically limit 5MB; exceeding throws an error
- Private mode: Some browsers disable LocalStorage in incognito/private mode
- JSON parsing: Corrupted data causes
JSON.parseto throw
React Persistence Hook#
Use useEffect in React components for automatic persistence:
const [customTemplates, setCustomTemplates] = useState<PromptTemplate[]>([])
// Load on mount
useEffect(() => {
const saved = localStorage.getItem('prompt-template-custom')
if (saved) {
try {
setCustomTemplates(JSON.parse(saved))
} catch {}
}
}, [])
// Save on changes
useEffect(() => {
localStorage.setItem('prompt-template-custom', JSON.stringify(customTemplates))
}, [customTemplates])
Note the empty dependency array [] in the first useEffect ensures it runs only once on mount.
Template Categories and Icon Mapping#
For better UX, templates need categories and icons. Use a mapping table:
const CATEGORY_ICONS: Record<string, React.ReactNode> = {
'code-review': <Code className="w-4 h-4" />,
'bug-fix': <Bug className="w-4 h-4" />,
'feature': <Wrench className="w-4 h-4" />,
'documentation': <BookOpen className="w-4 h-4" />,
'translation': <Languages className="w-4 h-4" />,
}
When rendering, dynamically get the icon based on template.category:
<span className="text-accent-cyan">
{CATEGORY_ICONS[template.category] || <FileText className="w-4 h-4" />}
</span>
If a category has no matching icon, display the default FileText icon.
Built-in Template Design Philosophy#
The 8 built-in templates cover common scenarios:
| Template Name | Variable Count | Primary Use |
|---|---|---|
| Code Review | 4 | Code quality checks |
| Bug Fix | 5 | Bug identification and fixes |
| Feature Implementation | 5 | New feature development |
| Documentation | 3 | Documentation generation |
| Translation | 4 | Code language conversion |
| Summarize | 3 | Content summarization |
| Explain Code | 3 | Code explanation |
| Unit Test | 4 | Unit test generation |
Template design principles:
- Structured output: Clearly tell AI what content to output
- Rich context: Provide sufficient background information
- Constraints: Clear requirements prevent AI from diverging
Example Bug Fix template:
**Expected behavior:** {{expectedBehavior}}
**Actual behavior:** {{actualBehavior}}
**Error message:**
{{errorMessage}}
**Code:**
{{code}}
Please identify the bug and provide a fix with explanation.
This structure helps AI quickly locate the problem: expected vs actual behavior + error message + relevant code.
ID Generation: Timestamp + Random Number#
Custom templates need unique IDs, using timestamp plus random number:
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substring(2)
}
Date.now().toString(36): Converts millisecond timestamp to base-36, shortening lengthMath.random().toString(36).substring(2): Converts random number to base-36, removing0.prefix
Example output: m5x2k7abc123xyz
Real-time Preview and Copy#
As users fill variables, show real-time preview:
const previewText = useMemo(() => {
if (!selectedTemplate) return ''
return fillTemplate(selectedTemplate.template, variableValues)
}, [selectedTemplate, variableValues])
Use useMemo to cache results, only recalculating when template or variables change.
Copy to clipboard using modern browser API:
const handleCopy = async () => {
if (!previewText) return
await navigator.clipboard.writeText(previewText)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
navigator.clipboard.writeText() returns a Promise, requiring await. After successful copy, show success state for 2 seconds.
Final Result#
Based on the above implementation, I built an online tool: Prompt Template Manager
Key features:
- 8 built-in templates covering common development scenarios
- Custom template creation and management
- Automatic variable extraction and smart filling
- Real-time preview and one-click copy
- LocalStorage persistent storage
The core code is under 500 lines, but getting the details right takes effort. Hope this helps!
Related Tools: Code Formatter | Regex Tester