Skip to main content

Overview

PassTru provides robust CSV import and export functionality for bulk attendee management. Import hundreds of attendees at once or export data for analysis and record-keeping.

CSV Import Process

1

Download Template

Click CSV Template to download a template file with the correct column headers for your event.
2

Fill Template

Open the CSV in Excel, Google Sheets, or any spreadsheet editor and add your attendee data.
3

Save as CSV

Export your spreadsheet as a CSV file (comma-separated values).
4

Import File

Click Import CSV and select your file. PassTru will validate and import the data.
5

Verify Import

Check the success message showing how many attendees were imported and tokens used.

Template Generation

The CSV template automatically includes columns based on your event’s configured attendee fields:
src/lib/csv.ts
export function generateTemplate(attendeeFields: string[]): string {
  return attendeeFields.join(",") + "\n";
}
Example Template: For an event with fields: Name, Email, Department, Table, Dietary
Name,Email,Department,Table,Dietary

CSV Format Requirements

File Specifications

file_type
string
required
Must be a .csv file (not .xlsx or other formats)
encoding
string
default:"UTF-8"
Text encoding (UTF-8 recommended for international characters)
delimiter
string
default:","
Comma-separated values (commas as field separators)
max_rows
number
default:"1000"
Maximum 1,000 rows per import (excluding header row)

Required Columns

Name

Attendee full name (max 200 characters)

Email

Valid email address (validated with regex)

Optional Columns

Include any attendee fields configured for your event:
  • Department
  • Organisation
  • Seat
  • Table
  • Booth
  • Dietary
  • Remarks

Data Validation

PassTru performs comprehensive validation during CSV import:

Field Validation

src/pages/event/AttendeeManagement.tsx
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const errors: string[] = [];

for (let i = 0; i < rows.length; i++) {
  const name = rows[i]["Name"]?.trim();
  const email = rows[i]["Email"]?.trim();
  
  if (!name) errors.push(`Row ${i + 1}: Name is missing`);
  else if (name.length > 200) errors.push(`Row ${i + 1}: Name exceeds 200 characters`);
  
  if (!email) errors.push(`Row ${i + 1}: Email is missing`);
  else if (!emailRegex.test(email)) errors.push(`Row ${i + 1}: Invalid email format`);
}

Custom Field Validation

Custom fields must meet naming requirements:
const validFieldName = (name: string) => /^[a-zA-Z0-9_\s-]{1,50}$/.test(name);
const invalidFields = attendeeFields.filter(
  (f) => f !== "Name" && f !== "Email" && !validFieldName(f)
);
Field names:
  • 1-50 characters
  • Letters, numbers, underscores, spaces, hyphens only
  • No special characters (@, #, $, etc.)

Value Sanitization

All imported values are sanitized to prevent injection attacks:
const sanitizeValue = (val: string) =>
  val.trim()
    .replace(/[<>]/g, "")  // Remove HTML tags
    .slice(0, 500);        // Limit length

Error Handling

If validation fails, PassTru displays up to 5 specific errors:
Row 3: Name is missing
Row 7: Invalid email format
Row 12: Name exceeds 200 characters
Fix the errors in your CSV and re-import.

Token Deduction

Each imported attendee consumes one attendee token:
src/pages/event/AttendeeManagement.tsx
const { data: deducted, error } = await supabase.rpc(
  "deduct_attendee_tokens",
  { _organization_id: organizationId, _count: records.length }
);

if (!deducted) {
  toast.error(`Insufficient attendee tokens. You need ${records.length} tokens.`);
  return;
}
Import operations are atomic. If you have insufficient tokens, no attendees will be imported and no tokens will be deducted.

Unique ID Generation

Each imported attendee receives an auto-generated unique ID:
function generateUniqueId() {
  const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  let result = "";
  for (let i = 0; i < 8; i++) {
    result += chars[Math.floor(Math.random() * chars.length)];
  }
  return result;
}
Unique IDs:
  • 8 characters long
  • Alphanumeric (A-Z, 0-9)
  • Used for check-in and QR codes
  • Example: AB12CD34

CSV Export

Export attendee data for backup or analysis:

Export Options

Includes all attendee data and check-in status:Columns:
  • Name
  • Email
  • Unique ID
  • Checked In (Yes/No)
  • Check-In Time
  • Check-In Method
  • Portal Active (Yes/No)
Click Export in the Attendee Management page.

Export Implementation

src/lib/csv.ts
export function toCSV(data: Record<string, string>[], columns: string[]): string {
  const header = columns.join(",");
  const rows = data.map((row) =>
    columns.map((col) => {
      const raw = row[col] ?? "";
      const val = sanitizeCellValue(raw);
      
      // Quote if contains comma, newline, or quote
      if (val.includes(",") || val.includes("\n") || val.includes('"')) {
        return `"${val.replace(/"/g, '""')}"`;
      }
      return val;
    }).join(",")
  );
  return [header, ...rows].join("\n");
}

CSV Injection Prevention

PassTru protects against CSV formula injection attacks:
src/lib/csv.ts
function sanitizeCellValue(val: string): string {
  // Prefix dangerous leading characters with a single quote
  if (/^[=+\-@\t\r]/.test(val)) {
    return "'" + val;
  }
  return val;
}
Security Note: Values starting with =, +, -, @ are automatically prefixed with a single quote to prevent spreadsheet formula execution.

CSV Parsing

PassTru uses a robust CSV parser that handles:
  • Quoted fields with commas
  • Escaped quotes within fields
  • Multi-line values
  • Empty rows (automatically skipped)
src/lib/csv.ts
function parseCsvLine(line: string): string[] {
  const result: string[] = [];
  let current = "";
  let inQuotes = false;
  
  for (let i = 0; i < line.length; i++) {
    const char = line[i];
    if (char === '"') {
      if (inQuotes && line[i + 1] === '"') {
        current += '"';
        i++;
      } else {
        inQuotes = !inQuotes;
      }
    } else if (char === "," && !inQuotes) {
      result.push(current);
      current = "";
    } else {
      current += char;
    }
  }
  result.push(current);
  return result;
}

Best Practices

Use Templates

Always start with the downloaded template to ensure correct column headers.

Validate Data

Review your CSV before importing to catch errors early.

Test Small

Import a small batch first to verify the format works correctly.

Keep Backups

Export data regularly for backup purposes.

Build docs developers (and LLMs) love