← All articles

12 November 2026 · 5 min read

What a GST invoice must contain, and how to generate a PDF

General information, not tax advice. This list is a summary of what is commonly required. Rules change, and details depend on what you sell. Ask your chartered accountant (CA) to check your invoice template once, before you send the first real one.

Every sale you make as a GST-registered seller needs an invoice. Buyers with a GSTIN use it to claim input tax credit, and you use your invoices to file your returns. A wrong invoice causes problems for both sides, so it is worth getting the template right once.

What goes on a tax invoice

The details below are commonly required on an Indian tax invoice for services. Your CA can confirm the exact list for your case.

About you (the seller)

  • Your name and address.
  • Your GSTIN.

About the invoice

  • A unique serial number. It runs in order for the financial year. The rules limit it to 16 characters, using letters, numbers, a hyphen and a slash. For example AB/2026-27/0001 fits.
  • The date of issue.

About the buyer

  • Name and address.
  • GSTIN, if they are registered.
  • The place of supply: the state, with its two-digit code. This decides CGST + SGST or IGST. See how GST works on software sales.

About what was sold

  • A description of the product or service.
  • The SAC code (Services Accounting Code). It is the number that classifies a service. Ask your CA which one fits your product.
  • The value before tax, after any discount.
  • The tax split: CGST and SGST with their rates and amounts, or IGST with its rate and amount.
  • The total.
  • Whether tax is payable under reverse charge (for most simple sales to Indian buyers, it is not).
  • A signature or digital signature.

If you are not registered for GST, you cannot issue a tax invoice and you must not show GST. Issue a plain invoice that says you are not registered. Your CA can tell you the exact wording and what else it should carry.

Gapless numbers

The rule says invoice numbers must be consecutive within a financial year. A financial year in India runs from 1 April to 31 March, written like 2026-27. That has a few effects for your code:

  • Keep one counter per financial year, and start again at 1 on 1 April.
  • Do not delete invoices. If something is wrong, issue a credit note and a new invoice instead (your CA will explain how).
  • Do not hand out a number before the sale is real. A number for a payment that fails creates a gap.
  • Work out the financial year in Indian time. A payment at 1:00 am IST on 1 April is already in the new year, even if it is still 31 March in UTC.

The safest way to avoid gaps and duplicates is to let the database enforce it:

create unique index invoices_fy_seq_uidx on invoices (fy, seq);
// Inside one database transaction, when a payment is confirmed:
// 1. find the highest seq for this financial year
// 2. insert the invoice with seq + 1
// The unique index makes two requests at the same moment fail instead of sharing a number. Retry the loser.

Keep your whole number under 16 characters. A prefix, a financial year and a four-digit counter already use 15, so choose a short prefix.

Save a snapshot

When you issue an invoice, copy the seller details, buyer details and line items into the invoice record. If you later change your address or a price, old invoices must not change.

Generate a PDF in Node.js

pdf-lib is a pure JavaScript library, so it runs on Vercel without extra setup. A small invoice looks like this:

import { PDFDocument, StandardFonts, rgb } from "pdf-lib";

export async function invoicePdf(inv: {
  number: string; date: string; sellerName: string; sellerGstin: string;
  buyerName: string; placeOfSupply: string; description: string;
  taxablePaise: number; cgstPaise: number; sgstPaise: number; igstPaise: number;
}) {
  const pdf = await PDFDocument.create();
  const page = pdf.addPage([595, 842]); // A4
  const font = await pdf.embedFont(StandardFonts.Helvetica);
  const rs = (paise: number) => `Rs. ${(paise / 100).toFixed(2)}`; // see the note below about the rupee sign

  let y = 790;
  const line = (text: string, size = 11) => { page.drawText(text, { x: 50, y, size, font, color: rgb(0, 0, 0) }); y -= size + 8; };

  line("TAX INVOICE", 18);
  line(`Invoice no: ${inv.number}    Date: ${inv.date}`);
  line(`Seller: ${inv.sellerName}    GSTIN: ${inv.sellerGstin}`);
  line(`Buyer: ${inv.buyerName}    Place of supply: ${inv.placeOfSupply}`);
  line(`${inv.description}    ${rs(inv.taxablePaise)}`);
  if (inv.igstPaise) line(`IGST: ${rs(inv.igstPaise)}`);
  else { line(`CGST: ${rs(inv.cgstPaise)}`); line(`SGST: ${rs(inv.sgstPaise)}`); }
  line(`Total: ${rs(inv.taxablePaise + inv.cgstPaise + inv.sgstPaise + inv.igstPaise)}`, 13);

  return pdf.save(); // Uint8Array
}

Send it from a route handler:

return new Response(bytes, { headers: { "content-type": "application/pdf", "content-disposition": `attachment; filename="invoice.pdf"` } });

About the rupee sign. The standard PDF fonts, such as Helvetica, do not include the ₹ symbol, and pdf-lib will throw an error if you try to draw it with one. Either write "Rs." as above, or embed a font file that contains the symbol (with @pdf-lib/fontkit).

Always check permission first. Only the buyer, or an admin, should be able to download an invoice. Look up the invoice by id and by the signed-in user.

Checklist

  • All required details are on the invoice. Ask your CA to approve the template.
  • Consecutive numbers per financial year, enforced by the database.
  • Financial year worked out in Indian time.
  • A snapshot of the details saved with the invoice.
  • A PDF the buyer can download, protected by a login check.

India SaaS Kit issues numbered GST invoices with a PDF and a receipt email after each payment. See the plans, or read the launch checklist next.

Keep reading