> ## Documentation Index
> Fetch the complete documentation index at: https://docs.langdock.com/llms.txt
> Use this file to discover all available pages before exploring further.

# File Support in Custom Integrations

> Handle file inputs and outputs in custom actions and triggers.

## 1. File Input in Actions

File inputs allow users to upload files that your action can then process or send to external APIs.

### 1.1 When to Use File Inputs

* **Upload files to external tools**: Send user-uploaded documents to APIs, cloud storage, or external services like email or tickets systems.
* **Process user files**: Analyze, convert, or transform files uploaded by users

### 1.2 Adding File Input Fields

#### Single File Input

Add an input field of type "FILE" to accept one file

#### Multiple File Input

For multiple files, enable *Allow multiple files*:

### 1.3 Accessing File Data in Code

Every uploaded file is delivered as a **`FileData`** object with this exact format:

```typescript theme={null}
{
  fileName: "Invoice.pdf",
  mimeType: "application/pdf",
  size: 102400,                    // bytes
  base64: "JVBERi0xLjQK...",        // binary content, Base64-encoded
  lastModified: "2024-01-15T10:30:00Z"  // ISO date string
}
```

> **Important**: File inputs do NOT include a `text` property. The `text` shortcut only exists for file outputs.

#### Single File Access

```javascript theme={null}
// 'document' in this example is the id of the created input field
const document = data.input.document; // FileData object
const buffer = Buffer.from(document.base64, "base64");
ld.log(`Processing ${document.fileName} (${document.size} bytes)`);
```

#### Multiple Files Access

```javascript theme={null}
// 'attachments' in this example is the id of the created input field
const files = data.input.attachments; // FileData[] array
for (const file of files) {
  const buffer = Buffer.from(file.base64, "base64");
  await processFile(buffer, file.mimeType);
}
```

> **Data Structure**: When "Allow multiple files" is enabled, `data.input.fieldName` is an array. Otherwise, it's a single object.

### 1.4 Common Input Patterns

<Expandable title="Email with Attachments">
  ```javascript theme={null}
  // Gmail-style email with file attachments
  const recipient = data.input.mailRecipient;
  const subject = data.input.mailSubject;
  const body = data.input.mailBody;
  const attachments = data.input.attachments || [];

  // Build multipart email
  let email = `To: ${recipient}\r\nSubject: ${subject}\r\n`;

  if (attachments.length > 0) {
  const boundary = `boundary_${Date.now()}`;
  email += `Content-Type: multipart/mixed; boundary="${boundary}"\r\n\r\n`;
  email += `--${boundary}\r\nContent-Type: text/html\r\n\r\n${body}\r\n`;

    // Add each attachment
    for (const attachment of attachments) {
      email += `--${boundary}\r\n`;
      email += `Content-Type: ${attachment.mimeType}\r\n`;
      email += `Content-Transfer-Encoding: base64\r\n`;
      email += `Content-Disposition: attachment; filename="${attachment.fileName}"\r\n`;
      email += `\r\n${attachment.base64}\r\n`;
    }
    email += `--${boundary}--`;

  }

  ```
</Expandable>

<Expandable title="Document Upload to API">
  ```javascript theme={null}
  const document = data.input.document;

  // Validate file type
  if (!document.mimeType.startsWith('application/pdf')) {
    throw new Error('Only PDF files are supported');
  }

  try {
    const response = await ld.request({
      method: 'POST',
      url: 'https://api.example.com/documents',
      headers: {
        'Authorization': `Bearer ${data.auth.apiKey}`,
        'Content-Type': 'application/json'
      },
      body: {
        filename: document.fileName,
        content: document.base64,
        mimeType: document.mimeType
      }
    });

    return {
      success: true,
      documentId: response.json.id,
      message: `Uploaded ${document.fileName} successfully`
    };
  } catch (error) {
    return {
      success: false,
      error: `Failed to upload ${document.fileName}: ${error.message}`
    };
  }
  ```
</Expandable>

<Expandable title="Batch File Processing">
  ```javascript theme={null}
  const files = data.input.files || [];

  if (files.length === 0) {
    return { error: 'No files provided' };
  }

  const results = [];

  for (const file of files) {
    try {
      const buffer = Buffer.from(file.base64, 'base64');
      
      // Process based on file type
      let result;
      if (file.mimeType.startsWith('image/')) {
        result = await processImage(buffer);
      } else if (file.mimeType === 'application/pdf') {
        result = await processPDF(buffer);
      } else {
        throw new Error(`Unsupported file type: ${file.mimeType}`);
      }
      
      results.push({
        filename: file.fileName,
        status: 'success',
        result: result
      });
      
      ld.log(`Processed ${file.fileName} successfully`);
    } catch (error) {
      results.push({
        filename: file.fileName,
        status: 'error',
        error: error.message
      });
      
      ld.log(`Failed to process ${file.fileName}: ${error.message}`);
    }
  }

  return {
    success: true,
    processed: results.filter(r => r.status === 'success').length,
    failed: results.filter(r => r.status === 'error').length,
    results: results
  };
  ```
</Expandable>

### 1.5 Input Validation & Error Handling

```javascript theme={null}
// Validate file presence
const files = data.input.attachments;
if (!files || files.length === 0) {
  return { error: "No files provided. Please attach at least one file." };
}

// Validate file types
const allowedTypes = ["application/pdf", "image/jpeg", "image/png"];
for (const file of files) {
  if (!allowedTypes.includes(file.mimeType)) {
    return {
      error: `Unsupported file type: ${
        file.mimeType
      }. Allowed: ${allowedTypes.join(", ")}`,
    };
  }
}

// Log file metadata for debugging
ld.log(
  "Processing files:",
  files.map((f) => ({
    fileName: f.fileName,
    mimeType: f.mimeType,
    size: f.size,
  }))
);
```

## 2. File Output in Actions

File outputs allow your action to generate and return files that users can download or use in subsequent actions.

### 2.1 When to Use File Outputs

* **Generate reports**: Create PDFs, spreadsheets, or documents from data
* **Retrieve files from APIs**: Download files from external services
* **Transform files**: Convert between formats or process uploaded files
* **Export data**: Create CSV exports, backup files, or data dumps

### 2.2 File Output Format

Return files under a `files` key in your response:

```javascript theme={null}
// Single file output
return {
  files: {
    fileName: "report.pdf",
    mimeType: "application/pdf",
    base64: "JVBERi0xLjQK...", // Base64 encoded content
  },
};

// Multiple files output
return {
  files: [
    {
      fileName: "data.csv",
      mimeType: "text/csv",
      text: "Name,Email\nJohn,john@example.com", // Text shortcut for UTF-8
    },
    {
      fileName: "chart.png",
      mimeType: "image/png",
      base64: "iVBORw0KGgoAAAANSUhEUgAA...",
    },
  ],
};
```

#### File Output Properties

| Field          | Required | Notes                                      |
| -------------- | -------- | ------------------------------------------ |
| `fileName`     | ✓        | Include proper file extension              |
| `mimeType`     | ✓        | Accurate MIME type for proper handling     |
| `base64`       | ✓\*      | Base64 encoded binary content              |
| `text`         | ✓\*      | UTF-8 text content (alternative to base64) |
| `lastModified` | –        | ISO date string (defaults to current time) |

\*Provide either `base64` OR `text`, never both.

### 2.3 Common Output Patterns

<Expandable title="Generate PDF Report">
  ```javascript theme={null}
  // Generate a PDF report from data
  const reportData = await fetchReportData();

  // Create PDF content (using custom built function / endpoint)
  const pdfBuffer = await generatePDF({
    title: 'Monthly Report',
    data: reportData,
    template: 'standard'
  });

  return {
    files: {
      fileName: `monthly-report-${new Date().toISOString().slice(0,7)}.pdf`,
      mimeType: 'application/pdf',
      base64: pdfBuffer.toString('base64')
    },
    success: true,
    message: `Generated report with ${reportData.length} entries`
  };
  ```
</Expandable>

<Expandable title="CSV Data Export">
  ```javascript theme={null}
  // Export data as CSV using text shortcut
  const customers = await fetchCustomers();

  // Build CSV content
  const csvHeader = 'Name,Email,Created,Status';
  const csvRows = customers.map(c => 
    `"${c.name}","${c.email}","${c.created}","${c.status}"`
  );
  const csvContent = [csvHeader, ...csvRows].join('\n');

  return {
    files: {
      fileName: `customers-export-${new Date().toISOString().slice(0,10)}.csv`,
      mimeType: 'text/csv',
      text: csvContent  // Use text for UTF-8 content
    },
    success: true,
    exported: customers.length
  };
  ```
</Expandable>

<Expandable title="Download File from API">
  ```javascript theme={null}
  // Download file from external API
  const fileId = data.input.fileId;

  try {
    const response = await ld.request({
      method: 'GET',
      url: `https://api.example.com/files/${fileId}`,
      headers: {
        'Authorization': `Bearer ${data.auth.apiKey}`
      },
      responseType: 'stream'
    });

    // Get filename from response headers or API
    const filename = response.headers['content-disposition']
      ?.match(/filename="(.+)"/)?.[1] || `file-${fileId}`;

    return {
      files: {
        fileName: filename,
        mimeType: response.headers['content-type'] || 'application/octet-stream',
        base64: Buffer.from(response.buffer).toString('base64')
      },
      success: true,
      message: `Downloaded ${filename}`
    };
  } catch (error) {
    return {
      success: false,
      error: `Failed to download file: ${error.message}`
    };
  }
  ```
</Expandable>

<Expandable title="Process and Return Multiple Files">
  ```javascript theme={null}
  // Process input files and return modified versions
  const inputFiles = data.input.documents || [];
  const outputFiles = [];

  for (const file of inputFiles) {
    try {
      const buffer = Buffer.from(file.base64, 'base64');
      
      // Process file (e.g., compress, convert, etc.)
      const processedBuffer = await processFile(buffer, file.mimeType);
      
      outputFiles.push({
        fileName: `processed-${file.fileName}`,
        mimeType: file.mimeType,
        base64: processedBuffer.toString('base64')
      });
    } catch (error) {
      ld.log(`Failed to process ${file.fileName}: ${error.message}`);
    }
  }

  return {
    files: outputFiles,
    success: true,
    processed: outputFiles.length,
    message: `Processed ${outputFiles.length} of ${inputFiles.length} files`
  };
  ```
</Expandable>

## 3. File Output in Triggers

Triggers can also return files when detecting events that include file attachments or when generating files based on trigger data.

> **Key Difference**: Unlike actions that return objects directly, triggers must return an **array** of events. Each event needs `id`, `timestamp`, and `data` properties, with files inside the `data` object.

### 3.1 When to Use File Outputs in Triggers

* **Email attachments**: Forward attachments from incoming emails
* **File change events**: Return modified or new files from monitored systems
* **Generated notifications**: Create summary files or reports when events occur
* **API webhook files**: Process and forward files from webhook payloads

### 3.2 Trigger File Output Format

Triggers must return an array of objects with `id`, `timestamp`, and `data` properties. Files go **inside** the `data` object:

```javascript theme={null}
// Required trigger format with files
return [
  {
    id: "msg_123", // Unique identifier for this event
    timestamp: "2024-01-15T10:30:00Z", // Event timestamp
    data: {
      from: "sender@example.com",
      subject: "Invoice #12345",
      body: "Please find the invoice attached.",
      messageId: "msg_123",
      files: [
        // Files go INSIDE data object
        {
          fileName: "invoice-12345.pdf",
          mimeType: "application/pdf",
          base64: "JVBERi0xLjQK...",
        },
      ],
    },
  },
];
```

> **Important**: Unlike actions, triggers must return an array of events, and `files` must be inside the `data` object, not at the top level.

### 3.3 Common Trigger Patterns

<Expandable title="Email Trigger with Attachments">
  ```javascript theme={null}
  // Gmail trigger processing incoming emails
  const emails = await fetchNewEmails();
  const results = [];

  for (const email of emails) {
    const attachments = [];
    
    // Process email attachments if any
    if (email.attachments && email.attachments.length > 0) {
      for (const attachment of email.attachments) {
        // Download attachment content
        const content = await downloadAttachment(attachment.id);
        
        attachments.push({
          fileName: attachment.filename,
          mimeType: attachment.mimeType,
          base64: content.toString('base64')
        });
      }
    }
    
    results.push({
      id: email.id,                     // Required: unique event ID
      timestamp: email.date,            // Required: event timestamp
      data: {
        from: email.from,
        subject: email.subject,
        body: email.body,
        receivedAt: email.date,
        messageId: email.id,
        threadId: email.threadId,
        files: attachments              // Files inside data object
      }
    });
  }

  return results;  // Return array of trigger events
  ```
</Expandable>

<Expandable title="File System Monitor">
  ```javascript theme={null}
  // Monitor for new files in a directory
  const newFiles = await checkForNewFiles(data.input.directory);
  const results = [];

  for (const file of newFiles) {
    try {
      // Download file content
      const content = await downloadFile(file.path);
      
      results.push({
        id: file.id || file.path,         // Required: unique file identifier
        timestamp: file.lastModified,     // Required: event timestamp
        data: {
          filePath: file.path,
          fileName: file.name,
          size: file.size,
          modifiedAt: file.lastModified,
          event: 'file_created',
          files: [                        // Files array inside data object
            {
              fileName: file.name,
              mimeType: file.mimeType || 'application/octet-stream',
              base64: content.toString('base64')
            }
          ]
        }
      });
    } catch (error) {
      ld.log(`Failed to process file ${file.name}: ${error.message}`);
    }
  }

  return results;  // Return array of trigger events
  ```
</Expandable>

## 4. Platform Constraints & Limits

| Constraint                     | Limit         |
| :----------------------------- | :------------ |
| **Total file size per action** | **100 MB**    |
| **Maximum files per action**   | **20 files**  |
| Individual documents           | ≤ 256 MB\*    |
| Individual images              | ≤ 20 MB       |
| Individual spreadsheets        | ≤ 30 MB       |
| Individual audio files         | ≤ 200 MB\*    |
| Individual video files         | ≤ 20 MB       |
| Other file types               | ≤ 256 MB\*    |
| **Action execution timeout**   | **2 minutes** |

\*Still bounded by the 100 MB total limit.

> **Validation**: Exceeding limits throws an error **before your code executes**.

```json theme={null}
{
  "error": "Total file size (120.0 MB) exceeds the action execution limit of 100.0 MB. Please use smaller files or reduce the number of files."
}
```

### 4.1 Sandbox Library Restrictions

> Custom action and trigger code runs in a secure sandboxed environment.\
> **You cannot install or import external libraries (npm, pip, etc.) - only a limited set of built-in JavaScript/Node.js APIs are available.**\
> For advanced file processing (e.g., PDF parsing, image manipulation), use external APIs or services and call them from your code.

## 5. Best Practices

### File Input Best Practices

* **Validate file types early**: Check MIME types before processing
* **Handle missing files gracefully**: Use `|| []` for optional file arrays
* **Log file metadata**: Help debug issues without exposing content
* **Provide clear error messages**: Tell users exactly what went wrong

```javascript theme={null}
// Good validation pattern
const files = data.input.attachments || [];
if (files.length === 0) {
  return { error: "Please attach at least one file to process." };
}

const allowedTypes = ["image/jpeg", "image/png", "application/pdf"];
for (const file of files) {
  if (!allowedTypes.includes(file.mimeType)) {
    return {
      error: `File "${file.fileName}" has unsupported type ${
        file.mimeType
      }. Allowed: ${allowedTypes.join(", ")}`,
    };
  }
}
```

### File Output Best Practices

* **Use meaningful filenames**: Include dates, IDs, or descriptive names
* **Set accurate MIME types**: Enables proper file handling and previews
* **Use text shortcut for UTF-8**: More efficient than base64 for text files
* **Include processing status**: Help users understand what happened

```javascript theme={null}
// Good output pattern
return {
  files: {
    fileName: `customer-report-${new Date().toISOString().slice(0, 10)}.pdf`,
    mimeType: "application/pdf",
    base64: reportBuffer.toString("base64"),
  },
  success: true,
  recordsProcessed: customerData.length,
  message: `Generated report with ${customerData.length} customer records`,
};
```

### Performance Best Practices

* **Process files in parallel when possible**: Use `Promise.all()` for independent operations
* **Avoid loading all files into memory**: Process one at a time for large batches
* **Log progress**: Use `ld.log()` to track processing status
* **Handle errors gracefully**: Continue processing other files if one fails

```javascript theme={null}
// Good error handling pattern
const results = [];
for (const file of files) {
  try {
    const result = await processFile(file);
    results.push({ fileName: file.fileName, status: "success", result });
    ld.log(`✓ Processed ${file.fileName}`);
  } catch (error) {
    results.push({
      fileName: file.fileName,
      status: "error",
      error: error.message,
    });
    ld.log(`✗ Failed ${file.fileName}: ${error.message}`);
  }
}
```

## 6. Troubleshooting

| Issue               | Likely Cause                   | Solution                                    |
| ------------------- | ------------------------------ | ------------------------------------------- |
| "File not found"    | User didn't attach file        | Make file input required or add validation  |
| Size limit error    | Files exceed 100 MB total      | Ask for smaller files or fewer files        |
| "Invalid file type" | Wrong MIME type                | Validate `file.mimeType` in your code       |
| Empty file content  | Base64 encoding issue          | Verify `file.base64` is valid               |
| Timeout errors      | Large files or slow processing | Optimize processing or reduce file sizes    |
| Memory errors       | Too many large files           | Process files sequentially, not in parallel |

### Debug Helper

```javascript theme={null}
// Safe logging for file metadata (never logs content)
function logFileInfo(files) {
  const fileInfo = Array.isArray(files) ? files : [files];
  ld.log(
    "File info:",
    fileInfo.map((f) => ({
      fileName: f.fileName,
      mimeType: f.mimeType,
      size: f.size,
      hasBase64: !!f.base64,
      hasText: !!f.text, // Only exists for outputs
    }))
  );
}
```

***
