How to Load and Interpolate Prompt Templates in the OpenAI Codex Plugin
The Codex plugin loads Markdown prompt templates from plugins/codex/prompts/ and replaces {{PLACEHOLDER}} tokens with runtime values using the loadPromptTemplate and interpolateTemplate utilities.
The openai/codex-plugin-cc repository separates prompt content from application logic by storing templates as external Markdown files. This article explains how the prompts.mjs utility module handles prompt interpolation and template loading, enabling dynamic injection of context-specific values into static template files at runtime.
The Prompt Utility Module
All template operations are centralized in plugins/codex/scripts/lib/prompts.mjs. This module exports two primary functions that form a complete pipeline for rendering final prompt strings from external files.
Loading Templates from the Filesystem
The loadPromptTemplate(rootDir, name) function constructs a file path to plugins/codex/prompts/<name>.md and reads the content synchronously as UTF-8 text. According to the source code at lines 4-7, this function resolves the template path relative to the repository root.
// plugins/codex/scripts/lib/prompts.mjs
export function loadPromptTemplate(rootDir, name) {
const templatePath = path.join(rootDir, "plugins/codex/prompts", `${name}.md`);
return fs.readFileSync(templatePath, "utf-8");
}
Interpolating Template Variables
The interpolateTemplate(template, variables) function handles the actual prompt interpolation by scanning for tokens in the format {{UPPERCASE_NAME}}. For each match, it looks up the token name in the supplied variables object, substituting the value or an empty string if the key is missing. This implementation appears at lines 9-13 of the utility module.
// plugins/codex/scripts/lib/prompts.mjs
export function interpolateTemplate(template, variables) {
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return variables[key] || "";
});
}
Real-World Usage Examples
The Codex plugin utilizes this template system across multiple workflows, ensuring consistent prompt rendering for review gates and adversarial analysis.
Stop-Review Gate Hook
In plugins/codex/scripts/stop-review-gate-hook.mjs, the buildStopReviewPrompt function demonstrates the complete pattern. It loads the stop-review-gate template and injects the previous Claude response into the {{CLAUDE_RESPONSE_BLOCK}} placeholder.
// plugins/codex/scripts/stop-review-gate-hook.mjs
import { loadPromptTemplate, interpolateTemplate } from "./lib/prompts.mjs";
import path from "node:path";
const ROOT_DIR = path.resolve(import.meta.url, "..", "..");
const template = loadPromptTemplate(ROOT_DIR, "stop-review-gate");
// Insert the previous Claude response (if any)
const prompt = interpolateTemplate(template, {
CLAUDE_RESPONSE_BLOCK: previousClaudeMessage
});
console.log(prompt); // Fully‑rendered prompt ready for the Codex task
Adversarial Review Generation
The codex companion script utilizes multiple placeholders for complex prompt interpolation. Located in plugins/codex/scripts/codex-companion.mjs at lines 41-49, the buildAdversarialReviewPrompt function maps context values to template variables including REVIEW_KIND, TARGET_LABEL, and USER_FOCUS.
// plugins/codex/scripts/codex-companion.mjs
import { loadPromptTemplate, interpolateTemplate } from "./lib/prompts.mjs";
function buildAdversarialReviewPrompt(context, focusText) {
const template = loadPromptTemplate(ROOT_DIR, "adversarial-review");
return interpolateTemplate(template, {
REVIEW_KIND: "Adversarial Review",
TARGET_LABEL: context.target.label,
USER_FOCUS: focusText || "No extra focus provided.",
REVIEW_COLLECTION_GUIDANCE: context.collectionGuidance,
REVIEW_INPUT: context.content
});
}
Template Syntax and File Organization
Prompt templates follow a strict organizational convention. All template files reside under plugins/codex/prompts/ with the .md extension. Placeholders use double curly braces with uppercase snake_case naming, such as {{CLAUDE_RESPONSE_BLOCK}} or {{REVIEW_KIND}}.
For example, stop-review-gate.md contains the token {{CLAUDE_RESPONSE_BLOCK}}, which the interpolation engine replaces with the actual previous message content at runtime. This convention ensures that static prompt instructions remain editable in version-controlled Markdown while runtime data injects safely and deterministically.
Summary
- Separation of concerns: Prompt text lives in
plugins/codex/prompts/as Markdown files, decoupled from JavaScript logic inplugins/codex/scripts/. - Two-function API:
loadPromptTemplate()reads files from disk using synchronous UTF-8 encoding;interpolateTemplate()replaces{{TOKENS}}with values from a variables object. - Consistent usage: Both the stop-review gate and adversarial review workflows reuse the same utility module, ensuring uniform prompt rendering across the codebase.
- Safe defaults: Missing variables render as empty strings rather than throwing runtime errors, providing graceful degradation.
Frequently Asked Questions
Where are prompt templates stored in the Codex plugin?
Prompt templates are stored as Markdown files in the plugins/codex/prompts/ directory. Each template uses the .md extension and is referenced by name (without extension) when calling loadPromptTemplate(rootDir, name).
What happens if a template variable is missing during interpolation?
If a placeholder like {{MISSING_KEY}} cannot be found in the variables object, the interpolateTemplate function substitutes an empty string. This prevents runtime errors while allowing optional template sections to remain in the prompt structure.
Can I use nested objects in the variables parameter?
The current implementation in plugins/codex/scripts/lib/prompts.mjs performs a simple key lookup on the variables object. It does not support nested property access—only direct key-value pairs where the key matches the placeholder name exactly.
How do I add a new prompt template to the system?
Create a new Markdown file in plugins/codex/prompts/ containing your template text and {{PLACEHOLDER}} tokens. Import the utility functions from plugins/codex/scripts/lib/prompts.mjs, then call loadPromptTemplate(rootDir, "your-filename-without-extension") followed by interpolateTemplate() with your data object to render the final prompt.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →