Skip to content

UPLOAD ​

Mark endpoint as a file upload handler.

Keywords ​

@upload, upload

Syntax ​

code
@upload
@upload for <handler_type>

If no handler is specified (only upload annotation without for), then the default handler will be used. The default handler is large_object unless configured otherwise via DefaultUploadHandler setting.

Handler Types ​

There are 4 handler types available:

HandlerKeyDescription
Large Objectlarge_objectStores files using PostgreSQL Large Objects API (default)
File Systemfile_systemStores files on the server file system
CSVcsvParses CSV files and processes rows via PostgreSQL command
ExcelexcelParses Excel files and processes rows via PostgreSQL command

Shared Annotation Options ​

These options are available for all handler types:

OptionTypeDefaultDescription
stop_after_first_successboolfalseStop upload after first successful upload when multiple handlers are used. Subsequent files will have status Ignored.
included_mime_typesstringnullCSV string of MIME type patterns to include. Set to null to allow all.
excluded_mime_typesstringnullCSV string of MIME type patterns to exclude. Set to null to exclude none.
buffer_sizeintnullBuffer size in bytes for raw content uploads (large_object and file_system).
check_textboolfalseValidate file is a text file (not binary). Set to true to accept only text files.
check_imagebool/stringfalseValidate file is an image. Set to true to accept only images, or CSV of allowed types: jpg, png, gif, bmp, tiff, webp.
test_buffer_sizeint4096Buffer size in bytes when checking text files.
non_printable_thresholdint5Maximum non-printable characters allowed in test buffer to consider a valid text file.
check_formatboolfalseValidate the file format before processing. When true and validation fails, the fallback_handler is used if configured.
fallback_handlerstringnullHandler name to delegate to if format validation fails (e.g., large_object, file_system, csv, excel). When a handler's format validation fails and a fallback_handler is configured, processing is automatically delegated to the named handler.

Upload Metadata ​

All handlers return upload metadata as JSON with the following common properties:

PropertyTypeDescription
typestringHandler type used (large_object, file_system, csv, excel)
fileNamestringOriginal uploaded file name
contentTypestringMIME type of the uploaded file
sizeintFile size in bytes
successboolWhether the upload succeeded
statusstringStatus message (e.g., Ok, InvalidMimeType)

Handler-specific properties:

HandlerPropertyTypeDescription
large_objectoidintPostgreSQL Large Object OID
file_systemfilePathstringPath where file was saved

Large Object Handler ​

Default handler that stores files using PostgreSQL Large Objects.

Basic Example ​

sql
sql
create function lo_simple_upload(
    _meta json = null
)
returns json
language plpgsql
as
$$
begin
    return _meta;
end;
$$;

comment on function lo_simple_upload(json) is '
@upload
@param _meta is upload metadata
';

Equivalent as a SQL file endpoint (sql/lo-simple-upload.sql):

sql
sql
/*
HTTP POST
@upload
@param $1 meta is upload metadata
*/
select $1;

With Custom OID Parameter ​

You can specify a custom OID for the large object:

sql
sql
create function lo_custom_parameter_upload(
    _oid bigint,
    _meta json = null
)
returns json
language plpgsql
as
$$
begin
    return _meta;
end;
$$;

comment on function lo_custom_parameter_upload(bigint, json) is '
@upload for large_object
@param _meta is upload metadata
@oid = {_oid}
';

Context Metadata ​

Upload metadata is also available via PostgreSQL context setting:

sql
sql
create function lo_simple_upload_context_metadata()
returns json
language plpgsql
as
$$
begin
    return current_setting('request.upload_metadata', true)::text;
end;
$$;

comment on function lo_simple_upload_context_metadata() is '@upload';

Large Object Annotation Options ​

All shared options plus:

OptionDescription
oidCustom OID for the large object
large_object_included_mime_typesHandler-specific MIME types to include
large_object_excluded_mime_typesHandler-specific MIME types to exclude
large_object_buffer_sizeHandler-specific buffer size
large_object_oidHandler-specific OID (alias for oid)
large_object_check_textHandler-specific text check
large_object_check_imageHandler-specific image check
large_object_test_buffer_sizeHandler-specific test buffer size
large_object_non_printable_thresholdHandler-specific non-printable threshold

File System Handler ​

Stores files on the server file system.

Basic Example ​

sql
sql
create function fs_simple_upload(
    _meta json = null
)
returns json
language plpgsql
as
$$
begin
    return _meta;
end;
$$;

comment on function fs_simple_upload(json) is '
@upload for file_system
@param _meta is upload metadata
';

With Custom Parameters ​

Control the file path, name, and behavior:

sql
sql
create function fs_custom_parameter_upload(
    _path text,
    _file text,
    _unique_name boolean,
    _create_path boolean,
    _meta json = null
)
returns json
language plpgsql
as
$$
begin
    return _meta;
end;
$$;

comment on function fs_custom_parameter_upload(text, text, boolean, boolean, json) is '
@upload for file_system
@param _meta is upload metadata
@path = {_path}
@file = {_file}
@unique_name = {_unique_name}
@create_path = {_create_path}
';

File System Annotation Options ​

All shared options plus:

OptionDescription
pathDirectory path for uploaded file
fileFile name to use
unique_nameGenerate unique file name (bool)
create_pathCreate directory if not exists (bool)
file_system_included_mime_typesHandler-specific MIME types to include
file_system_excluded_mime_typesHandler-specific MIME types to exclude
file_system_buffer_sizeHandler-specific buffer size
file_system_pathHandler-specific path (alias for path)
file_system_fileHandler-specific file name (alias for file)
file_system_unique_nameHandler-specific unique name setting
file_system_create_pathHandler-specific create path setting
file_system_check_textHandler-specific text check
file_system_check_imageHandler-specific image check
file_system_test_buffer_sizeHandler-specific test buffer size
file_system_non_printable_thresholdHandler-specific non-printable threshold

MIME Type Filtering ​

sql
sql
comment on function fs_upload_include_mime_type(json) is '
@upload for file_system
@param _meta is upload metadata
@path = ./test
@file = mime_type.csv
@included_mime_types = image/*, application/*
';

CSV Handler ​

Parses CSV files and processes each row via a PostgreSQL command.

Row Command Function Signature ​

The row command function receives up to 4 parameters:

sql
sql
create function my_csv_row_processor(
    _index int,           -- $1: Row index (1-based)
    _row text[],          -- $2: Parsed row values as text array
    _prev_result any,     -- $3: Result of previous row command (for chaining)
    _meta json            -- $4: Row metadata JSON
)
returns any               -- Return value passed to next row as $3

Row Command Parameters ​

ParameterTypeDescription
$1intRow index (1-based, includes header row)
$2text[]Parsed row values as text array (e.g., _row[1], _row[2], etc.)
$3anyResult of previous row command execution (see below)
$4jsonRow metadata JSON object

Row chaining with $3: The return value from each row command is passed to the next row as $3. For the first row, $3 is NULL. If the row command returns void (no return value), $3 will be NULL for the next row. This enables accumulating values across rows (e.g., counting rows, summing values).

Row Metadata Structure ($4) ​

The metadata JSON passed to each row command contains:

json
json
{
  "type": "csv",
  "fileName": "data.csv",
  "contentType": "text/csv",
  "size": 1234,
  "claims": {                    // Only if RowCommandUserClaimsKey is set (default: "claims")
    "user_id": "1",
    "user_name": "alice",
    "name_identifier": "1"
  }
}
PropertyTypeDescription
typestringHandler type ("csv")
fileNamestringOriginal uploaded file name
contentTypestringMIME type of the file
sizeintFile size in bytes
claimsobjectUser claims (when RowCommandUserClaimsKey is configured)

Note: Unlike Excel, CSV row metadata does NOT include rowIndex. Use the $1 parameter for the row index.

Upload Function Metadata (_meta parameter) ​

The main upload function receives metadata as a JSON array with one element per uploaded file:

json
json
[
  {
    "type": "csv",
    "fileName": "data.csv",
    "contentType": "text/csv",
    "size": 1234,
    "success": true,
    "status": "Ok",
    "lastResult": 100
  }
]
PropertyTypeDescription
lastResultanyFinal return value from the last row command execution

Basic Example ​

sql
sql
-- Table for uploads
create table csv_uploads (
    id int primary key generated always as identity,
    file_name text not null,
    row_index int not null,
    row_data text[] not null
);

-- Row command to process each CSV row
create function csv_upload_row(
    _index int,
    _row text[],
    _prev_result int,
    _meta json
)
returns int
language plpgsql
as $$
begin
    insert into csv_uploads (file_name, row_index, row_data)
    values (_meta->>'fileName', _index, _row);

    return coalesce(_prev_result, 0) + 1;
end;
$$;

-- HTTP POST endpoint
create function csv_upload(_meta json = null)
returns json
language sql
begin atomic;
    select _meta;
end;

comment on function csv_upload(json) is '
@upload for csv
@param _meta is upload metadata
@row_command = select csv_upload_row($1,$2,$3,$4)
';

Accessing User Claims in Row Command ​

With RowCommandUserClaimsKey configured (default: "claims"), user claims are available in the row metadata:

sql
sql
create function csv_upload_row(
    _index int,
    _row text[],
    _prev_result int,
    _meta json
)
returns int
language plpgsql
as $$
begin
    insert into csv_uploads (user_id, file_name, row_index, row_data)
    values (
        (_meta->'claims'->>'user_id')::int,  -- Access user_id from claims
        _meta->>'fileName',
        _index,
        _row
    );
    return coalesce(_prev_result, 0) + 1;
end;
$$;

Using User Context Variables ​

With UseUserContext: true, user context variables are set before upload and accessible via current_setting():

sql
sql
create function csv_upload_row(
    _index int,
    _row text[],
    _prev_result int,
    _meta json
)
returns int
language plpgsql
as $$
begin
    insert into csv_uploads (user_id, file_name, row_index, row_data)
    values (
        current_setting('request.user_id')::int,  -- Access from context
        _meta->>'fileName',
        _index,
        _row
    );
    return coalesce(_prev_result, 0) + 1;
end;
$$;

Custom Delimiters ​

Support multiple delimiter characters:

sql
sql
comment on function csv_upload(json) is '
@upload for csv
@param _meta is upload metadata
@delimiters = ,;
@row_command = select csv_upload_row($1,$2,$3,$4)
';

This will use comma (,) and semicolon (;) as delimiters. Use \t for tab.

CSV Annotation Options ​

All shared options (except buffer_size, check_text, check_image) plus:

OptionTypeDefaultDescription
row_commandstring-PostgreSQL command to process each row (required)
delimitersstring,Delimiter character(s)
check_formatboolfalseValidate file is text before processing
has_fields_enclosed_in_quotesbooltrueFields may be enclosed in quotes
set_white_space_to_nullbooltrueConvert whitespace-only values to NULL

Handler-specific prefixed aliases are also available (e.g., csv_row_command, csv_delimiters).

Excel Handler ​

Parses Excel files (.xlsx, .xls) and processes each row via a PostgreSQL command.

Row Command Function Signature ​

The row command function receives up to 4 parameters:

sql
sql
create function my_excel_row_processor(
    _index int,           -- $1: Row index (1-based, non-empty rows only)
    _row text[],          -- $2: Row values as text array (or json if row_is_json = true)
    _prev_result any,     -- $3: Result of previous row command
    _meta json            -- $4: Row metadata JSON (includes sheet name)
)
returns any               -- Return value passed to next row as $3

Row Command Parameters ​

ParameterTypeDescription
$1intRow index (1-based, only counts non-empty rows)
$2text[] or jsonRow values as text array, or JSON if row_is_json = true
$3anyResult of previous row command execution (see below)
$4jsonRow metadata JSON object (includes sheet info)

Row chaining with $3: The return value from each row command is passed to the next row as $3. For the first row, $3 is NULL. If the row command returns void (no return value), $3 will be NULL for the next row. This enables accumulating values across rows (e.g., counting rows, summing values). Note: When processing multiple sheets (all_sheets = true), $3 resets to NULL at the start of each sheet.

Row Metadata Structure ($4) ​

The metadata JSON passed to each row command contains:

json
json
{
  "type": "excel",
  "fileName": "data.xlsx",
  "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  "size": 5678,
  "sheet": "Sheet1",
  "rowIndex": 5,
  "claims": {                    // Only if RowCommandUserClaimsKey is set
    "user_id": "1",
    "user_name": "alice"
  }
}
PropertyTypeDescription
typestringHandler type ("excel")
fileNamestringOriginal uploaded file name
contentTypestringMIME type of the file
sizeintFile size in bytes
sheetstringCurrent sheet name being processed
rowIndexintExcel row index (1-based, includes empty rows)
claimsobjectUser claims (when RowCommandUserClaimsKey is configured)

Note: Excel row metadata includes rowIndex (actual Excel row number) and sheet name. The $1 parameter is a sequential counter for non-empty rows only, while rowIndex reflects the actual Excel row position.

Upload Function Metadata (_meta parameter) ​

The main upload function receives metadata as a JSON array. When all_sheets = true, there's one element per sheet:

json
json
[
  {
    "type": "excel",
    "fileName": "data.xlsx",
    "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    "size": 5678,
    "sheet": "Sheet1",
    "success": true,
    "rows": 99,
    "result": 100
  },
  {
    "type": "excel",
    "fileName": "data.xlsx",
    "contentType": "...",
    "size": 5678,
    "sheet": "Sheet2",
    "success": true,
    "rows": 50,
    "result": 50
  }
]
PropertyTypeDescription
sheetstringSheet name
rowsintNumber of non-empty rows processed
resultanyFinal return value from the last row command for this sheet

Basic Example ​

sql
sql
-- Table for uploads
create table excel_uploads (
    id int primary key generated always as identity,
    file_name text not null,
    sheet_name text,
    row_index int not null,
    row_data text[] not null
);

-- Row command to process each Excel row
create function excel_upload_row(
    _index int,
    _row text[],
    _prev_result int,
    _meta json
)
returns int
language plpgsql
as $$
begin
    insert into excel_uploads (file_name, sheet_name, row_index, row_data)
    values (
        _meta->>'fileName',
        _meta->>'sheet',
        _index,
        coalesce(_row, '{}')
    );

    return coalesce(_prev_result, 0) + 1;
end;
$$;

-- HTTP POST endpoint
create function excel_upload(_meta json = null)
returns json
language sql
begin atomic;
    select _meta;
end;

comment on function excel_upload(json) is '
@upload for excel
@param _meta is upload metadata
@all_sheets = true
@row_command = select excel_upload_row($1,$2,$3,$4)
';

Row Data as JSON ​

When row_is_json = true, row data is passed as JSON with Excel cell references as keys:

sql
sql
comment on function excel_upload(json) is '
@upload for excel
@param _meta is upload metadata
@row_is_json = true
@row_command = select excel_upload_row($1,$2,$3,$4)
';

The $2 parameter becomes JSON like:

json
json
{"A1": "Name", "B1": "Value", "C1": 123}

Excel Annotation Options ​

All shared options (except buffer_size, check_text, check_image, test_buffer_size, non_printable_threshold) plus:

OptionTypeDefaultDescription
row_commandstring-PostgreSQL command to process each row (required)
sheet_namestringnullSpecific sheet name to process (first sheet if null)
all_sheetsboolfalseProcess all sheets in the workbook
time_formatstringHH:mm:ssFormat for time values
date_formatstringyyyy-MM-ddFormat for date values
datetime_formatstringyyyy-MM-dd HH:mm:ssFormat for datetime values
row_is_jsonboolfalsePass row data as JSON instead of text array

Handler-specific prefixed aliases are also available (e.g., excel_row_command, excel_all_sheets).

Error Handling and Rollback ​

All upload handlers support automatic rollback on error. If the handler function raises an exception, any uploaded data is rolled back:

sql
sql
create function lo_upload_raise_exception(
    _oid bigint,
    _meta json = null
)
returns json
language plpgsql
as
$$
begin
    raise exception 'failed upload';
    return _meta;
end;
$$;

comment on function lo_upload_raise_exception(bigint, json) is '
@upload for large_object
@param _meta is upload metadata
@oid = {_oid}
';

If an exception is raised:

  • Large Object: The large object is deleted
  • File System: The uploaded file is deleted
  • CSV/Excel: All database changes are rolled back

Multiple File Uploads ​

Upload endpoints support multiple files in a single request. The metadata will be returned as a JSON array with one entry per file.

Behavior ​

  • Enables multipart/form-data file uploads
  • Handlers process the uploaded file (storage, validation, parsing)
  • Metadata parameter receives file information (name, size, type)
  • All uploads are transactional - errors trigger rollback
  • See Upload Options for configuration

Custom Parameters ​

Upload handlers accept custom parameters using the @key = value syntax to control file processing behavior per-endpoint.

Shared Parameters ​

ParameterDescription
stop_after_first_successWhen true, stops processing after the first successful upload handler.
included_mime_typesComma-separated list of MIME type patterns to include for upload processing.
excluded_mime_typesComma-separated list of MIME type patterns to exclude from upload processing.
check_formatWhen true, validates the file format before processing. If validation fails, fallback_handler is used.
fallback_handlerHandler name to delegate to if format validation fails (e.g., csv, large_object). Available on all upload handlers.

Large Object Upload Handler ​

ParameterDescription
buffer_size, large_object_buffer_sizeSize of the buffer used for reading/writing large object data.
check_text, large_object_check_textWhen true, checks if the uploaded content is text format.
check_image, large_object_check_imageWhen true, checks if the uploaded content is an image format.
test_buffer_size, large_object_test_buffer_sizeSize of the buffer used for testing file content type.
non_printable_threshold, large_object_non_printable_thresholdThreshold for determining if content contains non-printable characters.
oid, large_object_oidPostgreSQL large object OID to use for storage.
large_object_included_mime_typesMIME type patterns to include for large object upload processing.
large_object_excluded_mime_typesMIME type patterns to exclude from large object upload processing.

Example ​

sql
sql
comment on function upload_to_large_object(text, json) is '
HTTP POST
@upload for large_object
@param _meta is upload metadata
@check_image = true';

File System Upload Handler ​

ParameterDescription
buffer_size, file_system_buffer_sizeSize of the buffer used for reading/writing file system data.
check_text, file_system_check_textWhen true, checks if the uploaded content is text format.
check_image, file_system_check_imageWhen true, checks if the uploaded content is an image format.
test_buffer_size, file_system_test_buffer_sizeSize of the buffer used for testing file content type.
non_printable_threshold, file_system_non_printable_thresholdThreshold for determining if content contains non-printable characters.
path, file_system_pathFile system path where uploaded files will be stored. Supports dynamic placeholders.
file, file_system_fileSpecific file name to use for the uploaded content. Supports dynamic placeholders.
unique_name, file_system_unique_nameWhen true, generates unique file names to avoid conflicts.
create_path, file_system_create_pathWhen true, creates the directory path if it doesn't exist.
file_system_included_mime_typesMIME type patterns to include for file system upload processing.
file_system_excluded_mime_typesMIME type patterns to exclude from file system upload processing.

Example ​

sql
sql
comment on function upload_to_file_system(text, json) is '
HTTP POST
@upload for file_system
@param _meta is upload metadata
@check_image = true
@path = ./public/uploads
@unique_name = true
@create_path = true';

CSV Upload Handler ​

ParameterDescription
test_buffer_size, csv_test_buffer_sizeSize of the buffer used for testing CSV content type.
non_printable_threshold, csv_non_printable_thresholdThreshold for determining if content contains non-printable characters.
check_format, csv_check_formatWhen true, validates the CSV format before processing.
delimiters, csv_delimitersCharacters used as field delimiters in CSV files (e.g., comma, semicolon).
has_fields_enclosed_in_quotes, csv_has_fields_enclosed_in_quotesWhen true, expects CSV fields to be enclosed in quotes.
set_white_space_to_null, csv_set_white_space_to_nullWhen true, converts whitespace-only fields to NULL values.
row_command, csv_row_commandSQL command to execute for each CSV row during processing.
csv_included_mime_typesMIME type patterns to include for CSV upload processing.
csv_excluded_mime_typesMIME type patterns to exclude from CSV upload processing.

Example ​

sql
sql
comment on function csv_upload(json) is '
HTTP POST
@upload for csv
@param _meta is upload metadata
@delimiters = ,;
@row_command = select csv_upload_row($1,$2,$3,$4)';

Excel Upload Handler ​

ParameterDescription
sheet_name, excel_sheet_nameName of the specific Excel worksheet to process.
all_sheets, excel_all_sheetsWhen true, processes all worksheets in the Excel file.
time_format, excel_time_formatFormat string for parsing time values from Excel cells.
date_format, excel_date_formatFormat string for parsing date values from Excel cells.
datetime_format, excel_datetime_formatFormat string for parsing datetime values from Excel cells.
row_is_json, excel_row_is_jsonWhen true, treats each Excel row as JSON data.
row_command, excel_row_commandSQL command to execute for each Excel row during processing.
excel_included_mime_typesMIME type patterns to include for Excel upload processing.
excel_excluded_mime_typesMIME type patterns to exclude from Excel upload processing.

Example ​

sql
sql
comment on function excel_upload(json) is '
HTTP POST
@upload for excel
@param _meta is upload metadata
@all_sheets = true
@row_command = select excel_upload_row($1,$2,$3,$4)';

Blog Posts ​

  • AUTHORIZE - Protect upload endpoint
  • TSCLIENT - Control TypeScript client generation for upload endpoints

See Also ​

Comments