Skip to main content
Learn how to create an image gallery that allows authenticated users to upload and share images. This example demonstrates file upload handling, image storage, and combining authentication with user-generated content.
Image gallery homepage

What You’ll Build

A complete image gallery application with:
  • User authentication (login required to upload)
  • Image upload with validation
  • Image storage using data URLs or file persistence
  • Gallery display using cards
  • Upload size and type restrictions

Database Schema

Create tables for images and user sessions. sqlpage/migrations/0001_images_table.sql
CREATE TABLE image (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    description TEXT,
    image_url TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
sqlpage/migrations/0002_users.sql
CREATE TABLE session (
    id TEXT PRIMARY KEY,
    username TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE users (
    username TEXT PRIMARY KEY,
    password_hash TEXT NOT NULL
);

-- Create default admin user (password: admin)
INSERT INTO users (username, password_hash) 
VALUES ('admin', sqlpage.hash_password('admin'));
index.sql
select 'shell' as component,
    'My image gallery' as title,
    (
        case when sqlpage.cookie('session_token') is null then 'login'
        else 'logout' end
    ) as menu_item;

select 'card' as component,
    'My image gallery' as title;

select title, description, image_url as top_image
from image;

select 'Your gallery is empty' as title,
    'You have not uploaded any images yet. Click the button below to upload a new image.' as description
where not exists (select 1 from image);

select 'button' as component;
select
    'Upload a new image' as title,
    'upload_form.sql' as link,
    'plus' as icon,
    'primary' as color;

How It Works

1

Dynamic Menu

The shell menu changes based on authentication state - showing “login” or “logout” depending on whether a session cookie exists.
2

Card Display

Each image from the database is displayed as a card with:
  • top_image: The image URL shown at the top of the card
  • title: Image title
  • description: Optional description text
3

Empty State

The empty state message only displays when there are no images, using a WHERE clause with NOT EXISTS.

Protected Upload Form

Only authenticated users can access the upload form. upload_form.sql
-- Redirect to login if not authenticated
select 'redirect' as component, '/login.sql' as link
where not exists (
    select true from session 
    where sqlpage.cookie('session_token') = id 
    and created_at > datetime('now', '-1 day')
);

-- Display the upload form
select 'form' as component, 
    'Upload a new image' as title, 
    'upload.sql' as action;
    
select 'text' as type, 'Title' as name, true as required;
select 'text' as type, 'Description' as name;
select 'file' as type, 'Image' as name, 'image/*' as accept;
The accept attribute restricts file selection to image types only. The browser will filter the file picker to show only images.
Upload form

Handling File Uploads

upload.sql
-- Verify authentication
select 'redirect' as component, '/login.sql' as link
where not exists (
    select true from session
    where
        sqlpage.cookie('session_token') = id and
        created_at > datetime('now', '-1 day')
);

-- Validate file type
select 'redirect' as component, '/upload_form.sql' as link
where sqlpage.uploaded_file_mime_type('Image') NOT LIKE 'image/%';

-- Insert the image
insert or ignore into image (title, description, image_url)
values (
    :Title,
    :Description,
    sqlpage.persist_uploaded_file('Image', 'images', 'jpg,jpeg,png,gif')
)
returning 'redirect' as component,
          format('/?created_id=%d', id) as link;

-- Show error if insert failed
select 'alert' as component,
    'red' as color,
    'alert-triangle' as icon,
    'Failed to upload image' as title,
    'Please try again with a smaller picture. Maximum allowed file size is 500Kb.' as description;

File Storage Strategies

SQLPage provides two approaches for handling uploaded files:

File Upload Security

select 'redirect' as component, '/login.sql' as link
where not exists (
    select true from session
    where sqlpage.cookie('session_token') = id
);
Critical: Always verify authentication before processing uploads. Unauthenticated uploads can lead to abuse.
sqlpage.uploaded_file_mime_type('Image') NOT LIKE 'image/%'
Checks the file’s MIME type to ensure it’s actually an image. This prevents:
  • Uploading executable files disguised as images
  • Uploading documents or other non-image content
MIME type validation is not foolproof - it can be spoofed. For production applications, consider additional validation like image parsing or virus scanning.
sqlpage.persist_uploaded_file('Image', 'images', 'jpg,jpeg,png,gif')
The third parameter specifies allowed extensions. SQLPage will reject files with other extensions.
Configure in sqlpage.json:
{
  "max_uploaded_file_size": 524288
}
This sets the maximum upload size to 512KB (524,288 bytes). Adjust based on your needs.

File Upload Functions

sqlpage.uploaded_file_path

Returns the temporary path where the uploaded file is stored before processing.
sqlpage.uploaded_file_path('Image')

sqlpage.uploaded_file_mime_type

Returns the MIME type of the uploaded file.
sqlpage.uploaded_file_mime_type('Image')
-- Returns: 'image/jpeg', 'image/png', etc.

sqlpage.persist_uploaded_file

Saves the file to disk and returns the path.
sqlpage.persist_uploaded_file(
    'fieldname', 
    'directory', 
    'allowed,extensions'
)

sqlpage.read_file_as_data_url

Reads a file and converts it to a data URL for database storage.
sqlpage.read_file_as_data_url('/path/to/file')
-- Returns: 'data:image/jpeg;base64,/9j/4AAQ...'

Complete Application Flow

Project Structure

image-gallery/
├── sqlpage/
│   └── migrations/
│       ├── 0001_images_table.sql
│       └── 0002_users.sql
├── images/                    # Uploaded images stored here
│   ├── abc123.jpg
│   └── def456.png
├── index.sql                  # Gallery display
├── upload_form.sql           # Upload form (protected)
├── upload.sql                # Upload handler
├── login.sql                 # Login page
└── logout.sql                # Logout handler

Configuration

sqlpage.json
{
  "max_uploaded_file_size": 5242880,
  "web_root": ".",
  "database_url": "sqlite://gallery.db"
}
max_uploaded_file_size
integer
default:"10485760"
Maximum upload size in bytes (default 10MB). Set to 5MB in this example.
web_root
string
default:"."
Directory from which SQLPage serves static files. The images/ folder should be within this directory.

Enhancements

Image Thumbnails

Use sqlpage.exec() to call ImageMagick or similar tools to generate thumbnails:
SELECT sqlpage.exec(
    'convert', 
    image_path, 
    '-resize', '200x200', 
    thumbnail_path
);

Image Metadata

Store additional metadata like dimensions, file size, and upload user:
ALTER TABLE image ADD COLUMN 
  uploaded_by TEXT,
  file_size INTEGER,
  width INTEGER,
  height INTEGER;

Delete Functionality

Allow users to delete their own uploads:
DELETE FROM image 
WHERE id = $id 
AND uploaded_by = $current_user
RETURNING 
  'redirect' AS component, 
  '/' AS link;

Image Categories

Add categories or tags for better organization:
CREATE TABLE image_tag (
  image_id INTEGER,
  tag TEXT,
  FOREIGN KEY (image_id) REFERENCES image(id)
);

Production Considerations

1

Storage Limits

Implement storage quotas per user to prevent abuse:
SELECT SUM(file_size) as total_storage 
FROM image 
WHERE uploaded_by = $current_user;
2

CDN Integration

For high-traffic sites, upload to S3/CDN using sqlpage.exec() with AWS CLI or similar tools.
3

Image Optimization

Automatically compress images on upload to reduce storage and bandwidth.
4

Backup Strategy

Regularly backup both database and images directory. Consider using rsync or similar tools.

Full Example

The complete working example with login functionality is available in the SQLPage repository.
Default credentials: username: admin / password: admin

Next Steps

User Authentication

Deep dive into implementing secure authentication

File Upload Reference

Complete documentation of file handling functions

Build docs developers (and LLMs) love