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:

  1. Regex explanation: \{\{(\w+)\}\} matches {{ at the start and }} at the end, with (\w+) capturing the variable name
  2. Deduplication: The same variable may appear multiple times, so we use Set to remove duplicates
  3. Variable naming convention: \w only 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:

  1. Global replacement: Use the 'g' flag to replace all matches, not just the first
  2. Escape handling: In regex, { and } need to be escaped as \\{ and \\}
  3. 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:

  1. Quota exceeded: Browsers typically limit 5MB; exceeding throws an error
  2. Private mode: Some browsers disable LocalStorage in incognito/private mode
  3. JSON parsing: Corrupted data causes JSON.parse to 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:

  1. Structured output: Clearly tell AI what content to output
  2. Rich context: Provide sufficient background information
  3. 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 length
  • Math.random().toString(36).substring(2): Converts random number to base-36, removing 0. 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