Invoice Generator

Create professional invoices in seconds. Live preview, PDF export, shareable links.

👤 Your Information

+

👥 Client Information

📄 Invoice Details

πŸ” Recurring Schedule

📋 Line Items

Description Qty Rate Amount
Subtotal$0.00
Tax (0%)$0.00
Total$0.00

💵 Payment Details

πŸ’Έ

Get paid faster with 0 fees β€” Open a free Cenoa account β†’

📝 Notes

📃 Drafts

πŸ”’
Unlock with Pro Upgrade
CF
Your Name

INVOICE

INV-20260320-001
From
Your Name
Bill To
Client Name
Invoice Date
Due Date
Currency
USD
DescriptionQtyRateAmount
Add line items to see them here
Subtotal$0.00
Tax (0%)$0.00
Total$0.00

πŸ” Recurring Invoices

No recurring invoice series yet. Enable recurring on an invoice above to get started.

📃 My Invoices

No saved invoices yet. Create one above and click "Save Draft".
'); w.document.close(); w.focus(); setTimeout(function(){ try { w.print(); } catch (e) {} setTimeout(function(){ try { w.close(); } catch (e) {} }, 400); }, 350); } catch (err) { console.warn('[invoice] PDF export failed, falling back to window.print()', err); try { window.print(); } catch (e) {} if (typeof showToast === 'function') showToast('If download fails, allow popups and try again.'); } finally { if(btn){ btn.disabled = false; btn.innerHTML = origHtml; } } } // Copy shareable link function copyLink(){ if(!validateForm()) return; if(!checkProGate()) return; var btn = document.querySelectorAll('.btn-action')[1]; var origText = btn.innerHTML; btn.disabled = true; btn.textContent = 'Copying...'; dataLayer.push({'event': 'share_clicked', 'share_type': 'link'}); var data = gatherInvoiceData(); var encoded = btoa(unescape(encodeURIComponent(JSON.stringify(data)))); var url = window.location.origin + window.location.pathname + '#data=' + encoded; navigator.clipboard.writeText(url).then(function(){ showToast('Link copied to clipboard!'); btn.disabled = false; btn.innerHTML = origText; }).catch(function(){ // Fallback var ta = document.createElement('textarea'); ta.value = url; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); showToast('Link copied to clipboard!'); btn.disabled = false; btn.innerHTML = origText; }); } // Send to client via mailto function sendToClient(){ if(!validateForm()) return; if(!checkProGate()) return; var clientEmail = document.getElementById('client-email').value; var invNum = document.getElementById('inv-number').value; var yourName = document.getElementById('your-name').value || 'Your Name'; var total = document.getElementById('prev-total').textContent; var dueDate = fmtDateDisplay(document.getElementById('inv-due').value); var payTerms = document.getElementById('inv-payment-terms').value; var bankName = document.getElementById('inv-bank-name').value; var bankIban = document.getElementById('inv-bank-iban').value; var bodyParts = [ 'Hi,', '', 'Please find invoice ' + invNum + ' details below.', '', 'Amount due: ' + total, 'Due date: ' + dueDate ]; if(payTerms) bodyParts.push('Payment terms: ' + payTerms); if(bankName || bankIban){ bodyParts.push(''); bodyParts.push('Payment details:'); if(bankName) bodyParts.push('Bank: ' + bankName); if(bankIban) bodyParts.push('IBAN/Account: ' + bankIban); } bodyParts.push(''); bodyParts.push('Please let me know if you have any questions.'); bodyParts.push(''); bodyParts.push('Best regards,'); bodyParts.push(yourName); var subject = encodeURIComponent('Invoice ' + invNum + ' from ' + yourName); var body = encodeURIComponent(bodyParts.join('\n')); window.open('mailto:' + encodeURIComponent(clientEmail) + '?subject=' + subject + '&body=' + body); showToast('Email client opened!'); } // Save draft function saveDraft(){ if(!validateForm()) return; var data = gatherInvoiceData(); var drafts = JSON.parse(localStorage.getItem('cortex_invoices') || '[]'); // Check if editing existing var existingIdx = drafts.findIndex(function(d){ return d.number === data.number; }); if(existingIdx >= 0){ drafts[existingIdx] = data; } else { drafts.unshift(data); // Increment sequence counter for next new invoice var seq = parseInt(localStorage.getItem('cortex_invoice_seq') || '0') + 1; localStorage.setItem('cortex_invoice_seq', String(seq)); } localStorage.setItem('cortex_invoices', JSON.stringify(drafts)); renderDrafts(); showToast('Invoice saved!'); } function gatherInvoiceData(){ return { yourName: document.getElementById('your-name').value, yourEmail: document.getElementById('your-email').value, yourAddress: document.getElementById('your-address').value, logo: logoDataURL, clientName: document.getElementById('client-name').value, clientEmail: document.getElementById('client-email').value, clientAddress: document.getElementById('client-address').value, number: document.getElementById('inv-number').value, date: document.getElementById('inv-date').value, due: document.getElementById('inv-due').value, currency: document.getElementById('inv-currency').value, tax: document.getElementById('inv-tax').value, notes: document.getElementById('inv-notes').value, paymentTerms: document.getElementById('inv-payment-terms').value, bankName: document.getElementById('inv-bank-name').value, bankHolder: document.getElementById('inv-bank-holder').value, bankIban: document.getElementById('inv-bank-iban').value, bankSwift: document.getElementById('inv-bank-swift').value, items: getLineItems(), savedAt: new Date().toISOString() }; } function loadInvoice(data){ document.getElementById('your-name').value = data.yourName || ''; document.getElementById('your-email').value = data.yourEmail || ''; document.getElementById('your-address').value = data.yourAddress || ''; document.getElementById('client-name').value = data.clientName || ''; document.getElementById('client-email').value = data.clientEmail || ''; document.getElementById('client-address').value = data.clientAddress || ''; document.getElementById('inv-number').value = data.number || ''; document.getElementById('inv-date').value = data.date || ''; document.getElementById('inv-due').value = data.due || ''; document.getElementById('inv-currency').value = data.currency || 'USD'; document.getElementById('inv-tax').value = data.tax || ''; document.getElementById('inv-notes').value = data.notes || ''; document.getElementById('inv-payment-terms').value = data.paymentTerms || ''; document.getElementById('inv-bank-name').value = data.bankName || ''; document.getElementById('inv-bank-holder').value = data.bankHolder || ''; document.getElementById('inv-bank-iban').value = data.bankIban || ''; document.getElementById('inv-bank-swift').value = data.bankSwift || ''; if(data.logo){ logoDataURL = data.logo; document.getElementById('logo-img').src = logoDataURL; document.getElementById('logo-img').style.display = 'block'; document.getElementById('logo-placeholder').style.display = 'none'; } // Rebuild line items document.getElementById('line-items').innerHTML = ''; lineItemId = 0; if(data.items && data.items.length){ data.items.forEach(function(it){ lineItemId++; var id = lineItemId; var tbody = document.getElementById('line-items'); var tr = document.createElement('tr'); tr.id = 'li-' + id; tr.innerHTML = '' + '' + '' + '
'+fmtMoney(it.amount)+'
' + ''; tbody.appendChild(tr); var descEl = tr.querySelector('textarea.li-input'); if(descEl) autoResizeDesc(descEl); }); } else { addLineItem(); } updatePreview(); window.scrollTo({top:0,behavior:'smooth'}); } function escHtml(s){ return (s||'').replace(/&/g,'&').replace(/"/g,'"').replace(//g,'>'); } function renderDrafts(){ var drafts = JSON.parse(localStorage.getItem('cortex_invoices') || '[]'); // Populate draft dropdown var sel = document.getElementById('draft-select'); var currentNum = document.getElementById('inv-number').value; sel.innerHTML = '' + drafts.map(function(d, i){ var label = (d.number || 'Untitled') + ' β€” ' + (d.clientName || 'No client'); var selected = d.number === currentNum ? ' selected' : ''; return ''; }).join(''); var grid = document.getElementById('drafts-grid'); if(!drafts.length){ grid.innerHTML = '
No saved invoices yet. Create one above and click "Save Draft".
'; return; } grid.innerHTML = drafts.map(function(d, i){ var total = 0; if(d.items) d.items.forEach(function(it){ total += it.amount || 0; }); var taxPct = parseFloat(d.tax) || 0; total = total * (1 + taxPct/100); var sym = CURRENCY_SYMBOLS[d.currency] || '$'; var dateStr = d.savedAt ? new Date(d.savedAt).toLocaleDateString() : ''; return '
' + '' + '

' + escHtml(d.number || 'Untitled') + '

' + '

' + escHtml(d.clientName || 'No client') + '

' + '
' + sym + total.toFixed(2) + '
' + '
' + dateStr + '
' + '
'; }).join(''); } function loadDraft(idx){ var drafts = JSON.parse(localStorage.getItem('cortex_invoices') || '[]'); if(drafts[idx]) loadInvoice(drafts[idx]); } function deleteDraft(idx){ var drafts = JSON.parse(localStorage.getItem('cortex_invoices') || '[]'); drafts.splice(idx, 1); localStorage.setItem('cortex_invoices', JSON.stringify(drafts)); renderDrafts(); showToast('Invoice deleted'); } function loadSelectedDraft(){ var sel = document.getElementById('draft-select'); var idx = parseInt(sel.value); if(isNaN(idx)) return; var drafts = JSON.parse(localStorage.getItem('cortex_invoices') || '[]'); if(drafts[idx]) loadInvoice(drafts[idx]); } function deleteSelectedDraft(){ var sel = document.getElementById('draft-select'); var idx = parseInt(sel.value); if(isNaN(idx)){ showToast('Select a draft first'); return; } deleteDraft(idx); newInvoice(); } function newInvoice(){ // Reset form with next sequential number var seq = parseInt(localStorage.getItem('cortex_invoice_seq') || '0') + 1; var today = new Date(); var due = new Date(today); due.setDate(due.getDate() + 30); document.getElementById('inv-number').value = 'INV-' + today.getFullYear() + '-' + String(seq).padStart(3,'0'); document.getElementById('inv-date').value = fmtDateInput(today); document.getElementById('inv-due').value = fmtDateInput(due); document.getElementById('client-name').value = ''; document.getElementById('client-email').value = ''; document.getElementById('client-address').value = ''; document.getElementById('inv-currency').value = 'USD'; document.getElementById('inv-tax').value = ''; document.getElementById('inv-notes').value = ''; document.getElementById('inv-payment-terms').value = 'Net 30'; document.getElementById('inv-bank-name').value = ''; document.getElementById('inv-bank-holder').value = ''; document.getElementById('inv-bank-iban').value = ''; document.getElementById('inv-bank-swift').value = ''; logoDataURL = null; document.getElementById('logo-img').style.display = 'none'; document.getElementById('logo-placeholder').style.display = ''; document.getElementById('line-items').innerHTML = ''; lineItemId = 0; addLineItem(); loadSavedData(); document.getElementById('draft-select').value = ''; formTouched = false; updatePreview(); window.scrollTo({top:0,behavior:'smooth'}); showToast('New invoice started'); } // Pro gating function isPro(){ return localStorage.getItem('proUser') === 'true'; } function checkProGate(){ if(isPro()) return true; var count = parseInt(localStorage.getItem('cortex_invoice_count') || '0'); if(count < 1){ localStorage.setItem('cortex_invoice_count', String(count + 1)); return true; } document.getElementById('pro-gate').classList.remove('hidden'); return false; } // Load from URL hash function loadFromHash(){ var hash = window.location.hash; if(hash && hash.indexOf('#data=') === 0){ try { var json = decodeURIComponent(escape(atob(hash.substring(6)))); var data = JSON.parse(json); loadInvoice(data); } catch(e){ /* ignore bad data */ } } } loadFromHash(); // Save/restore user info for convenience function loadSavedData(){ var saved = localStorage.getItem('cortex_invoice_user'); if(saved){ try { var d = JSON.parse(saved); if(d.name) document.getElementById('your-name').value = d.name; if(d.email) document.getElementById('your-email').value = d.email; if(d.address) document.getElementById('your-address').value = d.address; if(d.logo){ logoDataURL = d.logo; document.getElementById('logo-img').src = logoDataURL; document.getElementById('logo-img').style.display = 'block'; document.getElementById('logo-placeholder').style.display = 'none'; } } catch(e){} } } // Save user info on any change ['your-name','your-email','your-address'].forEach(function(id){ document.getElementById(id).addEventListener('change', function(){ localStorage.setItem('cortex_invoice_user', JSON.stringify({ name: document.getElementById('your-name').value, email: document.getElementById('your-email').value, address: document.getElementById('your-address').value, logo: logoDataURL })); }); }); // Validation var formTouched = false; function validateForm(){ formTouched = true; var valid = true; var emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; function check(id, errId, condition){ var el = document.getElementById(id); var err = document.getElementById(errId); if(!condition){ el.classList.add('error'); if(err) err.classList.add('visible'); valid = false; } else { el.classList.remove('error'); if(err) err.classList.remove('visible'); } } var yourName = document.getElementById('your-name').value.trim(); var yourEmail = document.getElementById('your-email').value.trim(); var clientName = document.getElementById('client-name').value.trim(); var clientEmail = document.getElementById('client-email').value.trim(); var dueDate = document.getElementById('inv-due').value; check('your-name', 'err-your-name', yourName.length > 0); check('your-email', 'err-your-email', emailRe.test(yourEmail)); check('client-name', 'err-client-name', clientName.length > 0); check('client-email', 'err-client-email', emailRe.test(clientEmail)); // Due date must be today or future var today = new Date(); today.setHours(0,0,0,0); var due = dueDate ? new Date(dueDate + 'T00:00:00') : null; check('inv-due', 'err-inv-due', due && due >= today); // At least one line item with amount > 0 var items = getLineItems(); var hasAmount = items.some(function(it){ return it.amount > 0; }); if(!hasAmount) valid = false; // Enable/disable action buttons var btns = document.querySelectorAll('.actions .btn-action'); btns.forEach(function(b){ b.disabled = !valid; }); return valid; } // ========== RECURRING INVOICES ========== function toggleRecurring(){ var checked = document.getElementById('recurring-toggle').checked; var fields = document.getElementById('recurring-fields'); if(checked){ fields.classList.add('visible'); // Default start date to invoice date var invDate = document.getElementById('inv-date').value; if(invDate && !document.getElementById('recurring-start').value){ document.getElementById('recurring-start').value = invDate; } } else { fields.classList.remove('visible'); } } function addDays(date, days){ var d = new Date(date); d.setDate(d.getDate() + days); return d; } function addFrequency(date, freq){ var d = new Date(date); if(freq === 'weekly') d.setDate(d.getDate() + 7); else if(freq === 'biweekly') d.setDate(d.getDate() + 14); else if(freq === 'monthly') d.setMonth(d.getMonth() + 1); return d; } function generateRecurringSeries(){ if(!validateForm()){ showToast('Please fill required fields first'); return; } var freq = document.getElementById('recurring-freq').value; var startStr = document.getElementById('recurring-start').value; var endStr = document.getElementById('recurring-end').value; if(!startStr){ showToast('Please set a start date'); return; } var start = new Date(startStr + 'T00:00:00'); var end = endStr ? new Date(endStr + 'T00:00:00') : null; if(end && end <= start){ showToast('End date must be after start date'); return; } // Cap at 52 invoices max (1 year weekly) if no end date var maxInvoices = 52; if(!end){ // Default: generate next 6 upcoming maxInvoices = 6; } // Gather the current invoice as template var template = gatherInvoiceData(); var baseNum = template.number || 'INV-' + new Date().getFullYear() + '-001'; // Parse the base number to extract prefix and sequence var numMatch = baseNum.match(/^(.*?)(\d+)$/); var prefix = numMatch ? numMatch[1] : baseNum + '-'; var seqStart = numMatch ? parseInt(numMatch[2]) : 1; // Calculate due offset from invoice date var invDate = new Date((template.date || fmtDateInput(new Date())) + 'T00:00:00'); var invDue = new Date((template.due || fmtDateInput(new Date())) + 'T00:00:00'); var dueOffsetDays = Math.round((invDue - invDate) / (1000*60*60*24)); if(dueOffsetDays < 0) dueOffsetDays = 30; var invoices = []; var current = new Date(start); var idx = 0; while(idx < maxInvoices){ if(end && current > end) break; var dueDate = addDays(current, dueOffsetDays); var seqNum = seqStart + idx; var invNumber = prefix + String(seqNum).padStart(3, '0'); var draft = JSON.parse(JSON.stringify(template)); draft.number = invNumber; draft.date = fmtDateInput(current); draft.due = fmtDateInput(dueDate); draft.savedAt = new Date().toISOString(); draft._recurringSeriesId = null; // will be set below invoices.push(draft); current = addFrequency(current, freq); idx++; } if(invoices.length === 0){ showToast('No invoices to generate'); return; } // Create recurring series record var seriesId = 'RS-' + Date.now(); var series = { id: seriesId, clientName: template.clientName, frequency: freq, startDate: startStr, endDate: endStr || null, templateNumber: baseNum, invoiceNumbers: invoices.map(function(inv){ return inv.number; }), createdAt: new Date().toISOString(), cancelled: false }; // Tag each invoice with series ID invoices.forEach(function(inv){ inv._recurringSeriesId = seriesId; }); // Save invoices to drafts var drafts = JSON.parse(localStorage.getItem('cortex_invoices') || '[]'); invoices.forEach(function(inv){ drafts.unshift(inv); }); localStorage.setItem('cortex_invoices', JSON.stringify(drafts)); // Update sequence counter var lastSeq = seqStart + invoices.length - 1; var currentSeq = parseInt(localStorage.getItem('cortex_invoice_seq') || '0'); if(lastSeq > currentSeq){ localStorage.setItem('cortex_invoice_seq', String(lastSeq)); } // Save recurring series var allSeries = JSON.parse(localStorage.getItem('cortex_recurring_invoices') || '[]'); allSeries.unshift(series); localStorage.setItem('cortex_recurring_invoices', JSON.stringify(allSeries)); renderDrafts(); renderRecurringSeries(); showToast(invoices.length + ' recurring invoices created for ' + (template.clientName || 'client')); // Reset recurring toggle document.getElementById('recurring-toggle').checked = false; document.getElementById('recurring-fields').classList.remove('visible'); } function cancelRecurringSeries(seriesId){ var allSeries = JSON.parse(localStorage.getItem('cortex_recurring_invoices') || '[]'); var series = allSeries.find(function(s){ return s.id === seriesId; }); if(!series) return; series.cancelled = true; localStorage.setItem('cortex_recurring_invoices', JSON.stringify(allSeries)); // Remove future invoices from drafts (keep past ones) var today = new Date(); today.setHours(0,0,0,0); var drafts = JSON.parse(localStorage.getItem('cortex_invoices') || '[]'); drafts = drafts.filter(function(d){ if(d._recurringSeriesId !== seriesId) return true; var dueDate = d.due ? new Date(d.due + 'T00:00:00') : null; return dueDate && dueDate < today; // keep past invoices }); localStorage.setItem('cortex_invoices', JSON.stringify(drafts)); renderDrafts(); renderRecurringSeries(); showToast('Series cancelled'); } function renderRecurringSeries(){ var allSeries = JSON.parse(localStorage.getItem('cortex_recurring_invoices') || '[]'); var container = document.getElementById('recurring-series-list'); if(!allSeries.length){ container.innerHTML = '
No recurring invoice series yet. Enable recurring on an invoice above to get started.
'; return; } var drafts = JSON.parse(localStorage.getItem('cortex_invoices') || '[]'); var freqLabels = {weekly:'Weekly', biweekly:'Bi-weekly', monthly:'Monthly'}; container.innerHTML = allSeries.map(function(s){ var seriesInvoices = drafts.filter(function(d){ return d._recurringSeriesId === s.id; }); var upcomingCount = seriesInvoices.filter(function(d){ var due = d.due ? new Date(d.due + 'T00:00:00') : null; return due && due >= new Date(); }).length; var statusBadge = s.cancelled ? 'Cancelled' : '' + upcomingCount + ' upcoming'; var cancelBtn = s.cancelled ? '' : ''; var chips = seriesInvoices.slice(0, 6).map(function(inv){ var total = 0; if(inv.items) inv.items.forEach(function(it){ total += it.amount || 0; }); var taxPct = parseFloat(inv.tax) || 0; total = total * (1 + taxPct/100); var sym = CURRENCY_SYMBOLS[inv.currency] || '$'; return '
' + '
' + escHtml(inv.number) + '
' + '
Due: ' + fmtDateDisplay(inv.due) + '
' + '
' + sym + total.toFixed(2) + '
' + '
'; }).join(''); if(seriesInvoices.length > 6){ chips += '
+' + (seriesInvoices.length - 6) + ' more
'; } return '
' + '
' + '
πŸ” ' + escHtml(s.clientName || 'Unnamed') + ' ' + statusBadge + '
' + cancelBtn + '
' + '
' + 'πŸ“… ' + freqLabels[s.frequency] + '' + '▢️ ' + fmtDateDisplay(s.startDate) + '' + (s.endDate ? '⏹️ ' + fmtDateDisplay(s.endDate) + '' : '♾️ Ongoing') + 'πŸ“„ ' + seriesInvoices.length + ' invoices' + '
' + '
' + chips + '
' + '
'; }).join(''); } function loadInvoiceByNumber(num){ var drafts = JSON.parse(localStorage.getItem('cortex_invoices') || '[]'); var found = drafts.find(function(d){ return d.number === num; }); if(found) loadInvoice(found); } // Initialize recurring section renderRecurringSeries(); // Toast function showToast(msg){ var t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); setTimeout(function(){ t.classList.remove('show'); }, 2500); }