Flick
Guide Validation Services API Pro Examples GitHub
Docs / Services
Flick Pro Service

Tested and maintained, so you don't have to. $99/year.

Get Flick Pro

Upload

Advanced file upload service with automatic image optimization, format conversion, multiple size generation, and cloud storage support.

Why Use This Service?

What you'd build yourself: File validation, MIME type verification, image processing with GD/Imagick, format conversion, multiple size generation, secure filename handling, and S3/DigitalOcean Spaces integration.

Maintained for you: image library quirks and storage API changes get handled here, not in your app.

What Pro provides:

  • Real MIME type validation (not just extensions)
  • Image resizing with fit/cover/contain modes
  • WebP conversion for smaller files
  • Named conversion presets (thumbnail, medium, large)
  • S3 and DigitalOcean Spaces with temporary URLs
  • FileInfo and FileCollection objects for clean API

Installation

Flick Pro requires a license. See Pro installation for setup instructions.

Configuration

Basic configuration requires only a storage directory:

$config = [
    'services' => [
        'upload' => [
            'directory' => '/path/to/uploads',
            'url' => 'https://example.com/uploads'  // Base URL for files
        ]
    ]
];

$form = new Flick($config);

Full Configuration

$config = [
    'services' => [
        'upload' => [
            'directory' => '/path/to/uploads',
            'url' => 'https://example.com/uploads',
            'maxFileSize' => '10MB',  // Global max size
            'timeout' => 60,          // Processing timeout in seconds
            'overwrite' => false,     // Overwrite existing files

            // Predefined image conversions
            'conversions' => [
                'thumbnail' => [
                    'width' => 150,
                    'height' => 150,
                    'resizeMode' => 'cover',
                    'format' => 'webp',
                    'quality' => 80
                ],
                'medium' => [
                    'width' => 600,
                    'height' => 400,
                    'resizeMode' => 'contain',
                    'quality' => 85
                ],
                'large' => [
                    'width' => 1200,
                    'resizeMode' => 'fit',
                    'quality' => 90
                ]
            ]
        ]
    ]
];

Cloud Storage (S3)

Cloud storage needs two packages Flick Pro does not install for you — the AWS SDK they pull in is large, and most sites store files locally:

composer require league/flysystem league/flysystem-aws-s3-v3

Without them, resolving an S3 storage config throws with that same command in the message. Local storage needs nothing extra.

$config = [
    'services' => [
        'upload' => [
            'directory' => '/tmp/uploads',  // Temp directory for processing
            'storage' => [
                'driver' => 's3',
                'bucket' => 'my-bucket',
                'region' => 'us-east-1',
                'key' => $_ENV['AWS_ACCESS_KEY_ID'],
                'secret' => $_ENV['AWS_SECRET_ACCESS_KEY'],
                'acl' => 'public-read',  // public URLs; omit for private objects
                'cdnUrl' => 'https://cdn.example.com'   // optional custom public base URL
            ]
        ]
    ]
];

DigitalOcean Spaces

'storage' => [
    'driver' => 's3',
    'bucket' => 'my-space',
    'region' => 'nyc3',
    'endpoint' => 'https://nyc3.digitaloceanspaces.com',
    'key' => $_ENV['DO_SPACES_KEY'],
    'secret' => $_ENV['DO_SPACES_SECRET'],
    'acl' => 'public-read'
]

Public URLs are built from cdnUrl when set, otherwise from the endpoint (or the standard regional S3 URL). Without 'acl' => 'public-read', objects are stored private and getUrl() returns signed, expiring URLs instead of plain public ones.

Basic Usage

Upload an Image

$form->create('Photo|file');

if ($form->submitted()) {
    $file = $form->upload->image('photo', ['required']);

    if ($file) {
        $form->successMessage("Uploaded: {$file->url}");
    }
}

Upload a PDF

$file = $form->upload->pdf('document', [
    'required',
    'maxFileSize:10MB'
]);

Upload Any File

$file = $form->upload->file('attachment', [
    'required',
    'maxFileSize:5MB',
    'mime:text/plain,application/pdf,application/zip'
]);

Warning

file() requires a mime: rule. Leave it out and the upload is rejected with "A mime type must be specified for file uploads."file() accepts anything, so Flick makes you say what "anything" means. image() and pdf() have their own built-in allowlists and don't need it.

PHP has its own upload limits

maxFileSize can't raise PHP's own ceilings — a stock PHP install caps each file at 2 MB (upload_max_filesize) and the whole request at 8 MB (post_max_size). If uploads fail before Flick ever sees them, raise those two values in php.ini. Multiple files share the post_max_size budget, so five 5 MB files need a 25 MB+ request limit.

Image Processing

Resize Images

$file = $form->upload->image('photo', [
    'width:800',
    'height:600'
]);

Format Conversion

Convert images to WebP or other formats:

$file = $form->upload->image('photo', [
    'format:webp',
    'quality:85'
]);

Resize Modes

Control how images are resized:

// Cover - Fill dimensions, crop excess
$file = $form->upload->image('photo', [
    'width:400',
    'height:400',
    'resizeMode:cover'
]);

// Contain - Canvas is exactly 800x600; image scaled to fit inside, rest padded
$file = $form->upload->image('photo', [
    'width:800',
    'height:600',
    'resizeMode:contain'
]);

// Fit - Scale proportionally; output is the scaled image, no padding (default)
$file = $form->upload->image('photo', [
    'width:1200',
    'resizeMode:fit'
]);

// Stretch - Force exact dimensions, ignoring aspect ratio (may distort)
$file = $form->upload->image('photo', [
    'width:400',
    'height:400',
    'resizeMode:stretch'
]);

cover, contain and stretch need both width and height. fit works with either one on its own.

Preserve Original

Keep the original file alongside the processed version:

$file = $form->upload->image('photo', [
    'width:800',
    'format:webp',
    'preserveOriginal'
]);
// Creates: photo.webp (processed) and photo-original.jpg (original)

Adjustments

Three rules take a value and can be repeated — pass the same rule twice and both adjustments apply, in the order you wrote them.

Rule Description Example
brightness:N Brightness, -100 to 100 brightness:20
contrast:N Contrast, -100 to 100 contrast:15
gamma:N Gamma correction, 1.0 leaves it unchanged gamma:1.2
$file = $form->upload->image('photo', [
    'width:800',
    'brightness:10',
    'contrast:15'
]);

Effects

Rule Description Example
blur:N Blur radius; repeatable blur:5
sharpen:N Sharpen amount; repeatable sharpen:10
grayscale Convert to grayscale grayscale
sepia Sepia tone sepia
invert Invert colors invert
flip:DIRECTION horizontal or vertical flip:horizontal
rotate:DEGREES Rotate by degrees rotate:90
background:COLOR Background behind transparency background:#ffffff

grayscale, sepia and invert are bare flags with no value, and apply once even if repeated.

$file = $form->upload->image('photo', [
    'width:600',
    'grayscale',
    'blur:3'
]);

background accepts a hex color (#ffffff or #fff), an (r, g, b) triple, or one of transparent, white, black, red, green, blue.

Processing order is fixed

Rules are applied in a set order regardless of how you list them: resize, background, then adjustments (brightness, contrast, gamma), then effects (blur, sharpen, grayscale, sepia, invert, flip, rotate). Only repeats of the same rule run in the order you wrote them.

Thumbnails

thumbnails writes extra fixed-size copies next to the processed file, each cropped to fill its dimensions. Sizes are WIDTHxHEIGHT, comma-separated:

$form->upload->image('photo', [
    'width:1200',
    'thumbnails:150x150,300x300'
]);
// Writes photo.jpg, photo_150x150.jpg and photo_300x300.jpg

The thumbnails are written to disk but are not returned on the result object. For sizes you need to reference later — a thumbnail URL to put in a template — use conversions instead, which name each output and hand it back to you.

Conversions

Generate multiple sizes/formats from a single upload using predefined conversions.

Define Conversions in Config

$config = [
    'services' => [
        'upload' => [
            'directory' => '/uploads',
            'conversions' => [
                'thumbnail' => [
                    'width' => 150,
                    'height' => 150,
                    'resizeMode' => 'cover',
                    'format' => 'webp',
                    'quality' => 80
                ],
                'medium' => [
                    'width' => 600,
                    'quality' => 85
                ],
                'large' => [
                    'width' => 1200,
                    'quality' => 90
                ]
            ]
        ]
    ]
];

Use Conversions

// Apply multiple conversions
$files = $form->upload->image('photo', [
    'conversions:thumbnail,medium,large'
]);

// Returns FileCollection with all versions
echo $files->original->url;    // Main processed file
echo $files->thumbnail->url;    // Thumbnail version
echo $files->medium->url;       // Medium version
echo $files->large->url;        // Large version

Single Conversion

Apply a single predefined conversion. The conversion's settings are applied to the uploaded file, and this also returns a FileCollection (not a bare FileInfo), so read the result through ->original:

$files = $form->upload->image('photo', [
    'conversion:thumbnail'
]);

echo $files->original->url;

Override Conversion Settings

$files = $form->upload->image('photo', [
    'conversion:thumbnail',
    'quality:95'  // Override quality from config
]);

Validation Rules

Common Rules

Rule Description Example
required File must be uploaded required
maxFileSize:SIZE Maximum file size maxFileSize:5MB
mimeTypes:TYPES Narrow the handler's allowlist mimeTypes:image/jpeg,image/png
mime:TYPES Allowed MIME types for file() — required there mime:application/zip
blockedMimeTypes:TYPES Reject these types even if otherwise allowed blockedMimeTypes:image/gif
thumbnails:SIZES Extra cropped copies, WxH comma-separated thumbnails:150x150,300x300

mimeTypes and mime are different rules, not aliases. Use mimeTypes with image() or pdf() to narrow what that handler already allows; use mime with file(), which has no allowlist of its own and won't run without one.

Image Rules

Rule Description Example
width:PX Resize width width:800
height:PX Resize height height:600
format:TYPE Output format format:webp
quality:1-100 Output quality quality:85
resizeMode:MODE Resize behavior resizeMode:cover
preserveOriginal Keep original file preserveOriginal
conversion:NAME Apply single conversion conversion:thumbnail
conversions:NAMES Apply multiple conversions conversions:thumb,medium

Resize Modes

Mode Description
fit Scale proportionally to fit within the dimensions; the output canvas is the scaled image itself, so it may be smaller than the box (default)
cover Fill dimensions exactly, crop excess
contain Output canvas is exactly the given dimensions; the image is scaled to fit inside and the leftover space is padded. Requires both width and height
stretch Resize to exact dimensions, ignoring aspect ratio. Requires both width and height

Naming Rules

Each upload is stored in its own subdirectory named after the file — photo.jpg lands at uploads/photo/photo.jpg, with any conversions alongside it. Set 'flat' => true in the upload config to store files directly in the upload directory instead.

Leave the rule off and Flick keeps the uploaded filename, sanitized. Add a name: rule to pick a different strategy:

// Random hash — 12 characters by default
$file = $form->upload->image('photo', ['name:hash']);

// Random hash, custom length
$file = $form->upload->image('photo', ['name:hash,16']);   // 9b483583754b8aa6.jpg

// Today's date
$file = $form->upload->image('photo', ['name:date']);      // 2026-08-08.jpg

// Date and time
$file = $form->upload->image('photo', ['name:time']);      // 2026-08-08-03-21-55.jpg

// Anything else is used literally as the filename
$file = $form->upload->image('photo', ['name:avatar']);    // avatar.jpg
Strategy Result
(rule omitted) The uploaded filename, sanitized — photo.jpg, then photo_1.jpg on collision
name:hash 12 random hex characters
name:hash,N N random hex characters
name:date Y-m-d
name:time Y-m-d-H-i-s
name:anythingelse Used verbatim as the filename

Warning

There's no uuid strategy, and name:original is not special — both fall into the last row and produce files literally called uuid.jpg and original.jpg. The name: rule never sets the extension — the stored extension follows the file's processed format: images are re-encoded, so a format: rule changes the extension to match, and a BMP upload is re-encoded and stored as .jpg.

Predictable names + overwrite

The default naming produces guessable paths (the sanitized upload name, with _1/_2 suffixes on collision). If you also set overwrite => true, one user uploading avatar.png will replace another user's avatar.png. For user-supplied files, use name:hash for unpredictable names, and scope upload directories per user rather than relying on overwrite.

FileInfo Object

Successful uploads return a FileInfo object:

$file = $form->upload->image('photo', ['required']);

if ($file) {
    $file->name;              // Generated filename
    $file->originalFilename;  // Original upload name
    $file->path;              // Storage path
    $file->url;               // Public URL
    $file->size;              // Size in bytes
    $file->mimeType;          // MIME type
    $file->extension;         // File extension
}

FileCollection Object

Conversion rules (conversion: and conversions:) return a FileCollection:

$files = $form->upload->image('photo', [
    'conversions:thumbnail,medium'
]);

// Access by name
$files->original;    // Main processed file (FileInfo)
$files->thumbnail;   // Thumbnail conversion (FileInfo)
$files->medium;      // Medium conversion (FileInfo)
$files->getConversion('original'); // Preserved original if preserveOriginal (FileInfo|null)

// Get all as array (keyed by conversion name)
$allFiles = $files->getAllFiles();

foreach ($allFiles as $name => $fileInfo) {
    echo "{$name}: {$fileInfo->url}\n";
}

getAllFiles() puts the main processed file under the key original. When preserveOriginal is on, the untouched upload appears alongside it under preservedOriginal, and you can also reach it with $files->getConversion('original'). It is not listed by getConversionNames() — that lists conversions only.

Cloud Storage

Temporary URLs

Generate expiring URLs for private files:

// Get URL that expires in 60 minutes
$url = $form->upload->getTemporaryUrl('path/to/file.pdf', 60);

Tip

Only cloud storage can expire a URL. On local storage this returns the plain public URL with no expiry, so the call is safe to leave in place while you develop.

Check Available Conversions

// List all defined conversions
$conversions = $form->upload->getAvailableConversions();

// Check if specific conversion exists
if ($form->upload->hasConversion('thumbnail')) {
    // ...
}

Methods Reference

upload->image()

Upload and process image files.

$form->upload->image(
    string $name,
    array $rules = [],
    array $messages = []
): FileInfo|FileCollection|array|null
Parameter Type Description
$name string Form field name
$rules array Validation/processing rules
$messages array Custom error messages

Allowed MIME types: image/jpeg, image/png, image/gif, image/webp, image/bmp

upload->pdf()

Upload PDF files.

$form->upload->pdf(
    string $name,
    array $rules = [],
    array $messages = []
): FileInfo|FileCollection|array|null

Allowed MIME types: application/pdf

upload->file()

Upload generic files.

$form->upload->file(
    string $name,
    array $rules = [],
    array $messages = []
): FileInfo|FileCollection|array|null

Requires a mime: rule listing the allowed MIME types (see Common Rules) — without one, the upload fails with "A mime type must be specified for file uploads."

upload->getTemporaryUrl()

Generate expiring URL for cloud storage.

$form->upload->getTemporaryUrl(
    string $remotePath,
    int $expiresInMinutes = 60
): string

On failure it returns an empty string and records the reason in the error bag under the upload key. Local storage has no signing mechanism, so it returns the plain file URL.

upload->getAvailableConversions()

Get the names of every conversion defined in your config.

$form->upload->getAvailableConversions(): array
// ['thumbnail', 'medium', 'large']

upload->hasConversion()

Check if a conversion is defined.

$form->upload->hasConversion(string $name): bool

Complete Example

<?php
require 'vendor/autoload.php';

$form = new Flick\Flick([
    'services' => [
        'upload' => [
            'directory' => __DIR__ . '/uploads',
            'url' => 'https://example.com/uploads',
            'maxFileSize' => '10MB',
            'conversions' => [
                'thumbnail' => [
                    'width' => 200,
                    'height' => 200,
                    'resizeMode' => 'cover',
                    'format' => 'webp',
                    'quality' => 80
                ],
                'gallery' => [
                    'width' => 800,
                    'height' => 600,
                    'resizeMode' => 'contain',
                    'format' => 'webp',
                    'quality' => 85
                ]
            ]
        ],
        'sql' => [
            'driver' => 'mysql',
            'host' => 'localhost',
            'database' => 'myapp',
            'username' => 'root',
            'password' => 'secret'
        ]
    ]
]);

$db = $form->sql;

$form->create('Title, Description|textarea, Image|file');

if ($form->submitted()) {
    $request = $form->request('
        Title[required, min:3],
        Description[required, min:10]
    ');

    // Upload image with multiple conversions
    $files = $form->upload->image('image', [
        'required',
        'maxFileSize:5MB',
        'conversions:thumbnail,gallery',
        'preserveOriginal'
    ]);

    if ($form->ok() && $files) {
        // Save to database
        $form->sql->save('posts', [
            'title' => $request['title'],
            'description' => $request['description'],
            'image_original' => $files->getConversion('original')?->path,
            'image_thumbnail' => $files->thumbnail->path,
            'image_gallery' => $files->gallery->path,
            'image_processed' => $files->original->path
        ]);

        $form->successMessage('Post created successfully!');
    }
}

Security Features

  • MIME Type Validation: Actual file type is verified, not just extension
  • File Size Limits: Configurable size limits prevent large uploads
  • Secure File Names: Uploaded filenames are sanitized or replaced
  • Directory Traversal Prevention: Paths are validated and sanitized