SimpleRequest

HTTP request abstraction with input handling, validation, and content negotiation.

Alpha — v0.1.0. This module is in early testing. The API may change before a stable release. Feedback and bug reports are welcome.

A modern HTTP request handler for ProcessWire that provides an expressive, type-safe API for accessing request data. Handle form inputs, headers, files, cookies, JSON payloads, HTMX requests, and client information with an elegant, fluent interface - all while maintaining ProcessWire's philosophy of simplicity.

The Request lives at SimpleWire\Request\Request and can be installed standalone or as part of the SimpleWire suite.

Features


  • Type-Safe HTTP Methods: Enum-based method detection (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
  • Input Handling: Access and filter request data with sanitization built-in
  • Typed Getters: Get input as strings, integers, floats, booleans, or dates
  • HTMX Integration: Full support for HTMX headers and request detection
  • Content Negotiation: Accept header parsing, JSON detection, content type preferences
  • File Uploads: Simple file handling with validation
  • Validation System: Built-in validation with 40+ rules
  • Authentication: Bearer token extraction from Authorization headers
  • Client Information: IP detection, user agent, host information
  • Magic Methods: Dynamic property access for convenience

Quick Start


// Via the shorthand function (recommended)
$request = simplerequest();

// Via the global request() helper
$request = request();

// Via the ProcessWire API variable
$request = wire()->simplerequest;

// Via SimpleWire suite facade (if SimpleWire is installed)
$request = simplewire()->request();

Basic Usage

<?php
namespace ProcessWire;

// Check request method
if (request()->isPost()) {
    // Handle POST request
}

// Get input value
$email = request()->input('email');

// Get typed input
$age = request()->integer('age');
$price = request()->float('price');
$agreed = request()->boolean('terms');

// Check if HTMX request
if (request()->isHtmx()) {
    // Return partial HTML
}

// Access headers
$token = request()->bearerToken();
$userAgent = request()->userAgent();

// Get client IP
$ip = request()->ip();

With Router Integration

<?php
// /site/templates/api.php
namespace ProcessWire;

// Handle POST request
$page->route("post:users", function() {
    // Validate input
    $data = request()->validate([
        'name' => 'required|min:3|max:50',
        'email' => 'required|email',
        'age' => 'required|integer|gte:18'
    ]);

    // Get validated data
    $name = request()->string('name');
    $email = request()->string('email');
    $age = request()->integer('age');

    // Create user...

    header('Content-Type: application/json');
    return json_encode(['success' => true]);
});

// Handle JSON payload
$page->route("post:webhook", function() {
    $data = request()->json();
    // Process webhook data...
    header('Content-Type: application/json');
    return json_encode(['received' => true]);
});

$result = $page->dispatchRoutes();
if ($result !== null) {
    echo $result;
}

HTTP Method Detection


Check Request Method

// Individual method checks
if (request()->isGet()) { /* ... */ }
if (request()->isPost()) { /* ... */ }
if (request()->isPut()) { /* ... */ }
if (request()->isDelete()) { /* ... */ }
if (request()->isPatch()) { /* ... */ }
if (request()->isHead()) { /* ... */ }
if (request()->isOptions()) { /* ... */ }

// Generic check with string
if (request()->is('post')) { /* ... */ }

// Using enum (type-safe)
use SimpleWire\Request\HttpMethod;
if (request()->isMethod(HttpMethod::POST)) { /* ... */ }

// Get method as enum
$method = request()->getMethod();
// Returns: HttpMethod enum or null

Request Type Detection

// AJAX request
if (request()->isAjax()) {
    header('Content-Type: application/json');
    echo json_encode($data);
}

// HTMX request
if (request()->isHtmx()) {
    // Return partial HTML
}

// HTMX boosted request (full page load via HTMX)
if (request()->isBoosted()) {
    // Return full page
}

// Combined check
if (request()->is('htmx')) { /* ... */ }
if (request()->is('ajax')) { /* ... */ }

Input Handling


Getting Input Values

// Get single input (sanitized automatically)
$name = request()->input('name');

// Get all input as array
$data = request()->input();

// Get with magic methods
$email = request()->email;
$email = request()->email(); // Same as above

// Check if input exists
if (request()->has('email')) {
    // Input key exists (even if empty)
}

// Check if any of multiple keys exist
if (request()->hasAny(['email', 'username'])) {
    // At least one exists
}

// Check if input exists and is not empty
if (request()->filled('email')) {
    // Email exists and has a value
}

// Check if any of multiple keys are filled
if (request()->anyFilled(['email', 'username'])) {
    // At least one has a value
}

// Check if input is missing
if (request()->missing('phone')) {
    // Phone field not present
}

Typed Input Getters

// Get as string (sanitized via ProcessWire)
$name = request()->string('name');
// Returns: ?string

// Get as integer
$age = request()->integer('age');
// Returns: ?int

// Get as float
$price = request()->float('price');
// Returns: ?float

// Get as boolean
$agreed = request()->boolean('terms');
// Returns: bool (false if not present)

// Get as email (sanitized)
$email = request()->email('email');
// Returns: ?string

// Get as URL (sanitized)
$website = request()->urlInput('website');
// Returns: ?string

// Get as date
$birthdate = request()->date('birthdate');
// Returns: ?string (formatted date)

// Get as date with custom format
$date = request()->date('event_date', 'd/m/Y');

// Get as enum
use SimpleWire\Request\HttpMethod;
$method = request()->enum('method', HttpMethod::class);

Filtering Input

// Get only specific keys
$credentials = request()->only(['email', 'password']);
// Returns: ['email' => '...', 'password' => '...']

// Get all except specific keys
$userData = request()->except(['_token', 'submit']);
// Returns: All input except specified keys

// Merge with default values
$data = request()->merge(['status' => 'active']);
// Overwrites existing values

// Merge only if missing
$data = request()->mergeIfMissing(['role' => 'user']);
// Only adds if key doesn't exist

Conditional Input

// Execute callback when key exists
request()->whenHas('email', function($email) {
    // Process email
}, function() {
    // Default when missing
});

// Execute callback when key is filled (non-empty)
request()->whenFilled('search', function($query) {
    // Perform search
});

Specific Input Sources


// Get from query string ($_GET)
$page = request()->query('page');
$allQuery = request()->query();

// Get from POST data ($_POST)
$name = request()->post('name');
$allPost = request()->post();

// Get cookie value
$sessionId = request()->cookie('session_id');
$allCookies = request()->cookie();

// Get session value
$userId = request()->session('user_id');
$allSession = request()->session();

// Get uploaded file
$file = request()->file('avatar');
$allFiles = request()->file();

// Check if file was uploaded
if (request()->hasFile('avatar')) {
    $file = request()->file('avatar');
}

Headers


Accessing Headers

// Get single header
$contentType = request()->header('Content-Type');
$userAgent = request()->header('User-Agent');

// Get all headers
$headers = request()->headers();

// Check if header exists
if (request()->hasHeader('Authorization')) {
    // Header present
}

Bearer Token Authentication

// Extract bearer token from Authorization header
$token = request()->bearerToken();

if ($token) {
    // Validate token
    $user = authenticateToken($token);
}

// Example Authorization header:
// "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Content Negotiation


Accept Header Checking

// Check if client accepts JSON
if (request()->acceptsJson()) {
    header('Content-Type: application/json');
    echo json_encode($data);
}

// Check if client accepts HTML
if (request()->acceptsHtml()) {
    echo $page->render();
}

// Check if client accepts XML
if (request()->acceptsXml()) {
    header('Content-Type: application/xml');
    echo $xml;
}

// Check for specific content types
if (request()->accepts(['application/json', 'text/html'])) {
    // Client accepts one of these types
}

// Check if client expects JSON (AJAX + JSON accept or content-type)
if (request()->expectsJson()) {
    header('Content-Type: application/json');
    echo json_encode($data);
}

Content Type Preferences

// Get preferred content type from list
$preferred = request()->prefers(['application/json', 'text/html']);
// Returns: The first matching type or null

// Get preferred format as enum
$format = request()->preferredFormat();
// Returns: AcceptType::HTML, AcceptType::JSON, or AcceptType::XML

// Check if client wants JSON (accepts JSON or sends JSON)
if (request()->wantsJson()) {
    header('Content-Type: application/json');
    echo json_encode($data);
}

JSON & Request Body


JSON Payloads

// Get entire JSON payload as array
$data = request()->json();

// Get specific key from JSON
$name = request()->json('name');

// Get as object instead of array
$data = request()->json('', false);

// Check if request has JSON content type
if (request()->isJson()) {
    $data = request()->json();
}

File Uploads


Handling Uploaded Files

// Get file array
$file = request()->file('avatar');
// Returns: Array with 'name', 'type', 'tmp_name', 'error', 'size'

// Check if file was uploaded successfully
if (request()->hasFile('avatar')) {
    $file = request()->file('avatar');

    // Move uploaded file
    $filename = wire()->sanitizer->filename($file['name']);
    $destination = wire()->config->paths->files . 'avatars/' . $filename;
    move_uploaded_file($file['tmp_name'], $destination);
}

// Complete upload with validation
if (request()->isPost() && request()->hasFile('document')) {
    $data = request()->validate([
        'document' => 'required|file|mimes:pdf,doc,docx|max_size:5120'
    ]);

    $file = request()->file('document');
    $filename = wire()->sanitizer->filename($file['name']);
    $destination = wire()->config->paths->files . 'uploads/' . $filename;
    move_uploaded_file($file['tmp_name'], $destination);
}

Validation


Basic Validation

// Validate input — throws InvalidArgumentException on failure
try {
    $data = request()->validate([
        'email' => 'required|email',
        'password' => 'required|min:8',
        'age' => 'integer|gte:18'
    ]);
    // $data contains only the validated fields
} catch (\InvalidArgumentException $e) {
    $errors = json_decode($e->getMessage(), true);
    // Handle errors
}

// Safe validation — returns empty array on failure (no exception)
$data = request()->safe([
    'email' => 'required|email',
    'password' => 'required|min:8'
]);

Custom Error Messages

try {
    $data = request()->validate(
        [
            'email' => 'required|email',
            'username' => 'required|min:3|max:20'
        ],
        [
            'email.required' => 'Please provide your email address',
            'email.email' => 'Please provide a valid email address',
            'username.required' => 'Username cannot be empty',
            'username.min' => 'Username is too short (minimum 3 characters)',
            'username.max' => 'Username is too long (maximum 20 characters)'
        ]
    );
} catch (\InvalidArgumentException $e) {
    $errors = json_decode($e->getMessage(), true);
}

Available Validation Rules

Required & Presence

  • required - Field must be present and not empty
  • nullable - Field can be null or empty

String Validations

  • string - Must be a string
  • email - Must be valid email format
  • url - Must be valid URL format
  • alpha - Only letters (a-z, A-Z)
  • alpha_num - Letters and numbers only
  • regex:/pattern/ - Must match regex pattern

Numeric Validations

  • numeric - Must be numeric
  • integer - Must be an integer
  • decimal - Must be a decimal number
  • gt:value - Greater than value
  • gte:value - Greater than or equal to value
  • lt:value - Less than value
  • lte:value - Less than or equal to value

Size & Length

  • min:5 - Minimum length 5 characters
  • max:100 - Maximum length 100 characters
  • between:5,100 - Length between 5 and 100
  • size:10 - Exactly 10 characters

Date Validations

  • date - Must be valid date
  • date_format:Y-m-d - Must match specific format
  • before:2024-12-31 - Date must be before
  • after:2024-01-01 - Date must be after

Boolean

  • boolean - Must be boolean value (true, false, 0, 1, '0', '1')
  • accepted - Must be yes/on/true/1 (for checkboxes)

Arrays & Options

  • array - Must be an array
  • in:red,blue,green - Must be one of the listed values
  • not_in:admin,root - Cannot be one of the listed values

File Validations

  • file - Must be an uploaded file
  • image - Must be an image file
  • mimes:jpeg,png,pdf - File type must match
  • max_size:2048 - Maximum file size in KB

Network

  • ip - Valid IP address (IPv4 or IPv6)
  • ipv4 - Valid IPv4 address
  • ipv6 - Valid IPv6 address

Other

  • json - Must be valid JSON string

Validation Examples

// User registration
$data = request()->validate([
    'username' => 'required|alpha_num|min:3|max:20',
    'email' => 'required|email',
    'password' => 'required|min:8',
    'age' => 'required|integer|gte:18|lte:100',
    'website' => 'nullable|url',
    'terms' => 'accepted'
]);

// File upload
$data = request()->validate([
    'avatar' => 'required|image|max_size:2048',
    'document' => 'required|file|mimes:pdf,doc,docx|max_size:5120'
]);

// Date range
$data = request()->validate([
    'start_date' => 'required|date|date_format:Y-m-d',
    'end_date' => 'required|date|after:start_date'
]);

// API token
$data = request()->validate([
    'api_key' => 'required|regex:/^[a-zA-Z0-9]{32}$/',
    'callback_url' => 'required|url'
]);

// Product form
$data = request()->validate([
    'name' => 'required|min:3|max:100',
    'price' => 'required|decimal|gt:0',
    'quantity' => 'required|integer|gte:0',
    'category' => 'required|in:electronics,clothing,books',
    'description' => 'nullable|min:10'
]);

HTMX Integration


HTMX Request Detection

// Check if request is from HTMX
if (request()->isHtmx()) {
    // Return partial HTML
}

// Check if boosted (full page via HTMX)
if (request()->isBoosted()) {
    // Return full page but know it's HTMX
}

Client Information


User Agent & IP Address

// Get user agent
$userAgent = request()->userAgent();

// Get client IP (handles proxies via X-Forwarded-For)
$ip = request()->ip();

// Get all IPs in the chain
$ips = request()->ips();

URL & Path Information

// Get request URL
$url = request()->url();
$fullUrl = request()->fullUrl(); // With protocol and host

// Get path only
$path = request()->path();
$decoded = request()->decodedPath();

// Get host
$host = request()->host();
$httpHost = request()->httpHost();
$full = request()->schemeAndHttpHost(); // https://example.com

// Check if secure
if (request()->isSecure()) {
    // HTTPS connection
}

Complete Examples


User Registration Form

<?php
namespace ProcessWire;

$page->route("post:register", function() {
    try {
        $data = request()->validate([
            'username' => 'required|alpha_num|min:3|max:20',
            'email' => 'required|email',
            'password' => 'required|min:8',
            'password_confirm' => 'required',
            'age' => 'required|integer|gte:18',
            'terms' => 'accepted'
        ], [
            'username.required' => 'Please choose a username',
            'terms.accepted' => 'You must accept the terms of service'
        ]);
    } catch (\InvalidArgumentException $e) {
        header('Content-Type: application/json');
        http_response_code(422);
        return json_encode(['errors' => json_decode($e->getMessage(), true)]);
    }

    // Check password match
    if (request()->string('password') !== request()->string('password_confirm')) {
        header('Content-Type: application/json');
        http_response_code(422);
        return json_encode(['errors' => ['password_confirm' => ['Passwords do not match']]]);
    }

    // Get sanitized input
    $username = wire()->sanitizer->pageName(request()->string('username'));
    $email = wire()->sanitizer->email(request()->string('email'));
    $password = request()->string('password');

    // Create user
    $user = new User();
    $user->name = $username;
    $user->email = $email;
    $user->pass = $password;
    $user->save();

    header('Content-Type: application/json');
    http_response_code(201);
    return json_encode([
        'success' => true,
        'user_id' => $user->id,
        'message' => 'Registration successful'
    ]);
});
?>

File Upload API

<?php
namespace ProcessWire;

$page->route("post:upload", function() {
    // Check authentication
    $token = request()->bearerToken();
    if (!$token || !validateToken($token)) {
        header('Content-Type: application/json');
        http_response_code(401);
        return json_encode(['error' => 'Unauthorized']);
    }

    // Validate file
    if (!request()->hasFile('document')) {
        header('Content-Type: application/json');
        http_response_code(400);
        return json_encode(['error' => 'No file uploaded']);
    }

    try {
        $data = request()->validate([
            'document' => 'required|file|mimes:pdf,doc,docx|max_size:10240',
            'title' => 'required|min:3|max:100'
        ]);
    } catch (\InvalidArgumentException $e) {
        header('Content-Type: application/json');
        http_response_code(422);
        return json_encode(['errors' => json_decode($e->getMessage(), true)]);
    }

    // Process upload
    $file = request()->file('document');
    $title = request()->string('title');
    $filename = wire()->sanitizer->filename($file['name']);
    $destination = wire()->config->paths->files . 'uploads/' . $filename;

    if (move_uploaded_file($file['tmp_name'], $destination)) {
        $p = new Page();
        $p->template = 'document';
        $p->parent = wire()->pages->get('/documents/');
        $p->title = $title;
        $p->save();

        header('Content-Type: application/json');
        http_response_code(201);
        return json_encode([
            'success' => true,
            'document_id' => $p->id,
            'filename' => $filename
        ]);
    }

    header('Content-Type: application/json');
    http_response_code(500);
    return json_encode(['error' => 'File upload failed']);
});
?>

Content Negotiation API

<?php
namespace ProcessWire;

$page->route("get:products/{id<integer>}", function($id) {
    $product = wire()->pages->get($id);

    if (!$product->id || $product->template != 'product') {
        http_response_code(404);
        header('Content-Type: application/json');
        return json_encode(['error' => 'Product not found']);
    }

    $data = [
        'id' => $product->id,
        'title' => $product->title,
        'price' => $product->price,
        'description' => $product->description
    ];

    // Content negotiation based on Accept header
    if (request()->acceptsJson()) {
        header('Content-Type: application/json');
        return json_encode($data);
    }

    if (request()->acceptsXml()) {
        header('Content-Type: application/xml');
        return arrayToXml($data);
    }

    // Default to HTML
    return $product->render();
});
?>

Webhook Handler with JSON

<?php
namespace ProcessWire;

$page->route("post:webhooks/github", function() {
    // Verify signature
    $signature = request()->header('X-Hub-Signature-256');
    $payload = file_get_contents('php://input');

    if (!verifyGithubSignature($signature, $payload)) {
        header('Content-Type: application/json');
        http_response_code(401);
        return json_encode(['error' => 'Invalid signature']);
    }

    // Parse JSON payload
    $event = request()->json('action');
    $repository = request()->json('repository');
    $sender = request()->json('sender');

    // Log webhook
    wire()->log->save('webhooks', "GitHub {$event} on {$repository['name']} by {$sender['login']}");

    // Process based on event
    match ($event) {
        'opened' => handlePullRequestOpened(request()->json()),
        'closed' => handlePullRequestClosed(request()->json()),
        default => null
    };

    header('Content-Type: application/json');
    return json_encode(['received' => true]);
});
?>

Best Practices


  1. Always Validate Input: Use the built-in validation system for user data
  2. Use Typed Getters: Prefer integer(), string() over input() for type safety
  3. Sanitize Output: Input is auto-sanitized, but always sanitize for specific contexts
  4. Check Request Type: Use isHtmx(), expectsJson() for appropriate responses
  5. Handle File Uploads Safely: Always validate files and use hasFile() before processing
  6. Use Content Negotiation: Return appropriate format based on Accept headers
  7. Validate Tokens: Always verify bearer tokens before processing authenticated requests
  8. Use Custom Error Messages: Provide user-friendly validation messages
  9. Access PW API via wire(): Inside route handlers, use wire()->pages, wire()->sanitizer, etc.

Security Considerations


Important Security Notes:

  • Input Sanitization: Raw input values are trimmed but not escaped — always use typed getters (string(), integer(), email(), etc.) or ProcessWire's sanitizer to sanitize for your specific output context
  • File Uploads: Always validate file types and sizes. Never trust client-provided filenames
  • Bearer Tokens: Validate and verify tokens on the server side. Don't trust client headers alone
  • IP Detection: Be aware that IPs can be spoofed. Don't rely solely on IP for critical security
  • Rate Limiting: Implement rate limiting for public APIs to prevent abuse
  • HTTPS Only: Always use HTTPS for sensitive data transmission
  • Validation: Server-side validation is mandatory - never trust client-side validation alone

Performance Tips


  • Use Specific Getters: string('key') is faster than input('key') for typed values
  • Validate Only What You Need: Don't validate fields you don't use
  • Check Request Type Early: Use isPost(), isHtmx() early to avoid unnecessary processing
  • Use has() Before Access: Check if input exists before processing to avoid errors

Troubleshooting


Input not being captured:

  • Check request method matches route method
  • Verify form field names match input() keys
  • For JSON payloads, use json() instead of input()
  • Check Content-Type header for JSON requests

Validation not working:

  • Ensure input field names match validation rules
  • Check rule syntax (use pipe | separator)
  • For parameterized rules, use colon: min:5
  • File validation requires actual file upload, not just filename

File uploads failing:

  • Check PHP upload limits (upload_max_filesize, post_max_size)
  • Verify destination directory exists and is writable
  • Use hasFile() to check upload success before processing
  • Check file permissions on the destination folder

Headers not accessible:

  • Some servers don't expose all headers - check apache_request_headers() availability
  • Custom headers may need CORS configuration
  • Authorization headers might be stripped by some server configs

Debugging

// Log all input
wire()->log->save('requests', print_r(request()->input(), true));

// Log headers
wire()->log->save('headers', print_r(request()->headers(), true));

// Check request details
wire()->log->save('request-info', sprintf(
    'Method: %s, HTMX: %s, AJAX: %s, IP: %s',
    request()->getMethod()?->value,
    request()->isHtmx() ? 'yes' : 'no',
    request()->isAjax() ? 'yes' : 'no',
    request()->ip()
));

API Reference


Global Functions

simplerequest(): \SimpleWire\Request\Request  // recommended shorthand
request(): \SimpleWire\Request\Request        // alias

Enums

SimpleWire\Request\HttpMethod   // GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
SimpleWire\Request\AcceptType   // HTML, JSON, XML

HTTP Methods

  • getMethod(): ?HttpMethod - Get request method as enum
  • isMethod(HttpMethod|string $method): bool - Check specific method
  • isGet(): bool
  • isPost(): bool
  • isPut(): bool
  • isDelete(): bool
  • isPatch(): bool
  • isHead(): bool
  • isOptions(): bool
  • isAjax(): bool
  • isHtmx(): bool
  • isBoosted(): bool
  • is(string $key): bool - Generic check

Input Methods

  • input(string $key = ''): mixed
  • string(string $key): ?string
  • integer(string $key): ?int
  • float(string $key): ?float
  • boolean(string $key): bool
  • email(string $key): ?string
  • urlInput(string $key): ?string
  • array(string $key): array
  • date(string $key, string $format = 'Y-m-d'): ?string
  • enum(string $key, string $enumClass): mixed
  • has(array|string $keys): bool
  • hasAny(array $keys): bool
  • filled(array|string $keys): bool
  • anyFilled(array $keys): bool
  • missing(string $key): bool
  • only(array $keys): array
  • except(array|string $keys): array
  • merge(array $input): array
  • mergeIfMissing(array $input): array
  • whenHas(string $key, callable $callback, ?callable $default = null): mixed
  • whenFilled(string $key, callable $callback, ?callable $default = null): mixed

Specific Input Sources

  • query(string $key = ''): mixed
  • post(string $key = ''): mixed
  • cookie(string $key = ''): mixed
  • session(string $key = ''): mixed
  • file(string $key = ''): mixed
  • hasFile(string $key): bool

Headers

  • header(string $key): ?string
  • headers(): array
  • hasHeader(string $key): bool
  • bearerToken(): ?string

Content Negotiation

  • accepts(string|array $contentTypes): bool
  • acceptsJson(): bool
  • acceptsHtml(): bool
  • acceptsXml(): bool
  • prefers(array $contentTypes): ?string
  • preferredFormat(): AcceptType
  • wantsJson(): bool
  • expectsJson(): bool

JSON

  • json(string $key = '', bool $assoc = true): mixed
  • isJson(): bool

URL & Path

  • requestUrl(): string
  • url(): string
  • fullUrl(): string
  • path(): string
  • decodedPath(): string
  • host(): string
  • httpHost(): string
  • schemeAndHttpHost(): string
  • isSecure(): bool

Client Information

  • ip(): ?string
  • ips(): array
  • userAgent(): ?string

Validation

  • validate(array $rules, array $messages = []): array - Throws InvalidArgumentException on failure
  • validated(array $rules, array $messages = []): array - Alias for validate()
  • safe(array $rules, array $messages = []): array - Returns empty array on failure

License


This module is released under the MIT License.

More modules by WireCodex

  • SimpleRouter

    URL routing with pattern matching and caching for ProcessWire.
  • SimpleClient

    Fluent cURL-based HTTP client with retry, file download, and concurrent pool support.
  • SimpleRequest

    HTTP request abstraction with input handling, validation, and content negotiation.
  • SimpleResponse

    HTTP response builder with HTMX support, redirects, and content negotiation.
  • SimpleQuery

    GraphQL-like query engine for ProcessWire pages with caching, rate limiting, and write support.
  • SimpleQueue

    Background job queue with priority, delayed execution, retry, and LazyCron-based processing.
  • SimpleAsset

    Asset management for ProcessWire. Resolves, groups, and renders CSS/JS assets from CDN sources or local paths with cache-busting, SRI, and inline threshold support.

All modules by WireCodex

Install and use modules at your own risk. Always have a site and database backup before installing new modules.