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.
QuickstartPoint Codex at a clean console project and describe the output you want:
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.
Minimal Workflow (5 steps)
- Download the C# PDF library from NuGet
- Commit an
AGENTS.mdnaming 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.
@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.
SaveAsPdfAwrites a PDF/A file, andSaveAsPdfUAwrites 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 license. 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.
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 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.
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? covers the choice.
IronPDF watermarks every page until a license 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 license 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.
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 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:
// 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;
}
}' IronPdf 2026.8.1, targeting .NET 9
Imports 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 _renderer As New ChromePdfRenderer()
Public Async Function RenderAsync(companyName As String) As Task(Of Byte())
' Read the HTML template off disk, exactly as written.
Dim 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.
Dim pdf = Await _renderer.RenderHtmlAsPdfAsync(html)
' BinaryData returns the raw bytes, ready for an HTTP
' response or a storage upload.
Return pdf.BinaryData
End Function
End Class' IronPdf 2026.8.1, targeting .NET 9
Imports 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 _renderer As New ChromePdfRenderer()
Public Async Function RenderAsync(companyName As String) As Task(Of Byte())
' Read the HTML template off disk, exactly as written.
Dim 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.
Dim pdf = Await _renderer.RenderHtmlAsPdfAsync(html)
' BinaryData returns the raw bytes, ready for an HTTP
' response or a storage upload.
Return pdf.BinaryData
End Function
End ClassEvery 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 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.
After approving the plan, the follow-up is narrow:
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.
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:
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 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 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.
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 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 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:
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 into something you can query instead of scroll:
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.
Troubleshooting
- Apply a license key in IronPDF: if generated code produces watermarked output
- Initializing RenderingOptions Correctly: if Codex suggests option names that do not resolve
- 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: if a render works on Windows and fails inside a container
Questions?
If you have any questions, reach out to support@ironsoftware.com
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.