Skip to main content

Overview

The template system allows you to save frequently used invoice configurations as reusable templates. This is ideal for recurring services, monthly retainers, or any invoice type you create regularly. Templates store customer information, line items, notes, and tax settings without invoice numbers or dates.

What are templates?

Templates are pre-configured invoice structures that include:
  • Customer information (name, email, address)
  • Line items with descriptions, quantities, and rates
  • Tax rate percentage
  • Currency
  • Notes or payment terms
Templates do not include:
  • Invoice numbers (generated when you create an invoice from the template)
  • Invoice dates (set to current date)
  • Due dates (calculated from current date)
  • Status (all new invoices start as “draft”)

Template data structure

interface InvoiceTemplate {
  id: string;
  name: string;
  description?: string;
  customer: Partial<Customer>;
  items: Partial<InvoiceItem>[];
  notes?: string;
  taxRate?: number;
  currency: string;
  createdAt: string;
}
Note that customer and items use Partial<> types, meaning fields can be incomplete or omitted entirely, giving you flexibility in how much information to pre-fill.

Creating templates

Templates are created from the invoice creation form:

Step 1: Fill out the invoice form

Navigate to /invoices/create and complete the invoice form with:
  • Customer details
  • Line items
  • Tax rate
  • Currency
  • Any notes or terms

Step 2: Click “Save as Template”

Instead of creating the invoice, click the Save as Template button. This opens a modal dialog.

Step 3: Name your template

Provide:
  • Template name (required): A descriptive name like “Monthly Consulting Invoice” or “Website Maintenance”
  • Description (optional): Additional notes about when to use this template

Step 4: Save

Click Save Template to store the template. The modal closes and you can continue editing the invoice or discard it. The template save implementation from app/components/InvoiceForm.tsx:267:
const handleSaveAsTemplate = async () => {
  if (!templateName.trim()) {
    alert("Please enter a template name");
    return;
  }

  try {
    setIsSavingTemplate(true);

    const template: InvoiceTemplate = {
      id: String(Date.now()),
      name: templateName,
      description: templateDescription,
      customer: {
        name: formData.customerName,
        email: formData.customerEmail,
        address: formData.customerAddress,
        city: formData.customerCity,
        state: formData.customerState,
        zipCode: formData.customerZipCode,
      },
      items,
      notes: formData.notes,
      taxRate: Number(formData.taxRate),
      currency: formData.currency,
      createdAt: new Date().toISOString(),
    };

    await createTemplate(template);
    alert("Template saved successfully!");
    setShowSaveTemplateModal(false);
    setTemplateName("");
    setTemplateDescription("");
  } catch (error) {
    alert("Failed to save template. Please try again.");
    console.error(error);
  } finally {
    setIsSavingTemplate(false);
  }
};
You can save a template without filling in all customer details. This is useful for templates that apply to multiple customers or when you want to pre-fill only the line items.

Viewing templates

Navigate to /templates to see all saved templates in a grid layout. Each template card displays:
  • Template name (heading)
  • Description (if provided)
  • Customer name or “Template” if no customer set
  • Number of line items
  • Creation date
  • Use Template button
  • Delete button (trash icon)

Empty state

When no templates exist:
No templates yet. Create an invoice and save it as a template to reuse it.
With a Create Invoice button to get started.

Using templates

To create an invoice from a template:
  1. Go to /templates
  2. Click Use Template on any template card
  3. You’re redirected to /invoices/create
  4. The form auto-fills with template data
  5. Modify any fields as needed
  6. Click Create Invoice
The template loading mechanism uses sessionStorage for temporary data transfer between pages:
const handleUseTemplate = (template: InvoiceTemplate) => {
  // Store template in session for the create form to pick up
  sessionStorage.setItem("selectedTemplate", JSON.stringify(template));
  // Redirect to create invoice page
  window.location.href = "/invoices/create";
};
The invoice form checks for this data on mount (from app/components/InvoiceForm.tsx:165):
useEffect(() => {
  if (typeof window === "undefined") return;

  const selectedTemplate = sessionStorage.getItem("selectedTemplate");
  if (selectedTemplate && !initialData) {
    try {
      const template: InvoiceTemplate = JSON.parse(selectedTemplate);

      // Populate form with template data
      setFormData((prev) => ({
        ...prev,
        customerName: template.customer.name || "",
        customerEmail: template.customer.email || "",
        customerAddress: template.customer.address || "",
        customerCity: template.customer.city || "",
        customerState: template.customer.state || "",
        customerZipCode: template.customer.zipCode || "",
        customerCountry: template.customer.country || "",
        currency: template.currency,
        notes: template.notes || "",
        taxRate: template.taxRate || 0,
      }));

      // Populate items
      if (template.items && template.items.length > 0) {
        const populatedItems = template.items.map((item, index) => ({
          id: String(index + 1),
          description: item.description || "",
          quantity: item.quantity || 1,
          rate: item.rate || 0,
        }));
        setItems(populatedItems);
      }

      // Clear sessionStorage
      sessionStorage.removeItem("selectedTemplate");
    } catch (err) {
      console.error("Failed to load template:", err);
    }
  }
}, [initialData]);
The template data is cleared from sessionStorage after loading to prevent it from applying to future invoice creations.

Deleting templates

To delete a template:
  1. Click the Delete button (trash icon) on the template card
  2. Confirm the deletion in the dialog: “Are you sure you want to delete this template?”
  3. The template is permanently removed
Deleting a template cannot be undone. However, it does not affect any invoices previously created from that template.

Common use cases

Monthly retainer invoices

Create a template with:
  • Customer: Your client’s information
  • Items: “Monthly Retainer - [Service]” with fixed quantity and rate
  • Notes: Standard payment terms
  • Tax rate: Standard rate for your region

Hourly consulting

Create a template with:
  • Customer: Leave blank or use a placeholder
  • Items: “Consulting Services” with quantity=1, rate=your hourly rate
  • Notes: Include your billing terms
Update the quantity to match hours worked when creating each invoice.

Product bundles

Create templates for common product combinations:
  • Items: Multiple line items for each product in the bundle
  • Rates: Pre-filled with current pricing
  • Customer: Leave blank to use with any customer

Data persistence

Templates are stored in IndexedDB using these functions from app/lib/storage.ts:76:
export async function getTemplates(): Promise<InvoiceTemplate[]>
export async function getTemplateById(id: string): Promise<InvoiceTemplate | null>
export async function createTemplate(template: InvoiceTemplate): Promise<InvoiceTemplate>
export async function updateTemplate(id: string, updates: Partial<InvoiceTemplate>): Promise<InvoiceTemplate | null>
export async function deleteTemplate(id: string): Promise<boolean>
Template editing is not currently available in the UI. To modify a template, create an invoice from it, make changes, and save as a new template with the same name (delete the old one afterward).

Build docs developers (and LLMs) love