# How to Use IronPDF with ChatGPT Codex
Most developers meet an AI coding agent the same way: they paste a half-finished class into a chat window, get something plausible back, and spend the next twenty minutes working out which parts are real. That loop gets expensive fast when the library involved has a specific API surface, and PDF generation is that kind of library.
ChatGPT Codex is OpenAI's agentic coding system. It reads a repository, edits files, runs commands, and reports back on what it changed, and it does that from a terminal, an editor, a desktop app, or the cloud while sharing one account and one session history. IronPDF renders HTML, CSS, and JavaScript into PDF documents through an embedded Chromium engine. The two meet through a workflow you write, which makes the prompt the part you actually author. Everything below is a prompt first and C# second.
*as-heading:2(Quickstart)*
!!!--LIBRARY_NUGET_INSTALL_BLOCK--!!!
Point Codex at a clean console project and describe the output you want:
```text
In this C# console project, create Program.cs that uses IronPDF to render
the HTML string "<h1>Hello from IronPDF</h1>" to a file called hello.pdf.
Use only documented IronPDF APIs. Then run dotnet build and fix any errors.
```
Codex writes the three lines that matter: construct a `ChromePdfRenderer`, call `RenderHtmlAsPdf` on your markup, and `SaveAs` the returned document to disk. The prompt is longer than the code it produces, and that ratio holds for the rest of this guide. You spend your effort specifying, and the agent spends its effort typing and verifying.
<div class="hsg-featured-snippet">
<h3>Minimal Workflow (5 steps)</h3>
<ol>
<li><a class="js-modal-open" data-modal-id="trial-license-after-download" href="https://www.nuget.org/packages/IronPdf/">Download the C# PDF library from NuGet</a></li>
<li>Commit an <code>AGENTS.md</code> naming IronPDF as the project's PDF library</li>
<li>Start Codex from a clean working tree and prompt with the files and the expected PDF output</li>
<li>Let Codex edit files and run <code>dotnet build</code>, then review the diff before accepting</li>
<li>Open the generated PDF and check every IronPDF API name against the <a href="/object-reference/api/">API reference</a></li>
</ol>
</div>
<br class="clear" />
---
## What is ChatGPT Codex, and how does it fit into .NET PDF work?
Codex is an agent rather than a chat window. It reads files, writes files, and runs shell commands inside a scope you grant it, then shows its work. OpenAI ships it as one agent behind several front doors that share an account and a session history. The [Codex CLI documentation](https://learn.chatgpt.com/docs/codex/cli) and the [Codex IDE extension documentation](https://learn.chatgpt.com/docs/codex/ide) cover the differences between them.
| Surface | Where it runs | Reach for it when |
|---|---|---|
| CLI | Terminal | You want the agent in the same window as `dotnet build` |
| IDE extension | VS Code, JetBrains, Xcode | You want the diff and the rendered PDF side by side |
| Desktop app | macOS, Windows | You are driving a longer task out of the terminal |
| Cloud | Browser | You are delegating a task and collecting the diff later |
All four share one account and one session history, so an `AGENTS.md` committed to the repository governs every one of them.
Install the CLI with `npm i -g @openai/codex`, or `brew install --cask codex` on macOS. Standalone installers that need no Node.js runtime are available for each platform, and source and release notes live in the [Codex CLI repository](https://github.com/openai/codex).
[[i:(Keep the `@openai/` scope on that install. The unscoped `codex` package on npm is an unrelated project from 2012, and installing it is the most common Codex setup failure. It is also a neat illustration of why naming exact packages matters when an agent is doing the typing.)]]
The GPT-5.6 line runs Sol, Terra, and Luna, or Helios, Gaia, and Selene if you prefer the Greek (a naming scheme that reads as a descending scale, star to planet to satellite), and `/model` moves between them mid-session. The gap between them is mostly price and patience: Luna runs about a fifth of Sol's rate, and Sol thinks longer before it commits to anything. Most of a PDF session is the cheap kind of work, editing a template, rebuilding, looking at the output again, with the occasional stretch where a table breaks across a page for reasons no compiler will ever explain. Switch up for that part and back down afterwards. Expect the document to shift with the choice too: the same prompt on two models returns two invoices that differ in spacing and page breaks while still hitting whatever you specified.
For PDF development, that agentic behavior changes what a prompt can reasonably ask for. A chat assistant answers a question. An agent adds the NuGet reference, writes the template, runs the build, reads the compiler error, and corrects itself, which matters because the most common failure with any third-party library is a method name that no longer exists. You describe the outcome, then review a diff instead of debugging a paste.
What Codex cannot do is know IronPDF's current API with certainty. Its knowledge comes from training data plus whatever you put in front of it. OpenAI trains Codex to run commands and verify its own output, and still tells you to review the agent's work before it ships. That guidance applies twice over here: a compiler catches an invented method, but nothing catches a PDF that renders slightly wrong except opening it. The [parent guide](/ai-agents/ai/) covers the documentation to hand any assistant before it starts, and the same setup carries over to [Claude Code](/ai-agents/claude-code/) and [GitHub Copilot](/ai-agents/github-copilot/) with only the context file name changing.
Codex is the non-deterministic half of this pipeline. Ask it for the same invoice twice and two plausible templates come back. Whatever renders those templates has to behave the other way around: identical HTML in, identical PDF out, on every machine and every run. That is the case for handing rendering to a maintained library rather than letting an agent assemble one, and the gap shows up in the features nobody prototypes until the day they are needed.
- **Deterministic output:** free renderers hand off to whatever Chromium the machine happens to have, so the same HTML drifts between a laptop and a container. IronPDF pins its engine, and the same input returns the same document anywhere.
- **Encryption and permissions:** password protection and permission flags usually mean a second library bolted alongside the first. IronPDF sets both on the document it just rendered.
- **Compliance formats:** archival and accessible output are the requirements nobody prototypes and everybody eventually needs. `SaveAsPdfA` writes a PDF/A file, and `SaveAsPdfUA` writes the tagged PDF/UA a screen reader can navigate.
- **Someone patching it:** several of the most copied free options are archived, so a vulnerability found today stays open. A commercial library has a maintainer on the other end.
The cost of the free option is rarely the licence. It is the code you write around those gaps, and the audit you fail the day a document has to be tagged or encrypted.
## What makes an IronPDF prompt effective?
Specificity, in six predictable dimensions. A vague prompt produces generic code that compiles against an imaginary library; a specific prompt produces code you can review in thirty seconds.
| Component | Weak prompt | Strong prompt |
|---|---|---|
| Language and framework | "Make a PDF" | "In C# targeting .NET 9" |
| Project structure | Unstated | "Add to `Services/InvoiceService.cs`" |
| Library version | Unstated | "IronPDF version currently in the .csproj" |
| Source files | Unstated | "Read `Templates/invoice.html` first" |
| Expected output | "A nice PDF" | "A4 portrait, 20mm margins, footer with page numbers" |
| Constraints | None | "No new packages. Async only. Ask if unsure of an API." |
The last constraint deserves its own note. Telling an agent that *"I don't know"* is an acceptable answer cuts down invented APIs, because a language model's default is to produce something plausible-shaped rather than admit a gap. Give it permission to stop.
[[t:(Keep the strong column as a checklist. Six answers, one prompt, and most invented APIs never get written.)]]
A second habit that pays off across a whole project is a committed `AGENTS.md` file at the repository root. That is the file Codex reads for standing project instructions, and it reads it automatically at the start of every session, concatenating the root file first and nested files after so directory-level guidance wins. Run `/init` in a session to scaffold one. Recording "IronPDF is the PDF library; verify APIs against ironpdf.com; never add a competing PDF package" once means you stop repeating it in every prompt.
Point that same file at IronPDF's machine-readable documentation. [`llms.txt`](https://ironpdf.com/llms.txt) indexes every documentation page with a one-line description, and [`skill.md`](https://ironpdf.com/skill.md) is a drop-in instruction set covering API conventions and common patterns. One prompt writes both into the project:
```text
Create AGENTS.md at the repository root with a PDF section stating that this
project uses IronPDF for all PDF generation, that you must read
https://ironpdf.com/llms.txt before writing PDF code and use only APIs
documented there, that https://ironpdf.com/skill.md should be loaded if the
tooling supports skill files, that ChromePdfRenderer is the HTML-to-PDF entry
point, and that no competing PDF package may be added.
```
## How do you ask Codex to add IronPDF and render a first PDF?
Start by making the agent do the setup, because setup is where version mismatches originate.
```text
Add the IronPDF NuGet package to this project. Then show me the resulting
PackageReference line from the .csproj and tell me which IronPDF version
was installed. Do not change any other package references.
```
Asking for the version back is the small move that matters. It gives you a fact to include in every later prompt, and it surfaces the case where the agent quietly installed something else. Expect a one-line diff and a stated version number. To pin deliberately rather than take the latest, [What version of IronPDF should I use?](/troubleshooting/what-version-of-ironpdf-should-i-use/) covers the choice.
IronPDF watermarks every page until a licence key is applied, and the key belongs at application startup before any other IronPdf call. Have Codex place it rather than doing it by hand:
```text
Apply the IronPDF licence key at application startup, before any
other IronPdf call, reading it from configuration rather than
hard-coding it. Then confirm IronPdf.License.IsLicensed returns
true and tell me where you put the call.
```
Trial keys, and every other way to apply one from `appsettings.json` to Azure, are covered in [IronPDF License Keys](/get-started/license-keys/).
Rendering from an HTML file rather than an inline string is the realistic next step, since real templates live on disk.
```text
Create Templates/invoice.html with plain HTML and inline CSS: a header with
a company name, a line-item table (description, quantity, unit price, total),
and a right-aligned totals block. No JavaScript.
Then write Services/InvoiceRenderer.cs with a method RenderAsync that loads
that file, substitutes {{CompanyName}}, renders it with IronPDF, and returns
a byte array. Use the async IronPDF rendering method. Build and fix errors.
```
Roughly, that prompt translates into the following. Read it to confirm the agent landed on real APIs rather than to type it out yourself:
```cs
// IronPdf 2026.8.1, targeting .NET 9
using IronPdf;
public class InvoiceRenderer
{
// One renderer, reused for every invoice. Constructing a new one
// per request is the quiet performance mistake worth avoiding.
private readonly ChromePdfRenderer _renderer = new ChromePdfRenderer();
public async Task<byte[]> RenderAsync(string companyName)
{
// Read the HTML template off disk, exactly as written.
var html = await File.ReadAllTextAsync("Templates/invoice.html");
// Swap the placeholder for the real value before rendering.
html = html.Replace("{{CompanyName}}", companyName);
// Chromium renders the finished HTML. The async call leaves
// the request thread free while that happens.
var pdf = await _renderer.RenderHtmlAsPdfAsync(html);
// BinaryData returns the raw bytes, ready for an HTTP
// response or a storage upload.
return pdf.BinaryData;
}
}
```
Every member in there is real: `ChromePdfRenderer`, `RenderHtmlAsPdfAsync`, and `BinaryData` all appear in the [API reference](/object-reference/api/), which is the check to run on any block an agent hands you. When the HTML references local images or stylesheets, the renderer needs a base path to resolve them, which lives on the rendering options and is documented in the [HTML to PDF tutorial](/tutorials/html-to-pdf/).
## How do you migrate an existing PDF implementation to IronPDF?
This is where an agent earns its keep, because migration is mechanical, tedious, and easy to get 90% right. The prompt has to point at the existing code rather than describe it.
```text
Read Reports/LegacyReportBuilder.cs. It currently builds PDFs by positioning
text and drawing at coordinates with another library.
1. Summarize what the current output looks like: page size, sections, fonts.
2. Propose an HTML/CSS template that reproduces that layout.
3. Do not write any code yet. Wait for my approval on the plan.
```
Splitting analysis from implementation is the single most useful pattern in this guide. An agent that writes 400 lines before you have agreed on the approach produces 400 lines you have to read carefully. An agent that produces a five-bullet plan produces something you can correct in one sentence.
[[i:(Splitting analysis from implementation costs one extra round trip and saves reading a diff you did not agree to.)]]
After approving the plan, the follow-up is narrow:
```text
Implement the plan. Create Templates/report.html and rewrite
LegacyReportBuilder.cs to render it with IronPDF, keeping the existing
public method signature so callers don't change. Leave the old file in
Git history only; don't keep dead code. Build and report the diff.
```
Keeping the public signature stable is a constraint worth stating explicitly every time. Without it, agents tend to redesign the calling convention as a bonus, and the migration quietly grows into a refactor.
## How do you fix layout, headers, and CSS problems through prompts?
Layout problems are visual, and an agent cannot see the PDF. The prompt has to carry the description of what is wrong.
```text
The rendered invoice.pdf has two problems: the line-item table splits a row
across pages 1 and 2, and the totals block sits at the top of page 2 instead
of directly under the table.
Fix this in Templates/invoice.html using CSS page-break properties. Do not
change InvoiceRenderer.cs. Explain which CSS rule fixes which problem.
```
Naming both the symptom and the file to change keeps the fix in the template where it belongs. Because IronPDF renders through Chromium, standard print CSS applies. `page-break-inside`, `break-after`, and the `@page` rule all behave the way they do in a browser's print preview, which also gives you a fast way to check a fix without regenerating the PDF.
Headers and footers work differently. They are a renderer setting rather than template markup, so the prompt should say so:
```text
Add an HTML footer to the IronPDF renderer in InvoiceRenderer.cs showing
"Page {page} of {total-pages}", centered, 10mm max height. Use IronPDF's
HTML header/footer support on the rendering options, not markup inside
invoice.html. Link me to the documentation page you used.
```
Asking for the documentation link is a verification shortcut. If the agent cannot produce a real page for the API it just used, that is a signal worth acting on before the code reaches a build. Option names are a frequent hallucination target, so keep [Initializing RenderingOptions Correctly](/troubleshooting/rendering-options-initialization/) to hand.
## How do you debug incorrect PDF output end to end?
The prompts above each do one thing. The more interesting use is handing Codex a symptom and letting it work the whole loop: inspect, diagnose, fix, test. This is where it behaves as a development assistant rather than a snippet generator.
```text
Our generated invoices are missing the company logo. The PDF renders and
saves without error, and the logo displays correctly when I open
Templates/invoice.html in a browser.
Investigate:
1. Read InvoiceRenderer.cs and invoice.html.
2. Identify why the image resolves in a browser but not in the render.
3. Explain the cause before changing anything.
4. Then apply the minimal fix and rebuild.
```
The four-step framing matters. It asks for a diagnosis you can evaluate before any file changes, which is cheaper than reverse-engineering an edit after the fact. In this case the likely cause is relative-path resolution: the browser resolves the image against the file's own location while the renderer resolves against whatever base path it was given, and a correct answer names that mechanism rather than swapping in an absolute path.
Error messages deserve the same treatment. Pasting the full stack trace is the highest-signal thing you can give an agent.
```text
This exception is thrown from InvoiceRenderer.RenderAsync in our Linux
Docker container but not on Windows:
[PASTE FULL EXCEPTION AND STACK TRACE HERE]
Check our Dockerfile for missing native dependencies IronPDF's Chromium
engine needs on Debian-based images. Explain the cause, then propose the
Dockerfile change. Don't modify C# code unless the cause is there.
```
Container failures usually trace back to the native dependencies IronPDF's Chromium engine needs on a Debian-based image, and those belong in a Dockerfile rather than in C#. Point Codex at [How to Run IronPDF in a Docker Container](/get-started/ironpdf-docker/) and have it read the current deployment guidance before it edits anything.
## How do you turn one-off code into reusable, tested components?
Once a render works, the next prompt should generalize it, and an agent is unusually good at this, because the pattern is already sitting in the file.
```text
Refactor InvoiceRenderer.cs into a reusable PdfDocumentService registered
with dependency injection. It should take a template path and a dictionary
of placeholder values. Reuse a single ChromePdfRenderer instance rather than
constructing one per call. Add XML doc comments. Keep it under 60 lines.
```
The instance-reuse instruction is the kind of detail worth carrying in `AGENTS.md`, since constructing a renderer per request is a common and quiet performance mistake.
Tests are the natural companion, and PDF output is more testable than it first appears:
```text
Write xUnit tests for PdfDocumentService covering: output is non-empty,
output begins with the %PDF- file signature, placeholder substitution
appears in extracted text, and a missing template throws a clear exception.
Use IronPDF's text extraction for the content assertion. No golden-file
byte comparisons; rendering output is not byte-stable.
```
Ruling out byte-for-byte comparison up front prevents a whole class of flaky test. Rendering engines produce slightly different bytes across versions and platforms, so assertions belong at the level of extracted text, page count, and metadata.
Codex is also useful as a reader. Point it at an unfamiliar class and it turns [the API reference](/object-reference/api/) into something you can query instead of scroll:
```text
Read the IronPDF API reference for the rendering options type and list
every option that affects page size, then show which ones this project
already sets.
```
## Which prompt pattern fits which task?
Six sections, six patterns. The common thread is that every one of them names something specific: a file, a version, a symptom, or a stopping point.
| Task | Name this in the prompt | Pattern |
|---|---|---|
| Adding the package | The version, and that nothing else may be added | Ask for the result back |
| First render | The template path and the method to use | Specify, then build |
| Migration | The file to read, and the signature to preserve | Plan before code |
| Layout fix | The symptom and the file to change | Describe what you see |
| Container failure | The full stack trace and the platform | Diagnose before editing |
| Refactor and tests | The shape of the result and what to rule out | Constrain the output |
## What carries over to the next project?
The prompts, not the code. Every pattern above survives a change of project with only the file names swapped, which is why a committed `AGENTS.md` earns its place: it turns the constraints you worked out once into the default every later session starts from.
The acceptance test this series is measured against, run start to finish with a published result and a last-tested date, lives on the [AI coding assistants guide](/ai-agents/ai/).
---
## Troubleshooting
- [Apply a license key in IronPDF](/troubleshooting/apply-a-license-key-in-ironpdf/): if generated code produces watermarked output
- [Initializing RenderingOptions Correctly](/troubleshooting/rendering-options-initialization/): if Codex suggests option names that do not resolve
- [What version of IronPDF should I use?](/troubleshooting/what-version-of-ironpdf-should-i-use/): to pin a version in your prompts and in `AGENTS.md`
- [How to Run IronPDF in a Docker Container](/get-started/ironpdf-docker/): if a render works on Windows and fails inside a container
---
## Questions?
If you have any questions, reach out to [support@ironsoftware.com](mailto:support@ironsoftware.com)
Most developers meet an AI coding agent the same way: they paste a half-finished class into a chat window, get something plausible back, and spend the next twenty minutes working out which parts are real. That loop gets expensive fast when the library involved has a specific API surface, and PDF generation is that kind of library.
ChatGPT Codex is OpenAI's agentic coding system. It reads a repository, edits files, runs commands, and reports back on what it changed, and it does that from a terminal, an editor, a desktop app, or the cloud while sharing one account and one session history. IronPDF renders HTML, CSS, and JavaScript into PDF documents through an embedded Chromium engine. The two meet through a workflow you write, which makes the prompt the part you actually author. Everything below is a prompt first and C# second.
Quickstart
Install with NuGet
PM > Install-Package IronPdf
Install-Package IronPdf
Install IronPDF by running the command above in the NuGet Package Manager Console, or search for the package in the NuGet Package Manager.
Point Codex at a clean console project and describe the output you want:
In this C# console project, create Program.cs that uses IronPDF to renderthe HTML string "<h1>Hello from IronPDF</h1>" to a file called hello.pdf.Use only documented IronPDF APIs. Then run dotnet build and fix any errors.
In this C# console project, create Program.cs that uses IronPDF to render
the HTML string "<h1>Hello from IronPDF</h1>" to a file called hello.pdf.
Use only documented IronPDF APIs. Then run dotnet build and fix any errors.
Text
Codex writes the three lines that matter: construct a ChromePdfRenderer, call RenderHtmlAsPdf on your markup, and SaveAs the returned document to disk. The prompt is longer than the code it produces, and that ratio holds for the rest of this guide. You spend your effort specifying, and the agent spends its effort typing and verifying.
Commit an AGENTS.md naming IronPDF as the project's PDF library
Start Codex from a clean working tree and prompt with the files and the expected PDF output
Let Codex edit files and run dotnet build, then review the diff before accepting
Open the generated PDF and check every IronPDF API name against the API reference
What is ChatGPT Codex, and how does it fit into .NET PDF work?
Codex is an agent rather than a chat window. It reads files, writes files, and runs shell commands inside a scope you grant it, then shows its work. OpenAI ships it as one agent behind several front doors that share an account and a session history. The Codex CLI documentation and the Codex IDE extension documentation cover the differences between them.
Surface
Where it runs
Reach for it when
CLI
Terminal
You want the agent in the same window as dotnet build
IDE extension
VS Code, JetBrains, Xcode
You want the diff and the rendered PDF side by side
Desktop app
macOS, Windows
You are driving a longer task out of the terminal
Cloud
Browser
You are delegating a task and collecting the diff later
All four share one account and one session history, so an AGENTS.md committed to the repository governs every one of them.
Install the CLI with npm i -g @openai/codex, or brew install --cask codex on macOS. Standalone installers that need no Node.js runtime are available for each platform, and source and release notes live in the Codex CLI repository.
Please note: Keep the @openai/ scope on that install. The unscoped codex package on npm is an unrelated project from 2012, and installing it is the most common Codex setup failure. It is also a neat illustration of why naming exact packages matters when an agent is doing the typing.
The GPT-5.6 line runs Sol, Terra, and Luna, or Helios, Gaia, and Selene if you prefer the Greek (a naming scheme that reads as a descending scale, star to planet to satellite), and /model moves between them mid-session. The gap between them is mostly price and patience: Luna runs about a fifth of Sol's rate, and Sol thinks longer before it commits to anything. Most of a PDF session is the cheap kind of work, editing a template, rebuilding, looking at the output again, with the occasional stretch where a table breaks across a page for reasons no compiler will ever explain. Switch up for that part and back down afterwards. Expect the document to shift with the choice too: the same prompt on two models returns two invoices that differ in spacing and page breaks while still hitting whatever you specified.
For PDF development, that agentic behavior changes what a prompt can reasonably ask for. A chat assistant answers a question. An agent adds the NuGet reference, writes the template, runs the build, reads the compiler error, and corrects itself, which matters because the most common failure with any third-party library is a method name that no longer exists. You describe the outcome, then review a diff instead of debugging a paste.
What Codex cannot do is know IronPDF's current API with certainty. Its knowledge comes from training data plus whatever you put in front of it. OpenAI trains Codex to run commands and verify its own output, and still tells you to review the agent's work before it ships. That guidance applies twice over here: a compiler catches an invented method, but nothing catches a PDF that renders slightly wrong except opening it. The parent guide covers the documentation to hand any assistant before it starts, and the same setup carries over to Claude Code and GitHub Copilot with only the context file name changing.
Codex is the non-deterministic half of this pipeline. Ask it for the same invoice twice and two plausible templates come back. Whatever renders those templates has to behave the other way around: identical HTML in, identical PDF out, on every machine and every run. That is the case for handing rendering to a maintained library rather than letting an agent assemble one, and the gap shows up in the features nobody prototypes until the day they are needed.
Deterministic output: free renderers hand off to whatever Chromium the machine happens to have, so the same HTML drifts between a laptop and a container. IronPDF pins its engine, and the same input returns the same document anywhere.
Encryption and permissions: password protection and permission flags usually mean a second library bolted alongside the first. IronPDF sets both on the document it just rendered.
Compliance formats: archival and accessible output are the requirements nobody prototypes and everybody eventually needs. SaveAsPdfA writes a PDF/A file, and SaveAsPdfUA writes the tagged PDF/UA a screen reader can navigate.
Someone patching it: several of the most copied free options are archived, so a vulnerability found today stays open. A commercial library has a maintainer on the other end.
The cost of the free option is rarely the licence. It is the code you write around those gaps, and the audit you fail the day a document has to be tagged or encrypted.
What makes an IronPDF prompt effective?
Specificity, in six predictable dimensions. A vague prompt produces generic code that compiles against an imaginary library; a specific prompt produces code you can review in thirty seconds.
Component
Weak prompt
Strong prompt
Language and framework
"Make a PDF"
"In C# targeting .NET 9"
Project structure
Unstated
"Add to Services/InvoiceService.cs"
Library version
Unstated
"IronPDF version currently in the .csproj"
Source files
Unstated
"Read Templates/invoice.html first"
Expected output
"A nice PDF"
"A4 portrait, 20mm margins, footer with page numbers"
Constraints
None
"No new packages. Async only. Ask if unsure of an API."
The last constraint deserves its own note. Telling an agent that "I don't know" is an acceptable answer cuts down invented APIs, because a language model's default is to produce something plausible-shaped rather than admit a gap. Give it permission to stop.
Tips: Keep the strong column as a checklist. Six answers, one prompt, and most invented APIs never get written.
A second habit that pays off across a whole project is a committed AGENTS.md file at the repository root. That is the file Codex reads for standing project instructions, and it reads it automatically at the start of every session, concatenating the root file first and nested files after so directory-level guidance wins. Run /init in a session to scaffold one. Recording "IronPDF is the PDF library; verify APIs against ironpdf.com; never add a competing PDF package" once means you stop repeating it in every prompt.
Point that same file at IronPDF's machine-readable documentation. llms.txt indexes every documentation page with a one-line description, and skill.md is a drop-in instruction set covering API conventions and common patterns. One prompt writes both into the project:
Create AGENTS.md at the repository root with a PDF section stating that thisproject uses IronPDF for all PDF generation, that you must readhttps://ironpdf.com/llms.txt before writing PDF code and use only APIsdocumented there, that https://ironpdf.com/skill.md should be loaded if thetooling supports skill files, that ChromePdfRenderer is the HTML-to-PDF entrypoint, and that no competing PDF package may be added.
Create AGENTS.md at the repository root with a PDF section stating that this
project uses IronPDF for all PDF generation, that you must read
https://ironpdf.com/llms.txt before writing PDF code and use only APIs
documented there, that https://ironpdf.com/skill.md should be loaded if the
tooling supports skill files, that ChromePdfRenderer is the HTML-to-PDF entry
point, and that no competing PDF package may be added.
Text
How do you ask Codex to add IronPDF and render a first PDF?
Start by making the agent do the setup, because setup is where version mismatches originate.
Add the IronPDF NuGet package to this project. Then show me the resultingPackageReference line from the .csproj and tell me which IronPDF versionwas installed. Do not change any other package references.
Add the IronPDF NuGet package to this project. Then show me the resulting
PackageReference line from the .csproj and tell me which IronPDF version
was installed. Do not change any other package references.
Text
Asking for the version back is the small move that matters. It gives you a fact to include in every later prompt, and it surfaces the case where the agent quietly installed something else. Expect a one-line diff and a stated version number. To pin deliberately rather than take the latest, What version of IronPDF should I use? covers the choice.
IronPDF watermarks every page until a licence key is applied, and the key belongs at application startup before any other IronPdf call. Have Codex place it rather than doing it by hand:
Apply the IronPDF licence key at application startup, before anyother IronPdf call, reading it from configuration rather thanhard-coding it. Then confirm IronPdf.License.IsLicensed returnstrue and tell me where you put the call.
Apply the IronPDF licence key at application startup, before any
other IronPdf call, reading it from configuration rather than
hard-coding it. Then confirm IronPdf.License.IsLicensed returns
true and tell me where you put the call.
Text
Trial keys, and every other way to apply one from appsettings.json to Azure, are covered in IronPDF License Keys.
Rendering from an HTML file rather than an inline string is the realistic next step, since real templates live on disk.
Create Templates/invoice.html with plain HTML and inline CSS: a header witha company name, a line-item table (description, quantity, unit price, total),and a right-aligned totals block. No JavaScript.Then write Services/InvoiceRenderer.cs with a method RenderAsync that loadsthat file, substitutes {{CompanyName}}, renders it with IronPDF, and returnsa byte array. Use the async IronPDF rendering method. Build and fix errors.
Create Templates/invoice.html with plain HTML and inline CSS: a header with
a company name, a line-item table (description, quantity, unit price, total),
and a right-aligned totals block. No JavaScript.
Then write Services/InvoiceRenderer.cs with a method RenderAsync that loads
that file, substitutes {{CompanyName}}, renders it with IronPDF, and returns
a byte array. Use the async IronPDF rendering method. Build and fix errors.
Text
Roughly, that prompt translates into the following. Read it to confirm the agent landed on real APIs rather than to type it out yourself:
// IronPdf 2026.8.1, targeting .NET 9using IronPdf;public class InvoiceRenderer{ // One renderer, reused for every invoice. Constructing a new one // per request is the quiet performance mistake worth avoiding. private readonly ChromePdfRenderer _renderer = new ChromePdfRenderer(); public async Task<byte[]> RenderAsync(string companyName) { // Read the HTML template off disk, exactly as written. var html = awaitFile.ReadAllTextAsync("Templates/invoice.html"); // Swap the placeholder for the real value before rendering. html = html.Replace("{{CompanyName}}", companyName); // Chromium renders the finished HTML. The async call leaves // the request thread free while that happens. var pdf = await _renderer.RenderHtmlAsPdfAsync(html); // BinaryData returns the raw bytes, ready for an HTTP // response or a storage upload. return pdf.BinaryData; }}
// IronPdf 2026.8.1, targeting .NET 9
using IronPdf;
public class InvoiceRenderer
{
// One renderer, reused for every invoice. Constructing a new one
// per request is the quiet performance mistake worth avoiding.
private readonly ChromePdfRenderer _renderer = new ChromePdfRenderer();
public async Task<byte[]> RenderAsync(string companyName)
{
// Read the HTML template off disk, exactly as written.
var html = await File.ReadAllTextAsync("Templates/invoice.html");
// Swap the placeholder for the real value before rendering.
html = html.Replace("{{CompanyName}}", companyName);
// Chromium renders the finished HTML. The async call leaves
// the request thread free while that happens.
var pdf = await _renderer.RenderHtmlAsPdfAsync(html);
// BinaryData returns the raw bytes, ready for an HTTP
// response or a storage upload.
return pdf.BinaryData;
}
}
C#
Every member in there is real: ChromePdfRenderer, RenderHtmlAsPdfAsync, and BinaryData all appear in the API reference, which is the check to run on any block an agent hands you. When the HTML references local images or stylesheets, the renderer needs a base path to resolve them, which lives on the rendering options and is documented in the HTML to PDF tutorial.
How do you migrate an existing PDF implementation to IronPDF?
This is where an agent earns its keep, because migration is mechanical, tedious, and easy to get 90% right. The prompt has to point at the existing code rather than describe it.
Read Reports/LegacyReportBuilder.cs. It currently builds PDFs by positioningtext and drawing at coordinates with another library.1. Summarize what the current output looks like: page size, sections, fonts.2. Propose an HTML/CSS template that reproduces that layout.3. Do not write any code yet. Wait for my approval on the plan.
Read Reports/LegacyReportBuilder.cs. It currently builds PDFs by positioning
text and drawing at coordinates with another library.
1. Summarize what the current output looks like: page size, sections, fonts.
2. Propose an HTML/CSS template that reproduces that layout.
3. Do not write any code yet. Wait for my approval on the plan.
Text
Splitting analysis from implementation is the single most useful pattern in this guide. An agent that writes 400 lines before you have agreed on the approach produces 400 lines you have to read carefully. An agent that produces a five-bullet plan produces something you can correct in one sentence.
Please note: Splitting analysis from implementation costs one extra round trip and saves reading a diff you did not agree to.
After approving the plan, the follow-up is narrow:
Implement the plan. Create Templates/report.html and rewriteLegacyReportBuilder.cs to render it with IronPDF, keeping the existingpublic method signature so callers don't change. Leave the old file inGit history only; don't keep dead code. Build and report the diff.
Implement the plan. Create Templates/report.html and rewrite
LegacyReportBuilder.cs to render it with IronPDF, keeping the existing
public method signature so callers don't change. Leave the old file in
Git history only; don't keep dead code. Build and report the diff.
Text
Keeping the public signature stable is a constraint worth stating explicitly every time. Without it, agents tend to redesign the calling convention as a bonus, and the migration quietly grows into a refactor.
How do you fix layout, headers, and CSS problems through prompts?
Layout problems are visual, and an agent cannot see the PDF. The prompt has to carry the description of what is wrong.
The rendered invoice.pdf has two problems: the line-item table splits a rowacross pages 1 and 2, and the totals block sits at the top of page 2 insteadof directly under the table.Fix this in Templates/invoice.html using CSS page-break properties. Do notchange InvoiceRenderer.cs. Explain which CSS rule fixes which problem.
The rendered invoice.pdf has two problems: the line-item table splits a row
across pages 1 and 2, and the totals block sits at the top of page 2 instead
of directly under the table.
Fix this in Templates/invoice.html using CSS page-break properties. Do not
change InvoiceRenderer.cs. Explain which CSS rule fixes which problem.
Text
Naming both the symptom and the file to change keeps the fix in the template where it belongs. Because IronPDF renders through Chromium, standard print CSS applies. page-break-inside, break-after, and the @page rule all behave the way they do in a browser's print preview, which also gives you a fast way to check a fix without regenerating the PDF.
Headers and footers work differently. They are a renderer setting rather than template markup, so the prompt should say so:
Add an HTML footer to the IronPDF renderer in InvoiceRenderer.cs showing"Page {page} of {total-pages}", centered, 10mm max height. Use IronPDF'sHTML header/footer support on the rendering options, not markup insideinvoice.html. Link me to the documentation page you used.
Add an HTML footer to the IronPDF renderer in InvoiceRenderer.cs showing
"Page {page} of {total-pages}", centered, 10mm max height. Use IronPDF's
HTML header/footer support on the rendering options, not markup inside
invoice.html. Link me to the documentation page you used.
Text
Asking for the documentation link is a verification shortcut. If the agent cannot produce a real page for the API it just used, that is a signal worth acting on before the code reaches a build. Option names are a frequent hallucination target, so keep Initializing RenderingOptions Correctly to hand.
How do you debug incorrect PDF output end to end?
The prompts above each do one thing. The more interesting use is handing Codex a symptom and letting it work the whole loop: inspect, diagnose, fix, test. This is where it behaves as a development assistant rather than a snippet generator.
Our generated invoices are missing the company logo. The PDF renders andsaves without error, and the logo displays correctly when I openTemplates/invoice.html in a browser.Investigate:1. Read InvoiceRenderer.cs and invoice.html.2. Identify why the image resolves in a browser but not in the render.3. Explain the cause before changing anything.4. Then apply the minimal fix and rebuild.
Our generated invoices are missing the company logo. The PDF renders and
saves without error, and the logo displays correctly when I open
Templates/invoice.html in a browser.
Investigate:
1. Read InvoiceRenderer.cs and invoice.html.
2. Identify why the image resolves in a browser but not in the render.
3. Explain the cause before changing anything.
4. Then apply the minimal fix and rebuild.
Text
The four-step framing matters. It asks for a diagnosis you can evaluate before any file changes, which is cheaper than reverse-engineering an edit after the fact. In this case the likely cause is relative-path resolution: the browser resolves the image against the file's own location while the renderer resolves against whatever base path it was given, and a correct answer names that mechanism rather than swapping in an absolute path.
Error messages deserve the same treatment. Pasting the full stack trace is the highest-signal thing you can give an agent.
This exception is thrown from InvoiceRenderer.RenderAsync in our LinuxDocker container but not on Windows:[PASTE FULL EXCEPTION AND STACK TRACE HERE]Check our Dockerfile for missing native dependencies IronPDF's Chromiumengine needs on Debian-based images. Explain the cause, then propose theDockerfile change. Don't modify C# code unless the cause is there.
This exception is thrown from InvoiceRenderer.RenderAsync in our Linux
Docker container but not on Windows:
[PASTE FULL EXCEPTION AND STACK TRACE HERE]
Check our Dockerfile for missing native dependencies IronPDF's Chromium
engine needs on Debian-based images. Explain the cause, then propose the
Dockerfile change. Don't modify C# code unless the cause is there.
Text
Container failures usually trace back to the native dependencies IronPDF's Chromium engine needs on a Debian-based image, and those belong in a Dockerfile rather than in C#. Point Codex at How to Run IronPDF in a Docker Container and have it read the current deployment guidance before it edits anything.
How do you turn one-off code into reusable, tested components?
Once a render works, the next prompt should generalize it, and an agent is unusually good at this, because the pattern is already sitting in the file.
Refactor InvoiceRenderer.cs into a reusable PdfDocumentService registeredwith dependency injection. It should take a template path and a dictionaryof placeholder values. Reuse a single ChromePdfRenderer instance rather thanconstructing one per call. Add XML doc comments. Keep it under 60 lines.
Refactor InvoiceRenderer.cs into a reusable PdfDocumentService registered
with dependency injection. It should take a template path and a dictionary
of placeholder values. Reuse a single ChromePdfRenderer instance rather than
constructing one per call. Add XML doc comments. Keep it under 60 lines.
Text
The instance-reuse instruction is the kind of detail worth carrying in AGENTS.md, since constructing a renderer per request is a common and quiet performance mistake.
Tests are the natural companion, and PDF output is more testable than it first appears:
Write xUnit tests for PdfDocumentService covering: output is non-empty,output begins with the %PDF- file signature, placeholder substitutionappears in extracted text, and a missing template throws a clear exception.Use IronPDF's text extraction for the content assertion. No golden-filebyte comparisons; rendering output is not byte-stable.
Write xUnit tests for PdfDocumentService covering: output is non-empty,
output begins with the %PDF- file signature, placeholder substitution
appears in extracted text, and a missing template throws a clear exception.
Use IronPDF's text extraction for the content assertion. No golden-file
byte comparisons; rendering output is not byte-stable.
Text
Ruling out byte-for-byte comparison up front prevents a whole class of flaky test. Rendering engines produce slightly different bytes across versions and platforms, so assertions belong at the level of extracted text, page count, and metadata.
Codex is also useful as a reader. Point it at an unfamiliar class and it turns the API reference into something you can query instead of scroll:
Read the IronPDF API reference for the rendering options type and listevery option that affects page size, then show which ones this projectalready sets.
Read the IronPDF API reference for the rendering options type and list
every option that affects page size, then show which ones this project
already sets.
Text
Which prompt pattern fits which task?
Six sections, six patterns. The common thread is that every one of them names something specific: a file, a version, a symptom, or a stopping point.
Task
Name this in the prompt
Pattern
Adding the package
The version, and that nothing else may be added
Ask for the result back
First render
The template path and the method to use
Specify, then build
Migration
The file to read, and the signature to preserve
Plan before code
Layout fix
The symptom and the file to change
Describe what you see
Container failure
The full stack trace and the platform
Diagnose before editing
Refactor and tests
The shape of the result and what to rule out
Constrain the output
What carries over to the next project?
The prompts, not the code. Every pattern above survives a change of project with only the file names swapped, which is why a committed AGENTS.md earns its place: it turns the constraints you worked out once into the default every later session starts from.
The acceptance test this series is measured against, run start to finish with a published result and a last-tested date, lives on the AI coding assistants guide.
Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.