npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

personalia-node

v1.0.6

Published

A TypeScript/Node.js client library for the Personalia API to generate personalized PDFs, PNGs, and JPGs

Readme

A TypeScript/Node.js client library for the Personalia API, which allows you to generate personalized or versioned JPG/PNG images and PDF output from your Adobe InDesign templates.

About Personalia

Personalia is a powerful platform for creating personalized content at scale. With Personalia, you can:

  • Generate personalized PDFs, PNGs, and JPGs from Adobe InDesign templates
  • Create dynamic content for marketing campaigns, certificates, badges, and more
  • Automate content creation workflows with a robust API
  • Integrate with your existing systems and processes

Visit personalia.io to learn more. Personalia is currently in Beta Testing and is available for use via a Beta account - available at https://www.personalia.io/personalia-beta-signup/

For more information email [email protected]

Installation

npm install personalia-node

Features

  • Template Field Discovery: Retrieve template fields to understand what data is required
  • Content Generation: Create personalized PDFs, PNGs, and JPGs from templates
  • Automatic Polling: Built-in polling mechanism to wait for content generation completion
  • Content URL Creation: Generate on-demand URLs that create content when accessed
  • Robust Error Handling: Comprehensive error handling with detailed error messages
  • TypeScript Support: Full TypeScript type definitions for improved developer experience

Quick Start

This example shows a basic workflow with error handling:

import { PersonaliaClient } from 'personalia-node';
import * as fs from 'fs';
import * as path from 'path';

// Initialize with your API key
const client = new PersonaliaClient('YOUR_API_KEY');

async function generatePDF() {
  try {
    // 1. Create content request
    const createResponse = await client.createContent({
      TemplateId: 'your-template-id',
      Fields: {
        Product: 'iron',
        Offer: '20%',
        Price: '$25',
        'Expiry Date': '2025-04-01'
      },
      Output: {
        Format: 'PDF',
        Quality: 'Print'
      }
    });
    console.log(`Request ID: ${createResponse.RequestId}`);
    
    // 2. Poll for completion
    const contentResponse = await client.pollForContent(createResponse.RequestId);
    
    // 3. Check if content is ready and has URLs
    if (contentResponse.Status === 'Completed' && contentResponse.URLs?.length) {
      // 4. Download the content
      const pdfUrl = contentResponse.URLs[0];
      const response = await fetch(pdfUrl);
      const arrayBuffer = await response.arrayBuffer();
      const content = Buffer.from(arrayBuffer);
      
      // 5. Save to file
      fs.writeFileSync('output.pdf', content);
      console.log('PDF saved successfully!');
    }
  } catch (error) {
    console.error('Error:', error);
  }
}

// Run the function
generatePDF();

Error Handling

The Personalia Node.js client provides comprehensive error handling through the PersonaliaError class. All errors thrown by the client will be instances of PersonaliaError, which extends the standard JavaScript Error class with additional properties:

  • statusCode: The HTTP status code of the response (if available)
  • errorCode: The Personalia error code (if available)
  • errorId: The error ID from the API (if available)

Common Error Scenarios

1. Synchronous Errors (from POST /v1/content)

These errors occur immediately when making an API request with invalid data:

try {
  const response = await client.createContent({
    TemplateId: 'invalid-template',
    Fields: { /* ... */ }
  });
} catch (error) {
  if (error instanceof PersonaliaError) {
    console.error(`Error ${error.errorCode}: ${error.message}`);
    console.error(`Status: ${error.statusCode}`);
    
    // Handle specific error codes
    if (error.errorCode === 101) {
      console.error('API key does not belong to this template');
    } else if (error.errorCode === 103) {
      console.error('Invalid Template ID');
    }
  } else {
    console.error('Unexpected error:', error);
  }
}

2. Asynchronous Errors (from GET /v1/content)

These errors occur during content generation and are detected when polling for results:

try {
  const response = await client.pollForContent(requestId);
} catch (error) {
  if (error instanceof PersonaliaError) {
    console.error(`Content generation failed: ${error.message}`);
    
    // Check for specific error conditions
    if (error.errorCode === 107) {
      console.error('Failed to fetch a URL in your template');
    } else if (error.errorCode === 117) {
      console.error('Insufficient credits - please upgrade your plan');
    }
  }
}

3. Network and Request Errors

These errors occur when there are network issues or the server is unreachable:

try {
  const response = await client.createContent(/* ... */);
} catch (error) {
  if (error instanceof PersonaliaError) {
    if (error.statusCode === 0) {
      console.error('Network error - please check your internet connection');
    } else {
      console.error(`API error: ${error.message}`);
    }
  }
}

Complete Error Reference

| Error Code | Description | Possible Solution | |------------|-------------|-------------------| | 101 | API key does not belong to this template | Verify your API key and template ID | | 102 | Invalid or missing API key | Check your API key | | 103 | Invalid Template ID | Verify the template ID | | 104 | Invalid value(s) in the Output section | Check your output parameters | | 105 | Too many Fetch URLs | Reduce the number of Fetch URLs in your template | | 106 | Total Fetch URL size too large | Reduce the size of your Fetch URLs | | 107 | Failed to fetch a URL | Check the URL and try again | | 108 | Invalid/unsupported image format | Use PNG or JPG images | | 109 | Invalid JSON syntax | Check your request body | | 111 | Missing required field | Include all required fields | | 112 | Invalid date format | Use YYYY-MM-DD format | | 113 | Invalid number format | Check number formatting | | 114 | Invalid Fetch protocol | Use HTTP or HTTPS | | 117 | Insufficient credits | Upgrade your plan | | 118 | Unsupported output format | Use a supported format | | 1000-1009 | Server-side errors | Contact support |

Complete Workflow Example

Here's a complete example that demonstrates how to:

  1. Get template information
  2. Create a content request
  3. Poll for completion
  4. Download and save the content
import { PersonaliaClient } from 'personalia-node';
import * as fs from 'fs';
import * as path from 'path';

async function generatePersonalizedContent() {
  // Initialize the client with your API key
  const client = new PersonaliaClient('YOUR_API_KEY');
  const templateId = 'your-template-id';
  const outputDir = './output';
  
  // Ensure output directory exists
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }

  try {
    // Step 1: Get template information to understand required fields
    console.log('Getting template information...');
    const templateInfo = await client.getTemplateInfo(templateId);
    console.log('Template fields:');
    templateInfo.Fields.forEach(field => {
      console.log(`- ${field.Name} (${field.Type})`);
    });

    // Step 2: Create content request with the required fields
    console.log('\nSubmitting content creation request...');
    const createResponse = await client.createContent({
      TemplateId: templateId,
      Fields: {
        // Populate with actual field values based on templateInfo.Fields
        Product: 'iron',
        Offer: '20%',
        Price: '$25',
        'Expiry Date': '2025-04-01'
      },
      Output: {
        Format: 'PDF',
        Quality: 'Print',
        StrictPolicy: true
      }
    });
    console.log(`Request submitted. Request ID: ${createResponse.RequestId}`);

    // Step 3: Use the built-in polling mechanism
    console.log('\nPolling for content completion...');
    const contentResponse = await client.pollForContent(
      createResponse.RequestId,
      30,  // Maximum 30 attempts
      2000 // 2 second interval between attempts
    );

    // Step 4: Handle the generated content
    console.log('\nContent details:');
    console.log(`Status: ${contentResponse.Status}`);
    
    if (contentResponse.Status === 'Completed' && contentResponse.URLs?.length) {
      console.log(`Content URLs: ${contentResponse.URLs.length} available`);
      
      // Step 5: Download the content
      const contentUrl = contentResponse.URLs[0];
      console.log(`Downloading from: ${contentUrl}`);
      
      const response = await fetch(contentUrl);
      if (!response.ok) {
        throw new Error(`Download failed: ${response.status} ${response.statusText}`);
      }
      
      // Get content type to determine file extension
      const contentType = response.headers.get('content-type') || '';
      let extension = 'pdf'; // Default
      if (contentType.includes('png')) extension = 'png';
      else if (contentType.includes('jpg') || contentType.includes('jpeg')) extension = 'jpg';
      
      // Save the file
      const filename = path.join(outputDir, `personalia-${createResponse.RequestId}.${extension}`);
      const arrayBuffer = await response.arrayBuffer();
      const content = Buffer.from(arrayBuffer);
      fs.writeFileSync(filename, content);
      
      console.log(`File saved to: ${filename}`);
      console.log(`File size: ${(content.length / 1024).toFixed(2)} KB`);
    } else if (contentResponse.Status === 'Failed') {
      console.error(`Content generation failed: ${contentResponse.FailureDescription}`);
    }

    return contentResponse;
  } catch (error) {
    console.error('Error:', error instanceof Error ? error.message : error);
    throw error;
  }
}

API Reference

PersonaliaClient

The main class for interacting with the Personalia API.

Constructor

const client = new PersonaliaClient(apiKey: string, baseUrl?: string);
  • apiKey: Your Personalia API key (required)
  • baseUrl: Optional custom API base URL (defaults to 'https://api.personalia.io')

Methods

getTemplateInfo

Retrieves information about a template, including its required fields.

async getTemplateInfo(templateId: string): Promise<GetTemplateInfoResponse>

Example:

const templateInfo = await client.getTemplateInfo('template-123');
console.log(templateInfo.Fields); // Array of field definitions
createContent

Submits a request for content creation (PDF, PNG, or JPG).

async createContent(request: CreateContentRequest): Promise<CreateContentResponse>

Example:

const response = await client.createContent({
  TemplateId: 'template-123',
  Fields: {
    Product: 'iron',
    Offer: '20%',
    Price: '$25',
    'Expiry Date': '2025-04-01'
  },
  Output: {
    Format: 'PDF',
    Quality: 'Print',
    StrictPolicy: true
  }
});
console.log(response.RequestId); // Use this ID to poll for content
getContent

Retrieves the status and URLs for a previously submitted content request.

async getContent(requestId: string): Promise<GetContentResponse>

Example:

const content = await client.getContent('request-123');
if (content.Status === 'Completed' && content.URLs?.length) {
  console.log(`Content available at: ${content.URLs[0]}`);
}
pollForContent

Polls for content completion with automatic retries.

async pollForContent(requestId: string, maxAttempts = 30, interval = 2000): Promise<GetContentResponse>

Example:

// Will automatically retry until content is ready or max attempts reached
const content = await client.pollForContent('request-123', 20, 3000);
if (content.Status === 'Completed') {
  console.log('Content ready for download!');
}
createContentUrl

Creates a content on-demand URL that generates content when accessed.

async createContentUrl(request: CreateContentRequest): Promise<CreateUrlResponse>

Example:

const urlResponse = await client.createContentUrl({
  TemplateId: 'template-123',
  Fields: {
    Product: 'iron',
    Offer: '20%',
    Price: '$25'
  },
  Output: {
    Format: 'PDF',
    Quality: 'Print'
  }
});
console.log(`Content URL: ${urlResponse.Url}`);

Response Types

GetContentResponse

Represents the response from the getContent and pollForContent methods.

interface GetContentResponse {
  Status: 'Completed' | 'InProgress' | 'Failed';
  URLs?: string[];  // Array of URLs to download the content (when Status is 'Completed')
  FailureDescription?: string | null; // Description of the failure (when Status is 'Failed')
}
CreateContentResponse

Represents the response from the createContent method.

interface CreateContentResponse {
  RequestId: string; // ID to use with getContent or pollForContent
}
CreateUrlResponse

Represents the response from the createContentUrl method.

interface CreateUrlResponse {
  Url: string; // URL that will generate content on demand
}
TemplateInfo

Represents the response from the getTemplateInfo method.

interface TemplateInfo {
  TemplateId: string;
  Fields: Array<{
    Name: string;
    Type: string;
  }>;
}

Example response:

{
  "TemplateId": "c790fd3b-10ef-4a58-963b-b688e564eefa",
  "Fields": [
    {
      "Name": "First Name",
      "Type": "String"
    },
    {
      "Name": "Machine",
      "Type": "Number"
    },
    {
      "Name": "Color",
      "Type": "String"
    }
  ]
}

Examples

The library includes several example scripts in the examples directory:

Get Template Fields

Retrieve the fields required by a template:

// examples/get-template-fields.ts
import { PersonaliaClient } from '../src/client';

async function getTemplateFields() {
  const client = new PersonaliaClient('YOUR_API_KEY');
  const templateInfo = await client.getTemplateInfo('YOUR_TEMPLATE_ID');
  
  console.log('Template fields:');
  templateInfo.Fields.forEach(field => {
    console.log(`- ${field.Name} (${field.Type})${field.Required ? ' (Required)' : ''}`);
  });
}

Create PDF (Print Quality)

Generate a high-quality PDF and download it:

// examples/create-pdf-print.ts
import { PersonaliaClient } from '../src/client';
import * as fs from 'fs';
import * as path from 'path';

async function createPdfPrint() {
  const client = new PersonaliaClient('YOUR_API_KEY');
  
  // Create content request
  const createResponse = await client.createContent({
    TemplateId: 'YOUR_TEMPLATE_ID',
    Fields: { /* your fields */ },
    Output: {
      Format: 'PDF',
      Quality: 'Print'
    }
  });
  
  // Poll for completion
  const contentResponse = await client.pollForContent(createResponse.RequestId);
  
  // Download and save the PDF
  if (contentResponse.Status === 'Completed' && contentResponse.URLs?.length) {
    const pdfUrl = contentResponse.URLs[0];
    const response = await fetch(pdfUrl);
    const arrayBuffer = await response.arrayBuffer();
    const content = Buffer.from(arrayBuffer);
    
    fs.writeFileSync('output.pdf', content);
  }
}

Create PNG (High Resolution)

Generate a high-resolution PNG image:

// examples/create-png-high-res.ts
import { PersonaliaClient } from '../src/client';

async function createPngHighRes() {
  const client = new PersonaliaClient('YOUR_API_KEY');
  
  // Create content request
  const createResponse = await client.createContent({
    TemplateId: 'YOUR_TEMPLATE_ID',
    Fields: { /* your fields */ },
    Output: {
      Format: 'PNG',
      Resolution: 300, // High resolution (300 DPI)
      StrictPolicy: true
    }
  });
  
  // Poll for completion
  const contentResponse = await client.pollForContent(createResponse.RequestId);
  
  // Process the URLs
  if (contentResponse.Status === 'Completed' && contentResponse.URLs?.length) {
    console.log('PNG URLs:');
    contentResponse.URLs.forEach(url => console.log(url));
  }
}

Create Content URL

Generate a URL that creates content on demand:

// examples/create-content-url.ts
import { PersonaliaClient } from '../src/client';

async function createContentUrl() {
  const client = new PersonaliaClient('YOUR_API_KEY');
  
  // Create URL request
  const urlResponse = await client.createContentUrl({
    TemplateId: 'YOUR_TEMPLATE_ID',
    Fields: { /* your fields */ },
    Output: {
      Format: 'PDF',
      Quality: 'Display'
    }
  });
  
  console.log(`Content URL: ${urlResponse.Url}`);
  console.log('This URL will generate content on demand when accessed.');
}

Error Handling

The library provides a robust error handling system with the PersonaliaError class, which includes detailed information about API errors:

try {
  const content = await client.getContent('invalid-request-id');
} catch (error) {
  if (error instanceof PersonaliaError) {
    // Access detailed error information
    console.error(`Error ${error.statusCode}: ${error.message}`);
    console.error(`Error ID: ${error.errorId || 'N/A'}`);
    console.error(`Error Code: ${error.errorCode || 'N/A'}`);
    
    // Handle specific error types
    if (error.statusCode === 404) {
      console.error('Content not found or not ready yet');
    } else if (error.statusCode === 401) {
      console.error('Authentication failed - check your API key');
    } else if (error.errorId === '117') {
      console.error('Insufficient credits - please top up your account');
    }
  } else {
    console.error('An unexpected error occurred:', error.message);
  }
}

A full list of the error codes can be found at https://developer.personalia.io/

Personalia Error Codes

The library maps specific Personalia API error codes to helpful messages and suggested fixes. You can access the full list of error codes:

import { PERSONALIA_ERRORS } from 'personalia';

// Example: Get information about a specific error code
const errorInfo = PERSONALIA_ERRORS['101'];
console.log(`Error message: ${errorInfo.message}`);
console.log(`Suggested fix: ${errorInfo.fix}`);

License

MIT

Types

CreateContentRequest

interface CreateContentRequest {
  TemplateId: string;
  Fields: Record<string, string | number | boolean>;
  Output?: {
    Format?: 'PDF' | 'JPG' | 'PNG';
    Quality?: 'Display' | 'Print';
    Resolution?: number;
    Package?: boolean;
    StrictPolicy?: boolean;
  };
}

Field Value Formats

  • Strings: Use double quotes

    • Valid: "Hello", "2025-02-21"
    • Invalid: 'Hello', '2025-02-21'
  • Numbers: No comma separator, only 1 decimal point (optional), can be negative

    • Valid: 1000.50, -2000
    • Invalid: 1,000.50
  • Dates: Format must be YYYY-MM-DD

    • Valid: 2025-02-21
    • Invalid: 21/02/2025
  • Booleans: Use string values "1"/"0" (not boolean true/false)

    • Valid: "1" (true/yes/on), "0" (false/no/off)
    • Invalid: true, false, 1, 0 (as non-string values)

Output Options

  • Format:

    • PDF: PDF document
    • JPG: JPEG image
    • PNG: PNG image
  • Quality (PDF only):

    • Display: Optimized for screen viewing
    • Print: High-quality print output
  • Resolution (images only):

    • Range: 72-300 DPI
    • Default: 72
  • Package:

    • true: Compress output into ZIP file
    • false: Return single file (default)
  • StrictPolicy:

    • true: Fail on missing assets/fonts/styles (default)
    • false: Ignore issues (may cause unexpected results)

Error Handling

The client includes built-in error handling that will throw errors with descriptive messages. Each error includes an error ID and reason from the API.

try {
  await client.createContent({...});
} catch (error) {
  // Error will include API error ID and reason if available
  console.error(error.message);
  // Example: "Personalia API Error 101: API key does not belong to this template"
}

Best Practices

  1. API Key Security: Never expose your API key in client-side code. Always keep it secure on your server.

  2. Error Handling: Always implement proper error handling around API calls.

  3. Template Testing: Use getTemplateInfo to validate your template fields before submitting content requests.

  4. Output Format: Choose the appropriate output format and quality for your use case:

    • Use PDF with Print quality for documents that need to be printed
    • Use JPG/PNG with appropriate resolution for web/screen display

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT

Getting Started with Personalia

To use this library, you'll need a Personalia API key. Visit personalia.io to:

  1. Create an account and access the dashboard
  2. Create templates using Adobe InDesign
  3. Generate your API key
  4. Start creating personalized content at scale