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

@formpipe/contact-form

v0.1.2

Published

A collection of validators and sanitizers for form inputs in TypeScript.

Downloads

210

Readme

@formpipe/contact-form

A robust and flexible solution for handling contact form submissions and sending emails. This package provides a TypeScript library for frontend integration and a CLI tool to generate a PHP backend for email processing.

Features

  • Frontend and Backend Validation: Integrates with @formpipe/validators for form input validation.
  • CLI Configuration: Easily set up and generate your PHP email backend.
  • PHP Email Backend: Securely sends emails using PHPMailer, supporting SMTP and confirmation emails.
  • One-command Docker setup: Start local development environment with npx formpipe serve - includes PHP and Mailpit for testing email sending

Installation

To install the package, use npm:

npm install @formpipe/contact-form

Usage

1. Initialize Configuration

First, use the formpipe CLI to create a formpipe.config.json file in your project root. This file will store your email sending and form validation settings.

npx formpipe init

The formpipe.config.json is generated with default values ready to test email's send in a local environment. You can personalize the input's rules, smtp options, your endpoint path and more.

rules: ValidatorConstraints; // Rules for your inputs
endPointPath: string; // The endpoint to fetch - Default is http://localhost:8080/php/contact-form.php

// smtp config (Use environment variables to prevent security leeks)
// See more in php/ENV.md to learn how to config it
smtp: {
  host: string; // Default is "mailpit" for local testing
  port: number; // Default is 1025 for local testing
  user: string;
  pass: string;
}
from: string;
to: string;
useLocalPhpMailer: boolean; // Switch between local or composer phpmailer
rateLimit: number; // Frontend-only: max requests allowed per minute (client-side via localStorage)

See Configuration for more details

2. Generate PHP Backend

After personalize your formpipe.config.json file run:

npx formpipe generate

This command will create a php folder with the following files:

php
├── contact-form.php // The endpoint file
├── docker-compose.yml // A ready to use php + mailpit container (docker is required)
├── formConfig.ts // Your rules and endpoint url, you need this to setup your formpipe instance
└── PHPMailer // A ready to use local PHPMailer instance

3. Start Development Server

To start the Docker containers for local development and testing:

npx formpipe serve

This command will:

  • Start the PHP and Mailpit containers in detached mode
  • Display the URLs where services are available:
    • Mailpit: http://localhost:8025 (to view sent emails)
    • PHP Endpoint: http://localhost:8080/php/contact-form.php

Note: Make sure Docker is installed and running before using this command.

4. Methods

The ContactForm class provides the following methods:

validate(ValidationProps): ValidationResponse

Validates form data against the rules defined in your configuration without submitting the form.

Parameters:

  • data: ValidationProps object containing the fields to validate

Returns: ValidationResponse object with validation results

Use case: Useful when you want to validate form data without submitting, or when using your own API backend instead of the generated PHP endpoint.

Example:

import {
  ContactForm,
  type ValidationResponse,
  type ValidationProps,
} from '@formpipe/contact-form';
import { formConfig } from '../php/formConfig';

// Initialize ContactForm with the config generated by formpipe CLI
const form = new ContactForm(formConfig);

// Validate form data
const dataToValidate: ValidationProps = {
  replyTo: '[email protected]',
  subject: 'Testing formpipe',
  message: 'A simple message to test the formpipe',
  phoneNumber: '+5491133333333',
};

const validationResult: ValidationResponse = form.validate(dataToValidate);

if (validationResult.success) {
  console.log('Validation passed!');
} else {
  console.error('Validation errors:', validationResult.errors);
  // errors is an array of InputError objects
  validationResult.errors?.forEach((error) => {
    console.error(`${error.field}: ${error.message}`);
  });
}

See @formpipe/validators for details about validation.


submit(props: SubmitProps): Promise<FormResponse>

Validates and submits form data to the configured PHP backend endpoint.

Parameters:

  • props: SubmitProps object containing:
    • fields: Complete FormData object
    • options: Optional configuration
      • persistData: Store form data in localStorage if submission fails (default: false)

Returns: Promise resolving to FormResponse object

Behavior:

  1. First validates the data using validate() method
  2. If validation passes, sends POST request to the configured endpoint
  3. Returns response with success status or error details

loadFromStorage(): FormData | null

Retrieves form data from localStorage that was saved after a failed submission (only if persistData: true was used).

Returns: FormData object if data exists, null otherwise

clearStorage(): void

Clears any form data stored in localStorage.

Example:

import {
  ContactForm,
  type FormResponse,
  type SubmitProps,
} from '@formpipe/contact-form';
import { formConfig } from '../php/formConfig';

// Initialize ContactForm with the config generated by formpipe CLI
const contactForm = new ContactForm(formConfig);

async function handleSubmit(event: Event) {
  event.preventDefault();

  // Extract form data from HTML form
  const formData = new FormData(event.target as HTMLFormElement);
  const data = Object.fromEntries(formData.entries());

  const submitData: SubmitProps = {
    fields: {
      replyTo: data.email,
      name: data.name, // Optional
      subject: data.subject,
      message: data.message,
      phoneNumber: data.phone, // Optional
    },
    options: {
      persistData: true, // Store form data in localStorage if submission fails
    },
  };

  const response: FormResponse = await contactForm.submit(submitData);

  if (response.success) {
    // Success! Show success message
    alert(response.message); // "Email sent successfully"
  } else {
    // Handle errors
    alert(response.message);

    if (response.errors) {
      // Log validation errors
      console.error('Validation errors:', response.errors);

      // If persistData was true, retrieve stored data
      const storedData = contactForm.loadFromStorage();
      if (storedData) {
        console.log('Stored form data:', storedData);
        // You can use this to repopulate the form
      }

      // Display errors to user
      response.errors.forEach((error) => {
        if ('field' in error) {
          // Validation error
          console.error(`${error.field}: ${error.message}`);
        } else {
          // Network/server error
          console.error(`${error.type}: ${error.message}`);
        }
      });
    }
  }
}

// Attach handler to form submit event
document
  .getElementById('your-form-id')
  ?.addEventListener('submit', handleSubmit);

Error Handling

The FormResponse and ValidationResponse objects can contain different types of errors:

Validation Errors (InputError[])

Returned when form validation fails (status 400). Each error contains:

  • field: The field name that failed validation
  • value: The invalid value provided
  • rules: The validation rules that were applied
  • message: Human-readable error message
  • type: Always 'validation' for validation errors
if (!response.success && response.errors) {
  response.errors.forEach((error) => {
    if ('field' in error) {
      // This is a validation error
      console.error(`Field "${error.field}" failed: ${error.message}`);
      console.error(`Value: ${error.value}`);
      console.error(`Rules:`, error.rules);
    }
  });
}

Other Errors

Network, server, or system errors are returned as an array of error objects:

{
  type: 'validation' | 'network' | 'server' | 'system';
  message: string;
  data?: unknown;
}

Error Types:

  • validation: Form validation failed (usually handled as InputError[])
  • network: Network request failed (e.g., CORS, connection issues)
  • server: Server returned an error (e.g., 500, 503)
  • system: Internal system error (e.g., validator not initialized)

Configuration (formpipe.config.json)

The formpipe.config.json file is generated by npx formpipe init and contains all settings for your contact form. Here's what each configuration option does:

| Option | Type | Description | | ----------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | rules | ValidatorConstraints | Validation rules for each form field. See Validation Rules section for details. | | endPointPath | string | The URL of your PHP backend endpoint. Default: http://localhost:8080/php/contact-form.php | | smtp | object | SMTP server configuration for sending emails. Contains: host, port, user, pass. Default: Mailpit local testing settings. | | from | string | Sender email address displayed in outgoing emails. | | to | string | Recipient email address where form submissions are sent. | | useLocalPhpMailer | boolean | Whether to use the bundled local PHPMailer (true) or Composer's PHPMailer (false). | | rateLimit | number | Frontend-only: maximum number of form submissions allowed per minute (client-side via localStorage). In production, configure rate limiting at the infrastructure level (Cloudflare, firewall, hosting panel). |

Environment Variables for Production

⚠️ Security Best Practice: For production deployments, store sensitive configuration in environment variables instead of hardcoding them in formpipe.config.json or the generated PHP file. This prevents credentials from being accidentally committed to version control.

The generated contact-form.php automatically uses the following environment variables if they are set (otherwise falls back to config file values):

| Environment Variable | Description | | -------------------- | ---------------------------- | | FORMPIPE_SMTP_HOST | SMTP server hostname | | FORMPIPE_SMTP_PORT | SMTP server port number | | FORMPIPE_SMTP_USER | SMTP authentication username | | FORMPIPE_SMTP_PASS | SMTP authentication password | | FORMPIPE_FROM | Sender email address | | FORMPIPE_TO | Recipient email address |

Benefits:

  • ✅ Credentials stay out of version control
  • ✅ Use different settings for development, staging, and production
  • ✅ Follows security best practices
  • ✅ Easy to manage secrets in production environments

Setup Examples:

Apache (.htaccess):

SetEnv FORMPIPE_SMTP_HOST "smtp.gmail.com"
SetEnv FORMPIPE_SMTP_PORT "587"
SetEnv FORMPIPE_SMTP_USER "[email protected]"
SetEnv FORMPIPE_SMTP_PASS "your-app-password"
SetEnv FORMPIPE_FROM "[email protected]"
SetEnv FORMPIPE_TO "[email protected]"

Nginx (in PHP-FPM pool or nginx config):

env[FORMPIPE_SMTP_HOST] = smtp.gmail.com
env[FORMPIPE_SMTP_PORT] = 587
env[FORMPIPE_SMTP_USER] = [email protected]
env[FORMPIPE_SMTP_PASS] = your-app-password
env[FORMPIPE_FROM] = [email protected]
env[FORMPIPE_TO] = [email protected]

Docker (docker-compose.yml):

services:
  php:
    environment:
      - FORMPIPE_SMTP_HOST=smtp.gmail.com
      - FORMPIPE_SMTP_PORT=587
      - [email protected]
      - FORMPIPE_SMTP_PASS=your-app-password
      - [email protected]
      - [email protected]

Validation Rules

Each field in the rules object can have the following validation options:

interface InputRules {
  required?: boolean; // Field must be provided
  minLength?: number; // Minimum number of characters
  maxLength?: number; // Maximum number of characters
  isEmail?: boolean; // Validate email format (use for replyTo field)
  phoneValidationMode?: 'loose' | 'strict' | 'e164'; // Phone number validation mode
}

Phone Validation Modes

The phoneValidationMode option supports three different formats for validating phone numbers. Choose the one that best fits your needs:

e164 (Recommended for international forms)

International E.164 format. Optional + prefix, followed by 8–15 digits, cannot start with 0.

✅ Valid:   +14155552671, 34911223344, +33912345678
❌ Invalid: +0012345678, 00000000, 123456

strict (Digits only)

Only digits (0-9), between 8 and 15 characters. No special characters, spaces, or formatting allowed.

✅ Valid:   12345678, 123456789012345
❌ Invalid: +1234567890, 123 456 789, (123) 456-7890

loose (Flexible formatting)

Allows spaces, + prefix, parentheses, and hyphens. Requires at least 8 characters total.

✅ Valid:   (123) 456-7890, +34 600 123 456, +1 (555) 123-4567
❌ Invalid: 12345 (too short), 123456789abc (non-numeric characters)

Configuration Example

Here's a complete example of a valid rules configuration:

{
  "rules": {
    "replyTo": {
      "isEmail": true,
      "required": true,
      "minLength": 5,
      "maxLength": 100
    },
    "subject": {
      "required": true,
      "minLength": 5,
      "maxLength": 100
    },
    "name": {
      "required": false,
      "minLength": 2,
      "maxLength": 50
    },
    "phoneNumber": {
      "required": false,
      "phoneValidationMode": "e164"
    },
    "message": {
      "required": true,
      "minLength": 10,
      "maxLength": 500
    }
  }
}

How to update rules:

  1. Modify the validation rules in your formpipe.config.json file
  2. Run npx formpipe generate to sync the rules to your PHP backend
  3. The frontend and backend validation will stay in sync

TypeScript Types

FormData

The data structure for form submissions. All fields except replyTo, subject, and message are optional.

interface FormData {
  replyTo: string; // Email address (required)
  subject: string; // Email subject (required)
  message: string; // Email message body (required)
  name?: string; // Optional name field
  phoneNumber?: string; // Optional phone number
}

ValidationProps

type ValidationProps = Partial<FormData>;

ValidationResponse

Response returned by the validate() method.

interface ValidationResponse {
  success: boolean;
  status: number; // HTTP status code (200 for success, 400 for validation errors)
  message: string; // Human-readable message
  errors: InputError[] | null;
  data?: {
    fields?: FormData;
    rules?: ValidatorConstraints;
  };
}

FormResponse

Response returned by the submit() method. Can contain validation errors or other error types.

interface FormResponse {
  success: boolean;
  status: number; // HTTP status code
  message: string; // Human-readable message
  data?: {
    fields?: FormData | null;
    url?: string;
    rules?: ValidatorConstraints;
  };
  errors?:
    | InputError[] // Validation errors (when status is 400)
    | Array<{
        type: 'validation' | 'network' | 'server' | 'system';
        message: string;
        data?: unknown;
      }>
    | null;
}

InputError

Structure of validation errors returned in errors arrays.

interface InputError {
  field: keyof FormData; // The field that failed validation
  value: string; // The value that was provided
  rules: ValidationConstraints; // The rules that were applied
  message: string; // Error message
  type: 'validation'; // Error type
}

SubmitProps

Input for the submit() method.

interface SubmitProps {
  fields: FormData; // Form data to submit
  options?: {
    persistData?: boolean; // Store data in localStorage on failure (default: false)
    debug?: boolean; // Enable debug logging (default: false)
  };
}

FormConfig

Configuration object for initializing ContactForm.

interface FormConfig {
  rules: ValidatorConstraints; // Validation rules for each field
  endPointPath: string; // URL of the PHP backend endpoint
  rateLimit: number; // Frontend-only: max requests per minute (client-side via localStorage)
}

Best Practices

1. Always Import formConfig from Generated PHP Folder

The formConfig.ts file generated by npx formpipe generate ensures your frontend validation rules match the backend rules. Always use this file instead of manually creating the config:

// ✅ Good
import { formConfig } from '../php/formConfig';
const form = new ContactForm(formConfig);

// ❌ Bad - Manual config can get out of sync
const form = new ContactForm({
  rules: {
    /* ... */
  },
  endPointPath: '...',
});

2. Start Development Server with Docker

Use the serve command to start Docker containers for local testing:

npx formpipe serve

This will start both PHP and Mailpit containers. You can then:

  • Access your PHP endpoint at: http://localhost:8080/php/contact-form.php
  • View sent emails in Mailpit at: http://localhost:8025

Alternative: You can also manually start Docker containers:

cd php
docker compose up -d

To stop containers:

cd php
docker compose down

OpenCode Skill

This package ships with a SKILL.md file that helps AI agents set up and use the library correctly. If you're using OpenCode, you can load it into your project:

cp node_modules/@formpipe/contact-form/SKILL.md .opencode/skills/contact-form/SKILL.md

Related Packages

License

MIT