@formpipe/contact-form
v0.1.2
Published
A collection of validators and sanitizers for form inputs in TypeScript.
Downloads
210
Maintainers
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/validatorsfor 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-formUsage
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 initThe 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 generateThis 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 instance3. Start Development Server
To start the Docker containers for local development and testing:
npx formpipe serveThis 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:ValidationPropsobject 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/validatorsfor details about validation.
submit(props: SubmitProps): Promise<FormResponse>
Validates and submits form data to the configured PHP backend endpoint.
Parameters:
props:SubmitPropsobject containing:fields: CompleteFormDataobjectoptions: Optional configurationpersistData: Store form data in localStorage if submission fails (default:false)
Returns: Promise resolving to FormResponse object
Behavior:
- First validates the data using
validate()method - If validation passes, sends POST request to the configured endpoint
- 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 validationvalue: The invalid value providedrules: The validation rules that were appliedmessage: Human-readable error messagetype: 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 asInputError[])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, 123456strict (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-7890loose (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:
- Modify the validation rules in your
formpipe.config.jsonfile - Run
npx formpipe generateto sync the rules to your PHP backend - 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 serveThis 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 -dTo stop containers:
cd php
docker compose downOpenCode 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.mdRelated Packages
@formpipe/validators- Validation utilities used by this package
License
MIT
