# How to Customize Jinja Templates for Different Resume Formats in Hiring Agent

> Learn to customize Jinja templates for dynamic resume parsing in Hiring Agent. Edit, register, and invoke templates to expertly handle diverse resume layouts and structures.

- Repository: [HackerRank/hiring-agent](https://github.com/interviewstreet/hiring-agent)
- Tags: how-to-guide
- Published: 2026-07-10

---

**You customize resume parsing in the Hiring Agent by editing Jinja files in `prompts/templates/`, registering them in `TemplateManager`, and invoking them from [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py) to handle any resume layout or section structure.**

The Hiring Agent repository (interviewstreet/hiring-agent) uses a declarative template system to extract structured data from PDF resumes using Large Language Models (LLMs). By customizing Jinja templates for different resume formats, you can adapt the pipeline to parse custom sections, handle unconventional layouts, or add new fields like certifications without modifying core parsing logic.

## Understanding the Template Architecture

The pipeline revolves around three core components that work together to convert PDF resumes into structured JSON:

- **Jinja templates** (`prompts/templates/*.jinja`) – Declarative instructions that tell the LLM how to parse specific resume sections (basics, work, education, etc.)
- **TemplateManager** ([`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py)) – Loads all templates at start-up, caches them, and renders chosen templates with resume markdown via `render_template()`
- **PDF handler** ([`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py)) – Splits PDF-converted markdown into logical sections and orchestrates LLM calls

The flow works as follows: [`pymupdf_rag.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pymupdf_rag.py) converts PDF pages to markdown, [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py) extracts section blocks, `TemplateManager.render_template()` injects the markdown into the appropriate Jinja template, and [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py) sends the rendered prompt to the LLM provider (Ollama or Gemini).

## Creating Custom Templates for New Resume Sections

To support a resume format that lists **certifications** in a separate block, follow this four-step process:

### 1. Create the Template File

Add `certifications.jinja` under `prompts/templates/`:

```jinja
Extract ONLY the certifications from this resume.

--- The input resume markdown starts here ---
{{ text_content }}
--- The input resume markdown ends here ---

Return ONLY a JSON object with this structure:
{
  "certifications": [
    {
      "name": "Certification name",
      "issuer": "Issuing organization",
      "date": "YYYY-MM"
    }
  ]
}

**IMPORTANT**: Return ONLY valid JSON.

```

### 2. Register the Template

Update `TemplateManager._load_templates()` in [`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py) to include the new entry in the `template_files` dictionary:

```python
"certifications": "certifications.jinja",

```

### 3. Expose the Section in the PDF Handler

In [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py), extract the markdown block for certifications and render the template:

```python
cert_prompt = manager.render_template(
    "certifications", text_content=cert_block
)
cert_json = llm.ask(cert_prompt)

```

### 4. Update the Data Model

Add a field to `models.JSONResume` (or a separate Pydantic schema) to hold the `certifications` list, and merge the LLM response when building the final résumé object.

## Modifying Existing Templates for Format Variations

If a resume format swaps the order of "location" and "summary" in the basics block, you only need to modify `prompts/templates/basics.jinja`. To make the summary optional but capture it when present:

```jinja
{% if "Summary" in text_content %}
  **IMPORTANT**: If there is any About Me or Summary section, add that to the summary field.
{% endif %}

```

Because the template is re-rendered on each run, no other code changes are required to handle format variations.

## Adding Conditional Logic and Preprocessing

Jinja supports conditionals, loops, and filters for preprocessing resume text. For example, filter out non-ASCII characters to prevent LLM confusion:

```jinja
{% set clean_text = text_content | replace('[^\\x00-\\x7F]', '') %}
{{ clean_text }}

```

This preprocessing step helps when resumes contain exotic symbols that might confuse the parsing model.

## Testing Your Template Customizations

Validate your changes using these three methods:

1. **Unit testing** – Add a test in [`tests/test_templates.py`](https://github.com/interviewstreet/hiring-agent/blob/main/tests/test_templates.py) that loads the modified template with a sample markdown snippet and asserts the JSON shape
2. **End-to-end testing** – Execute `python score.py path/to/sample_resume.pdf` and verify the console output contains the newly added fields
3. **Cache busting** – If `DEVELOPMENT_MODE` is enabled, clear the `cache/` directory to force a fresh LLM call after template changes

## Summary

- **Template files** live in `prompts/templates/` and define LLM instructions for each resume section
- **TemplateManager** ([`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py)) handles loading and rendering via `render_template()`
- **New sections** require creating a `.jinja` file, registering it in `TemplateManager._load_templates()`, and calling it from [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py)
- **Format variations** can be handled by editing existing templates without touching Python code
- **Jinja filters** enable preprocessing like ASCII character filtering before LLM ingestion
- **Testing** involves unit tests in [`tests/test_templates.py`](https://github.com/interviewstreet/hiring-agent/blob/main/tests/test_templates.py) and integration tests via [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py)

## Frequently Asked Questions

### How do I add a new section to the resume parser?

Create a new `.jinja` file in `prompts/templates/`, add the filename to the `template_files` dictionary in `TemplateManager._load_templates()`, then call `manager.render_template('new_section', text_content=block)` from [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py) when processing that section. Finally, update `models.JSONResume` to include the new field in the final schema.

### Where are the Jinja templates stored in the Hiring Agent repository?

All Jinja templates are stored in the `prompts/templates/` directory, with files like `basics.jinja`, `work.jinja`, and `education.jinja` defining the extraction logic for each resume section.

### Can I use Jinja filters to preprocess resume text before sending to the LLM?

Yes, Jinja supports filters and conditionals inside templates. For example, you can use `{% set clean_text = text_content | replace('[^\\x00-\\x7F]', '') %}` to strip non-ASCII characters, or use `{% if %}` blocks to conditionally include instructions based on content detection.

### How do I test template changes without running the full pipeline?

You can write unit tests in [`tests/test_templates.py`](https://github.com/interviewstreet/hiring-agent/blob/main/tests/test_templates.py) that instantiate `TemplateManager` and call `render_template()` with sample markdown snippets to verify the output structure. For integration testing, run `python score.py path/to/resume.pdf` after clearing the `cache/` directory if `DEVELOPMENT_MODE` is enabled.