> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/get-started/samples/showcase-demo-invoice-generation.md).

# Invoice Generation Showcase Demo Code Sample

Generate invoices using loops, denoted by {{loop var}} and {{endloop}} clauses. Loops are used to repeat content in a document and fill in unique data for each repetition - especially when building ta

{% hint style="info" %}
**Requirements**

*These packages are required to use these features in production. Trial keys have unlimited access to all features*

<a href="/web/get-started/readme.md" class="button primary">Web SDK</a><a href="https://apryse.com/capabilities#Template" class="button primary">Package: Template</a><a href="https://showcase.apryse.com/invoice-generation" class="button primary">Live demo</a>
{% endhint %}

Easily generate invoices using loops, denoted by `{{loop var}}` and `{{endloop}}` clauses. Loops are used to repeat content in a document and fill in unique data for each repetition - especially when building tables.

**Note:** The demo focuses on generating rows with the provided data. It does *not* perform calculations on invoice items.

This demo allows you to:

* Add items to your invoice and automatically generate a new row for each entry.
* Export your invoice as a PDF, PNG, or Office file format for easy sharing or further editing.
* Customize input fields with your own data to tailor the invoice to your needs.

**Implementation steps** To add Invoice Generation capability with WebViewer:

Step 1: [Get started with WebViewer](/web/get-started/readme.md) in your preferred web stack. Step 2: Add the ES6 JavaScript sample code provided in this guide.

Once you generate your license key, it will automatically be included in your sample code below.

{% @apryse-license-key/apryse-license-key platform="WEB\_VIEWER" variant="compact" %}

{% tabs %}
{% tab title="index.js" %}

<pre class="language-js" data-line-numbers><code class="lang-js">// ES6 Compliant Syntax
// GitHub Copilot - GPT-4 Model - October 14, 2025
// File: invoice-generation/index.js

const licenseKey = '<code class="expression">visitor.claims.wvKey || "YOUR_WEBVIEWER_LICENSE_KEY"</code>';

// Global variables
const element = document.getElementById('viewer');
let documentViewer = null;
let sampleData = {};
let templateApplied = false;
const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/invoice_template.docx';

// Initialize WebViewer
WebViewer({
  path: '/lib',
  licenseKey: licenseKey,
}, element).then((instance) => {
  documentViewer = instance.Core.documentViewer;
  loadTemplateDocument(); // Load the default template document, initialize sample data and generate input fields
});

// Load default template document and initialize sample data
const loadTemplateDocument = async () => {
  // Load DOCX template
  await documentViewer.loadDocument(defaultDoc, {extension: 'docx'});
  templateApplied = false;
  // Initialize sample data
  sampleData = {
    invoice_number: '3467821',
    bill_to_name: 'Victoria Guti\u00e9rrez',
    ship_to_name: 'Mar\u00eda Rosales',
    items: [
        { description: 'Item 1', qty: '1', price: '10.00', total: '10.00' },
        { description: 'Item 2', qty: '20', price: '20.00', total: '400.00' },
        { description: 'Item 3', qty: '1', price: '0.00', total: '0.00' },
        { description: 'Item 4', qty: '1', price: '0.00', total: '0.00' },
    ],
    subtotal: '410.00',
    sales_tax_rate: '5.0%',
    sales_t: '20.50',
    total_t: '500.00',
  };
  generateInputFields(); // Generate input fields based on default sampleData values
};

const fillTemplate = async () => {
  // Update sampleData from the input field values
  // Each field is identified by its unique ID
  // The ID was originally generated from sampleData key for the fixed fields, or from (key, index, subKey) for array items
  Object.keys(sampleData).forEach(key => {
    if (Array.isArray(sampleData[key])) {
      // array field items
      sampleData[key].forEach((item, index) => {
        Object.keys(item).forEach(subKey => {
          const input = document.getElementById(`${key}_${index}_${subKey}`.toLowerCase());
          sampleData[key][index][subKey] = input.value;
        });
      });
    }
    else {
      // non-array (fixed) fields
      const input = document.getElementById(key.toLowerCase());
      sampleData[key] = input.value;
    }
  });
  // Apply JSON data to the PDF
  if(templateApplied) // reload the template document if it has already been applied
    await documentViewer.loadDocument(defaultDoc, {extension: 'docx'});
  await documentViewer.getDocument().applyTemplateValues(sampleData);
  templateApplied = true;
};

// Generate input fields based on the sampleData structure
// The left div contains the fixed text inputs for non-array fields
// The right div contains the dynamic text inputs for array fields (items).
// Each array item corresponds to a row in the table (loop in the template)
const generateInputFields = () => {
  // clear previous controls from the 2 divs
  leftDiv.innerHTML = rightDiv.innerHTML = '';  
  Object.keys(sampleData).forEach(key => {
    if (Array.isArray(sampleData[key])) {
      // array field - create multiple text inputs for each item in the array in the right div
      sampleData[key].forEach((item, index) => {
        // create a header row before the first row
        if (index === 0) {
          Object.keys(item).forEach(subKey => {
            const desc = document.createElement('input');
            desc.type = 'text';
            desc.disabled = true;
            desc.value = subKey || '';
            rightDiv.appendChild(desc);
          });
        rightDiv.appendChild(document.createElement('br'));
        }
        // create the rows that correspond to each item in the array
        Object.keys(item).forEach(subKey => {
          const input = document.createElement('input');
          // generate a unique ID for each input based on the key, index, and subKey
          input.id = `${key}_${index}_${subKey}`.toLowerCase();
          input.type = 'text';
          input.value = item[subKey] || '';
          rightDiv.appendChild(input);
        });
        // add a delete button for each row
        const deleteButton = document.createElement('button');
        deleteButton.textContent = '\u2716'; // Unicode for '✖' symbol
        deleteButton.className = 'btn-delete';
        deleteButton.onclick = () => {
          sampleData[key].splice(index, 1);
          generateInputFields(); // regenerate the input fields to reflect the deletion
        }
        rightDiv.appendChild(deleteButton);
        rightDiv.appendChild(document.createElement('br'));
      });
    }
    else {  // create text input for each field in the left div
      const desc = document.createElement('input');
      desc.disabled = true;
      desc.type = 'text';
      desc.value = key + ': ';
      leftDiv.appendChild(desc);
      const input = document.createElement('input');
      input.type = 'text';
      input.value = sampleData[key] || '';
      // use the key as the ID for easy lookup later
      input.id = key.toLowerCase();
      leftDiv.appendChild(input);
      leftDiv.appendChild(document.createElement('br'));
    }
  });
  // create a button to add a new item to the items array
  const addRowButton = document.createElement('button');
  addRowButton.textContent = 'Add Row';
  addRowButton.className = 'btn';
  addRowButton.onclick = () => {
    const randQty = Math.floor(Math.random() * 20) + 1;
    sampleData.items.push({ description: `Item ${sampleData.items.length + 1}`, qty: randQty.toString(), price: '0.00', total: '0.00' });
    generateInputFields(); // regenerate the input fields to reflect the new row
  };
  addRowButton.disabled = sampleData.items.length >= 10; // limit to 10 items
  rightDiv.appendChild(addRowButton);
}

// UI section

// Create a container for the controls
const controlsContainer = document.createElement('div');

// Create 2 divs inside the container for left and right sections
const leftDiv = document.createElement('div');
const rightDiv = document.createElement('div');
leftDiv.className = rightDiv.className = 'vertical-container'; // side-by-side divs using (display: inline-block) and (vertical-align: top)
leftDiv.style.width = "40%";
rightDiv.style.width = "60%"; // right div is wider to accommodate table rows
controlsContainer.appendChild(leftDiv);
controlsContainer.appendChild(rightDiv);

const fillInvoiceButton = document.createElement('button');
fillInvoiceButton.className = 'btn';
fillInvoiceButton.textContent = 'Fill Template';
fillInvoiceButton.onclick = async () => {
  await fillTemplate(); // generate the invoice by filling the template with data
};
controlsContainer.appendChild(fillInvoiceButton);

const resetDocumentButton = document.createElement('button');
resetDocumentButton.className = 'btn';
resetDocumentButton.textContent = '🗘 Reset Document';
resetDocumentButton.onclick = async () => {
  await loadTemplateDocument(); // reset document, data and input fields
};
controlsContainer.appendChild(resetDocumentButton);
element.insertBefore(controlsContainer, element.firstChild);

</code></pre>

{% endtab %}

{% tab title="index.css" %}
{% code title="index.css" lineNumbers="true" %}

```css
/* side-by-side divs */
.vertical-container {
    display: inline-block;
    vertical-align: top;
}

/* General Button Styles */
.btn {
    background-color: #007bff;
    margin: 0 10px;
    padding: 5px 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    transition: all 0.2s ease;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    color: white;
}

.btn:hover {
    background-color: #0056b3;
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

.btn:active {
    transform: translateY(1px);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

.btn:disabled {
    background-color: #ccc;
    cursor: not-allowed;
    box-shadow: none;
}

/* Delete Button Styles */
.btn-delete {
    background-color: red;
    padding: 0 5px;
    color: white
}

/* Responsive Design */
@media (max-width: 768px) {
    .btn {
        width: 100%;
        margin: 5px 0;
    }
}

```

{% endcode %}
{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/get-started/samples/showcase-demo-invoice-generation.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
