We had an interesting requirement in one of our projects wherein we needed to generate a Word document on the fly. Here, we have a web application with a .NET core backend running on Azure. Using Office SDK for this purpose was ruled out for this reason. We needed something free, open-source, and supported on .Net core. We zeroed in on the .NET version of the Apache POI project (simplified as NPOI).
NPOI is a .NET library that can read or write in office formats without Microsoft Office installed. It doesn’t need COM+ or interop. Yes, not only Word but other Office documents as well. For our use case, we will just focus on generating a simple Word document to demonstrate the features of NPOI.
Let’s see a quick example of how we can utilize NPOI to create a Word document.
The first step is to create a new .NET core project. Here, I’m using .NET 8. Next, add the NPOI Nuget package. And now, it all begins…
Create a new Word document in memory
XWPFDocument wordDoc = new XWPFDocument();
We can create a Header and Footer:
XWPFHeaderFooterPolicy headerFooterPolicy = wordDoc.GetHeaderFooterPolicy();
if (headerFooterPolicy == null) headerFooterPolicy = wordDoc.CreateHeaderFooterPolicy();
// create header and add a test header
XWPFHeader header = headerFooterPolicy.CreateHeader(XWPFHeaderFooterPolicy.DEFAULT);
XWPFParagraph paragraph1 = header.CreateParagraph();
paragraph1.Alignment = (ParagraphAlignment.LEFT);
XWPFRun run1 = paragraph1.CreateRun();
run1.SetText("Header Test");
// create footer
XWPFFooter footer = headerFooterPolicy.CreateFooter(XWPFHeaderFooterPolicy.DEFAULT);Now, we add a simple copyright in the footer
// add copyright
var paragraph2 = footer.CreateParagraph();
paragraph2.Alignment = (ParagraphAlignment.CENTER);
XWPFRun run2 = paragraph2.CreateRun();
run2.SetText("© 2024 Devon Software & Services Pvt. Ltd.");Next step, we can add an image to the header
// Add an image in the header const string imageName = "sampleImage.png"; XWPFRun run = AddImage(paragraph1.CreateRun(), imageName, 50, 50);
Add Image Method looks like this:
public static XWPFRun AddImage(XWPFRun run, string imageName, int width, int height)
{
var widthEmus = (int)(width * 9525 * 0.75);
var heightEmus = (int)(height * 9525 * 0.75);
using (FileStream picData = new FileStream(imageName, FileMode.Open, FileAccess.Read))
{
run.AddPicture(picData, (int)NPOI.XWPF.UserModel.PictureType.PNG, imageName, widthEmus, heightEmus);
}
return run;
}Configure page margins:
// Add Page Margins CT_SectPr sectPr = wordDoc.Document.body.sectPr; CT_PageMar pageMar = sectPr.AddPageMar(); pageMar.left = 720; //720 Twentieths of an Inch Point (Twips) = 720/20 = 36 pt = 36/72 = 0.5" pageMar.right = 720; pageMar.top = 720; //1440 Twips = 1440/20 = 72 pt = 72/72 = 1" pageMar.bottom = 720; pageMar.header = 72; //45.4 pt * 20 = 908 = 45.4 pt header from top pageMar.footer = 72; //28.4 pt * 20 = 568 = 28.4 pt footer from bottom
Create a table and set its layout to fixed
// Create a table XWPFTable exampleTable = wordDoc.CreateTable(3, 2); exampleTable.SetColumnWidth(0, 3500); exampleTable.SetColumnWidth(1, 7000); // Set Table Layout to fixed SetTableLayoutToFixed(exampleTable);
Hide table borders
// Hide table borders
exampleTable = HideBorders(exampleTable);
public static XWPFTable HideBorders(XWPFTable table)
{
// Vanish borders
table.SetBottomBorder(XWPFTable.XWPFBorderType.NONE, 0, 0, "WHITE");
table.SetTopBorder(XWPFTable.XWPFBorderType.NONE, 0, 0, "WHITE");
table.SetLeftBorder(XWPFTable.XWPFBorderType.NONE, 0, 0, "WHITE");
table.SetRightBorder(XWPFTable.XWPFBorderType.NONE, 0, 0, "WHITE");
table.SetInsideHBorder(XWPFTable.XWPFBorderType.NONE, 0, 0, "WHITE");
table.SetInsideVBorder(XWPFTable.XWPFBorderType.NONE, 0, 0, "WHITE");
return table;
}Add some content to a table cell
// Add contents to a cell
XWPFTableCell cell1 = exampleTable.GetRow(0).GetCell(0);
XWPFParagraph paragraph4 = cell1.AddParagraph();
paragraph4.Alignment = ParagraphAlignment.CENTER;
XWPFRun run4 = paragraph4.CreateRun();
run4.SetText(" Some random text...");
run4.SetColor("FF5000");
run4.FontFamily = "Josefin Sans";
run4.FontSize = 14;
run4.IsBold = true;Some Bullet examples:
A simple bullet example
public void SimpleBulletExample(XWPFDocument doc, XWPFTableCell cell)
{
XWPFNumbering numbering = doc.CreateNumbering();
string abstractNumId = numbering.AddAbstractNum();
string numId = numbering.AddNum(abstractNumId);
XWPFParagraph p0 = cell.AddParagraph();
XWPFRun r0 = p0.CreateRun();
r0.SetText(fillerText2);
r0.FontFamily = "Josefin Sans";
r0.FontSize = 10;
p0.SetNumID(numId);
XWPFParagraph p1 = cell.AddParagraph();
XWPFRun r1 = p1.CreateRun();
r1.SetText(fillerText2);
r1.FontFamily = "Josefin Sans";
r1.FontSize = 10;
p1.SetNumID(numId);
XWPFParagraph p2 = cell.AddParagraph();
XWPFRun r2 = p2.CreateRun();
r2.SetText(fillerText2);
r2.FontFamily = "Josefin Sans";
r2.FontSize = 10;
p2.SetNumID(numId);
}A multi-level bullet example
public void MultiLevelBulletExample(XWPFDocument doc)
{
XWPFNumbering numbering = doc.CreateNumbering();
var abstractNumId = numbering.AddAbstractNum();
var numId = numbering.AddNum(abstractNumId);
doc.CreateParagraph();
doc.CreateParagraph();
var p1 = doc.CreateParagraph();
var r1 = p1.CreateRun();
r1.SetText("multi level bullet");
r1.IsBold = true;
r1.FontFamily = "Courier";
r1.FontSize = 12;
p1 = doc.CreateParagraph();
r1 = p1.CreateRun();
r1.SetText("first");
p1.SetNumID(numId, "0");
p1 = doc.CreateParagraph();
r1 = p1.CreateRun();
r1.SetText("first-first");
p1.SetNumID(numId, "1");
p1 = doc.CreateParagraph();
r1 = p1.CreateRun();
r1.SetText("first-second");
p1.SetNumID(numId, "1");
p1 = doc.CreateParagraph();
r1 = p1.CreateRun();
r1.SetText("first-third");
p1.SetNumID(numId, "1");
p1 = doc.CreateParagraph();
r1 = p1.CreateRun();
r1.SetText("second");
p1.SetNumID(numId, "0");
p1 = doc.CreateParagraph();
r1 = p1.CreateRun();
r1.SetText("second-first");
p1.SetNumID(numId, "1");
p1 = doc.CreateParagraph();
r1 = p1.CreateRun();
r1.SetText("second-second");
p1.SetNumID(numId, "1");
p1 = doc.CreateParagraph();
r1 = p1.CreateRun();
r1.SetText("second-third");
p1.SetNumID(numId, "1");
p1 = doc.CreateParagraph();
r1 = p1.CreateRun();
r1.SetText("second-third-first");
p1.SetNumID(numId, "2");
p1 = doc.CreateParagraph();
r1 = p1.CreateRun();
r1.SetText("second-third-second");
p1.SetNumID(numId, "2");
p1 = doc.CreateParagraph();
r1 = p1.CreateRun();
r1.SetText("third");
p1.SetNumID(numId, "0");
}
Write the in-memory built Word document to a file
string outputFile = AppDomain.CurrentDomain.BaseDirectory + "TestWord" + ".docx"; FileStream resumeOutputFile = new FileStream(outputFile, FileMode.Create); wordDoc.Write(resumeOutputFile);
The generated Word document looks like below

These are just a few examples. We have only scratched the tip of the iceberg here. You can find the example code here https://github.com/ravindrank/NPOI-Word-Demo
Download, build, and run the project yourself to see the code in action.
Applications and Advantages
The applications and advantages are numerous.
- Being a platform-agnostic library, we can use this on any platform where the .NET core runs (Windows, Linux, MacOS).
- Open Source code and community supported – Feel free to contribute
- Totally free to use
- Support multiple office formats
- Read as well as Write to office documents
- Some very good examples
Visit https://github.com/nissl-lab/npoi to know more about NPOI
Available freely on GitHub: nissl-lab/npoi: a .NET library that can read/write Office formats without Microsoft Office installed. No COM+, no interop. (github.com)
Of course, we have a NuGet package available as well: NuGet Gallery | NPOI 2.6.2
Let me know if you are interested in learning more. Glad to hear back if this has helped you. Any feedback/suggestions are welcome!


