ワンクリックで
invoice-generator
Generate professional invoices as React web apps with auto-scaling layout and pixel-perfect PDF export via Puppeteer.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Generate professional invoices as React web apps with auto-scaling layout and pixel-perfect PDF export via Puppeteer.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Design static ad creatives for social media and display advertising campaigns.
Source and evaluate candidates with job analysis, search strategies, specific candidate profiles, and outreach templates.
Draft emails, manage calendar scheduling, prepare meeting agendas, and organize productivity
Create brand identity kits with color palettes, typography, logo concepts, and brand guidelines.
Perform competitive market analysis with feature comparisons, positioning, and strategic recommendations.
Create social media posts, newsletters, and marketing content calibrated to your voice and platform.
| name | invoice-generator |
| description | Generate professional invoices as React web apps with auto-scaling layout and pixel-perfect PDF export via Puppeteer. |
Build invoices as React web artifacts that auto-scale to fit the page, then generate pixel-perfect PDFs via Puppeteer. The web page is the single source of truth — the PDF is a screenshot of it.
Do NOT start building the invoice until you have enough information to populate real line items and details. An invoice with placeholder data is useless.
Go ahead and start building immediately. You have what you need.
You MUST ask clarifying questions before writing any code. Ask about:
Start with the essentials:
"To create your invoice, I need a few details:
- Your business name and address (the seller)
- Who you're billing — client name and address
- What you're billing for — list each item/service with the quantity and price
- Payment terms — when is it due, and how should they pay?"
Then follow up for tax details, numbering, branding, etc. based on what they share.
If the user gives vague descriptions like "consulting work," push for specifics: "Can you break that down into specific deliverables? e.g., 'Website redesign — 3 revision rounds' at $5,000. Specific line items look more professional and reduce client pushback."
If you had to guess or infer any details — tax rates, payment terms, invoice numbers, dates — you MUST tell the user what you assumed. After presenting the first draft, explicitly list anything you weren't sure about. For example:
"A few things I assumed — let me know if any need adjusting:
- I used invoice number INV-2026-0001 — do you have an existing numbering scheme?
- I set the tax rate to 0% since you didn't mention taxes — should I add sales tax or VAT?
- I set payment terms to Net 30 with a due date of April 13 — is that right?"
Do NOT silently present fabricated details as fact. Getting invoice details wrong can cause real payment and legal issues.
Follow this exact order so the user gets fast results:
The user cares most about seeing their invoice quickly. Get the PDF into their hands first. The web app is a bonus for previewing and iterating.
artifacts/<client>-invoice/
client/src/pages/Invoice.tsx # Invoice data + component with auto-scale
client/src/index.css # Print-ready styles (A4 or Letter)
scripts/src/generate-invoice.ts # Puppeteer PDF generator
output/ # Generated PDF
transform: scale() to shrink everything to fit — content is never clippedpage.pdf() with exact page dimensionsEU (VAT Directive 2006/112/EC, Article 226) — legally mandatory:
"Reverse charge — VAT to be accounted for by the recipient (Art. 196, Directive 2006/112/EC)". Include the customer's VAT number (validate via VIES).US — no federal invoice law. Sequential numbering is best practice (IRS wants unique IDs for audit trail) but not legally required. Sales tax rules vary by state; many services are untaxed.
Numbering scheme: {PREFIX}-{YYYY}-{SEQ:04d} e.g. INV-2026-0042. Prefix can distinguish clients or entities. Never reuse or skip; if you void one, keep the voided record.
| Term | Meaning | Typical use |
|---|---|---|
| Due on receipt | Pay immediately | Small amounts, new clients |
| Net 30 | Due 30 days from invoice date | Standard B2B |
| Net 60 / Net 90 | 60/90 days | Large enterprise (push back on this) |
| 2/10 Net 30 | 2% discount if paid in 10 days, else full in 30 | Incentivize fast payment |
| EOM | Due end of month | |
| 1.5% monthly late fee | Compounds on overdue balance | Check local usury caps — often ~18% APR max |
The invoice component should include:
INVOICE_DATA object at the top holding all content — line items, client info, seller info, tax rates, payment terms, datesuseEffect with scaleToFit() that measures scrollHeight vs available height and applies transform: scale() if content overflowsdocument.fonts.ready.then(scaleToFit) ensures scaling runs after web fonts loadsetTimeout calls as fallback for slow font/layoutLayout structure:
const INVOICE_DATA = {
invoiceNumber: "INV-2026-0042",
issueDate: "2026-03-14",
dueDate: "2026-04-13",
seller: {
name: "Your Company",
address: "123 Main St, City, ST 12345",
email: "billing@company.com",
vatNumber: "", // EU only
},
client: {
name: "Client Corp",
address: "456 Oak Ave, City, ST 67890",
vatNumber: "", // EU B2B only
},
items: [
{ description: "Homepage redesign — 3 rounds of revisions", qty: 1, rate: 5000 },
{ description: "Logo design and brand kit", qty: 1, rate: 2500 },
],
taxRate: 0, // 0.20 for 20% VAT, etc.
currency: "USD",
paymentTerms: "Net 30",
paymentInstructions: "Bank: ... | ACH Routing: ... | Account: ...",
notes: "",
lateFeePolicy: "1.5% monthly interest on overdue balances",
};
Component structure:
<header> Logo + "INVOICE" title + invoice number/dates </header>
<section class="parties"> Two columns: From (seller) / Bill To (client) </section>
<table> Description | Qty | Rate | Amount — with calculated line totals </table>
<section class="totals"> Subtotal / Tax (X%) / Total Due — right-aligned, bold total </section>
<footer> Payment instructions · Bank/IBAN/SWIFT or payment link · Late fee policy </footer>
The component should compute subtotal, tax, and total from the items array. Display the due date as an actual date ("Due: April 13, 2026") not just the payment term.
Key patterns:
@page { size: letter; margin: 0; } for print (swap letter for A4 for non-US).invoice-page is exactly 8.5in x 11in with overflow: hidden@media print hides Replit banners/iframesmax-height: 48px at top-leftimport puppeteer from "puppeteer-core";
import * as fs from "fs";
import * as path from "path";
const outDir = path.resolve(import.meta.dirname, "..", "..", "output");
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
// UPDATE THESE for each invoice:
// 1. Find Chromium path: ls /nix/store/*chromium*/bin/chromium
// 2. Set INVOICE_URL to the web artifact's URL
// 3. Set OUTPUT_NAME for the filename
const CHROMIUM_PATH = "/nix/store/FIND_YOUR_PATH/bin/chromium";
const INVOICE_URL = `https://${process.env.REPLIT_DEV_DOMAIN}/ARTIFACT-SLUG`;
const OUTPUT_NAME = "INV-2026-0042";
async function generatePDF() {
const browser = await puppeteer.launch({
executablePath: CHROMIUM_PATH,
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"],
});
const page = await browser.newPage();
await page.setViewport({ width: 816, height: 1056 });
await page.goto(INVOICE_URL, { waitUntil: "networkidle0", timeout: 30000 });
await page.waitForSelector(".invoice-page", { timeout: 10000 });
await new Promise((r) => setTimeout(r, 2000));
// Remove Replit dev UI and auto-scale content to fit one page
await page.evaluate(() => {
document.querySelectorAll('[class*="replit"], [class*="banner"], [id*="replit"], [id*="banner"], iframe').forEach(
(el) => (el as HTMLElement).remove()
);
document.body.style.background = "#fff";
document.body.style.margin = "0";
document.body.style.padding = "0";
const pg = document.querySelector(".invoice-page") as HTMLElement;
if (!pg) return;
pg.style.margin = "0";
pg.style.boxShadow = "none";
pg.style.width = "8.5in";
pg.style.height = "11in";
pg.style.overflow = "hidden";
pg.style.position = "relative";
const inner = pg.children[0] as HTMLElement;
if (!inner) return;
// Reset any existing transform to measure true height
inner.style.transform = "none";
inner.style.width = "100%";
const padTop = parseFloat(getComputedStyle(pg).paddingTop);
const padBot = parseFloat(getComputedStyle(pg).paddingBottom);
const availH = pg.clientHeight - padTop - padBot;
const contentH = inner.scrollHeight;
// Scale down if content overflows available space
if (contentH > availH) {
const scale = availH / contentH;
inner.style.transformOrigin = "top left";
inner.style.transform = `scale(${scale})`;
inner.style.width = `${100 / scale}%`;
}
});
await new Promise((r) => setTimeout(r, 300));
const pdfPath = path.join(outDir, `${OUTPUT_NAME}.pdf`);
await page.pdf({
path: pdfPath,
width: "8.5in",
height: "11in",
printBackground: true,
margin: { top: "0", bottom: "0", left: "0", right: "0" },
preferCSSPageSize: false,
});
await browser.close();
console.log("PDF saved:", pdfPath);
return pdfPath;
}
await generatePDF();
console.log("Done!");
The most common issue is content getting clipped at the bottom. Use a two-layer defense:
React side (for browser preview): The scaleToFit useEffect measures scrollHeight vs clientHeight - padding and applies CSS transform: scale(). This runs on mount, resize, font load, and via timeouts.
Puppeteer side (for PDF): The page.evaluate() block does the exact same measurement and scaling. This is necessary because Puppeteer's PDF renderer may not execute React effects reliably.
Both use the same formula:
availH = container.clientHeight - paddingTop - paddingBottom
scale = availH / content.scrollHeight
content.style.transform = `scale(${scale})`
content.style.width = `${100 / scale}%` // compensate for horizontal shrink
When the user requests changes (line items, amounts, branding, payment terms, etc.):
Invoice.tsxNever deliver an updated invoice without re-generating the PDF. Every iteration cycle ends with the user seeing a fresh PDF.