Blog

Luis Majano

July 09, 2026

Spread the word


Share your thoughts

Every enterprise runs on Word documents. Contracts. RFPs. Proposals. Board reports. Offer letters. HR handbooks. Compliance policies. Invoices. Statements of work. Legal memos. Today, we are incredibly proud to announce bx-word 1.0.0, a first-class, fluent, production-ready module for creating, populating, and transforming Microsoft Word documents natively in BoxLang. This is not a wrapper. This is not a converter. This is a real document builder designed for the way enterprise teams actually work.

Why This Matters for the Enterprise

Document generation is one of the quietest bottlenecks in a large organization. Sales teams wait on legal to spin up contracts. HR waits on ops to produce offer letters. Finance manually formats quarterly reports. Every one of those workflows can be automated, but only if the tooling is trustworthy, scriptable, and enterprise-grade.

Here are some use cases for bx-word:

  • Batch-generating thousands of personalized contracts from a database in seconds
  • Emitting branded, compliance-ready quarterly reports on a schedule
  • Ingesting Word documents submitted by customers, transforming them, and re-emitting them for archives
  • Producing password-protected board packets with watermarks and auto-generated tables of contents
  • Building conversion pipelines that bridge Word, HTML, Markdown, and plain text for archival, indexing, and version control

If your organization touches Word at any scale, you were probably paying a real hidden tax to keep it working. bx-word is designed to make that tax disappear.

The word() BIF: One Entry Point

Everything flows through a single BIF: word(). It returns a WordDocument builder that supports fluent method chaining, and it can create documents in several ways from a single call.

// A brand new empty document
doc = word()

// Open an existing .docx to modify
doc = word( "/path/to/existing.docx" )

// Create directly from text, Markdown, or HTML
doc = word( text = "Hello World\n\nParagraph two" )
doc = word( markdown = "## Title\n\n**bold** text" )
doc = word( html = "<h1>Title</h1><p>Content</p>" )

That is the entire mental model. From here, every method returns the builder so you can chain forever.

Your First Document in Three Lines

word( "hello.docx" )
    .addHeading( "Hello, BoxLang!", 1 )
    .addParagraph( "This is my first Word document created with BoxLang." )
    .save()

Three lines. Real .docx on disk. Opens cleanly in Word, Google Docs, or LibreOffice.

The Building Blocks

The fluent API covers the surface area you need for real business documents.

Headings and Formatted Paragraphs

word()
    .addHeading( "Q4 Financial Report", 1 )
    .addHeading( "Executive Summary", 2 )
    .addParagraph( "This quarter exceeded projections by 18%." )
    .addParagraph( "Key highlights below.", {
        bold      : true,
        size      : 12,
        color     : "2C3E50",
        alignment : "CENTER",
        spacingAfter : 240
    } )
    .save( "report.docx" )

Tables from Arrays, Queries, or Structs

Tables are one of the most common enterprise needs, and bx-word handles all three shapes with automatic header bolding.

// From a 2D array
word().addTable( [
    [ "Product", "Price", "Stock" ],
    [ "Widget A", "$10.00", "150" ],
    [ "Widget B", "$15.00", "200" ]
] )

// From a query (column names become bold headers)
products = queryExecute( "SELECT name, price, stock FROM products" )
word().addTable( products )

// From a struct with explicit headers
word().addTable( {
    headers : [ "Month", "Revenue", "Expenses" ],
    data    : [
        [ "January", "$50,000", "$30,000" ],
        [ "February", "$55,000", "$32,000" ]
    ]
} )

Nested Lists

word().addUnorderedList( [
    "Fruits",
    { text : "Vegetables", items : [ "Carrots", "Broccoli", "Spinach" ] },
    "Grains"
] )

Nesting goes as deep as you need.

Images from Anywhere

Files. Bytes. Base64. Remote URLs. All first-class.

word()
    .addImage( "/assets/logo.png", 5.0, 3.0 )
    .addImageFromUrl( "https://cdn.example.com/hero.jpg", 18.0, 5.0 )
    .addImageBase64( chartBase64, "chart.png" )
    .save( "brochure.docx" )
word()
    .margins( 1.78, 1.78, 1.78, 1.78 )
    .pageSize( "A4" )
    .orientation( "landscape" )
    .header( "ACME Corp Confidential" )
    .footer( "Q4 2026 Report" )
    .addHyperlink( "Visit BoxLang", "https://boxlang.io" )
    .save( "formatted.docx" )

Template Population at Scale

This is the feature that pays for itself in a week.

Take an existing Word template with {{placeholder}} markers designed by your legal, HR, or marketing team, then fill it programmatically from any data source.

word( "/templates/contract-template.docx" )
    .populate( {
        clientName    : "Acme Corp",
        signDate      : "2026-06-15",
        contractValue : "$150,000"
    } )
    .save( "/output/contract_acme.docx" )

Batch generation from a database becomes trivial:

employees = queryExecute( "SELECT name, department, start_date, salary FROM new_hires" )

for( row in employees ){
    word( "/templates/offer-letter.docx" )
        .populate( {
            employeeName : row.name,
            department   : row.department,
            startDate    : dateTimeFormat( row.start_date, "yyyy-mm-dd" ),
            salary       : dollarFormat( row.salary )
        } )
        .save( "/output/offers/offer_#row.name#.docx" )
}

That loop generates as many personalized, on-brand offer letters as your database can feed it. HR gets their entire hiring cohort processed in seconds instead of hours.

Beyond populate(), you also get literal replaceText() for legacy templates and setBookmark() for named regions.

Multi-Format Bridge

Documents rarely live in one format. Customers submit HTML from web forms. Editors work in Markdown. Search indexes want plain text. Archives want Word.

bx-word bridges all of them.

// Receive HTML from a web form
submittedHtml = form.content

// Convert to Word for the archive
doc = word()
    .setTitle( form.title )
    .setAuthor( form.author )
    .fromHTML( submittedHtml )

doc.save( "/archive/#form.title#.docx" )

// Also emit Markdown for version control
fileWrite( "/archive/#form.title#.md", doc.toMarkdown() )

// And plain text for the search index
searchService.index( form.title, doc.toText() )

doc.close()

One document. Four representations. Zero external services.

Enterprise-Grade Features

The features that matter when a legal or compliance team is looking over your shoulder.

Watermarks

Every page of a draft or confidential document, marked automatically.

word()
    .addHeading( "Q4 Financial Report", 1 )
    .addParagraph( "Sensitive data." )
    .addWatermark( "CONFIDENTIAL" )
    .save( "confidential-report.docx" )

Password Protection

word()
    .addHeading( "Board Packet", 1 )
    .addParagraph( "Restricted content." )
    .protect( "boardOnly2026!" )
    .save( "board-packet.docx" )

Table of Contents

Insert a TOC field that Word populates automatically from your headings.

word()
    .setTitle( "Employee Handbook 2026" )
    .addTableOfContents()
    .addPageBreak()
    .addHeading( "Company Overview", 1 )
    .addHeading( "Our Mission", 2 )
    .addHeading( "Policies", 1 )
    .save( "handbook.docx" )

Section Breaks for Mixed Layouts

Portrait for the narrative, landscape for the wide financial tables, back to portrait for the appendix.

word()
    .orientation( "portrait" )
    .addHeading( "Executive Summary", 1 )
    .addParagraph( "..." )
    .addSectionBreak()
    .orientation( "landscape" )
    .addHeading( "Financial Detail", 1 )
    .addTable( wideFinancialData )
    .addSectionBreak()
    .orientation( "portrait" )
    .addHeading( "Appendix", 1 )
    .save( "mixed-orientation.docx" )

Compliance Metadata

Document properties that show up in Word's Info panel and travel with the file.

word()
    .setTitle( "Q4 Financial Report" )
    .setAuthor( "Finance Department" )
    .setSubject( "Quarterly financial summary FY2026" )
    .setKeywords( "finance, quarterly, report, 2026" )

Fluent Helpers for Real Workflows

Two small methods that pay off enormously in production code.

tap() for Audit and Logging

Hook into the chain for logging, auditing, or side effects without breaking flow.

word()
    .addHeading( "Contract", 1 )
    .tap( ( doc ) => auditLog.info( "Contract draft initiated for client #clientId#" ) )
    .populate( contractData )
    .tap( ( doc ) => auditLog.info( "Contract populated, #len( doc.toText() )# chars" ) )
    .save( outputPath )

when() for Conditional Content

Ship different content for different tiers, roles, or regions in a single fluent expression.

word()
    .addHeading( "Sales Report", 1 )
    .addParagraph( "Standard content for all recipients." )
    .when( user.hasRole( "executive" ), ( doc ) => doc
        .addHeading( "Executive Detail", 2 )
        .addTable( executiveMetrics )
    )
    .when( region == "EU", ( doc ) => doc
        .addParagraph( "GDPR-compliant addendum applies.", { italic : true } )
    )
    .save( "report.docx" )

No more branching helper functions. The conditions live where the content lives.

A Real-World Example

Here is a complete Q4 sales report generated from a live query, with metadata, styled header, table, and a summary section:

salesData = queryExecute( "
    SELECT product_name, units_sold, unit_price,
           ( units_sold * unit_price ) as revenue
    FROM sales
    WHERE quarter = 'Q4'
    ORDER BY revenue DESC
" )

word( "q4-sales-report.docx" )
    .margins( 1.5, 1.5, 1.5, 1.5 )
    .setTitle( "Q4 2026 Sales Report" )
    .setAuthor( "Sales Department" )
    .setSubject( "Quarterly sales summary" )
    .header( "ACME Corp | Q4 2026 Sales Report" )
    .footer( "Confidential" )
    .addHeading( "Q4 2026 Sales Report", 1 )
    .addParagraph( "Generated on " & dateTimeFormat( now(), "yyyy-mm-dd HH:nn" ), {
        italic : true,
        size   : 10,
        color  : "808080"
    } )
    .addParagraph( "The following report summarizes sales performance for Q4 2026.", {
        spacingAfter : 240
    } )
    .addTable( salesData )
    .addPageBreak()
    .addHeading( "Summary", 2 )
    .addParagraph( "All products met or exceeded quarterly targets.", { bold : true } )
    .save()

That is a full board-ready document from a live database query, in about twenty lines of BoxLang.

Licensing and the 60-Day Trial

bx-word is a BoxLang+ module, available under our Plus, Plus Plus, and Starter license tiers. This is a commercial-grade capability with the support and long-term maintenance commitments enterprises need.

I want your team to prove the value before you commit. Every install includes a 60-day free trial so you can build real workflows, measure the impact, and bring hard numbers to your budget conversation. No feature gating during the trial. Full access to everything.

If you already have a BoxLang+ license, you are ready to go.

Installation

box install bx-word

Requirements:

  • BoxLang Runtime 1.14.0 or higher
  • BoxLang+ license (or the 60-day trial)

Get Started

The full documentation lives here: https://boxlang.ortusbooks.com/boxlang-+-++/modules/bx-word

Questions, feedback, or want to talk about an enterprise rollout? Reach out to us at support@ortussolutions.com or join the community on our Slack.

Add Your Comment

Recent Entries

Open at the Core. Ready for Production: BoxLang

Open at the Core. Ready for Production: BoxLang

Open source should give developers freedom not push them into a corner when an application becomes successful.

That principle shaped one of the most important segments of the Into the Box 2026 Day 1 keynote: the continued evolution of BoxLang+, BoxLang++, and the growing ecosystem of professional modules surrounding the language.

Maria Jose Herrera
Maria Jose Herrera
August 20, 2026
BoxLang in Motion: Modern Web App Development

BoxLang in Motion: Modern Web App Development

The Day 1 keynote at Into the Box 2026 was not simply a list of new BoxLang features.

It showed what happens when a language, compiler, runtime, deployment model, and developer ecosystem evolve together around one goal: helping teams build modern applications without unnecessary complexity.

Victor Campos
Victor Campos
August 18, 2026