Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to this project will be documented in this file.

## [Unreleased]
### Added
- `returnFileMetadata` option on `FILE_INPUT` and `MULTI_FILE_INPUT` elements (default `false`): when `true`, each entry of the `fileUploadResponse` from `uploadFiles()` and `uploadMultipleFiles()` includes `fileMetadata` (`fileName`, `fileSize`, `fileType`)
- `returnFileMetadata` option on file render elements, passed in the `create()` options like `enableCopy` (default `true`, preserving existing behaviour): set `false` to omit `fileMetadata` from the render response; applies to reveal and composable reveal containers
- `FileUploadMetadata` type export
### Fixed
- `uploadFiles()` on a collect container returned each `fileUploadResponse` entry as a JSON string (visible as escaped quotes); entries are now plain objects, matching the composable container and the documented response. Callers that worked around this with their own `JSON.parse` must remove it.

## [2.8.0] - 2026-9-22
### Added
- Render zip files in composable reveal file elements (list-detail layout with per-file preview), opt-in via `renderFile({ zipRender: true })`
Expand Down
3 changes: 3 additions & 0 deletions core/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ export const formatRevealElementOptions = (options:IRevealElementOptions) => {
if (Object.prototype.hasOwnProperty.call(revealOptions, 'enableCopy') && !validateBooleanOptions(revealOptions.enableCopy)) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BOOLEAN_OPTIONS, ['enableCopy'], true);
}
if (Object.prototype.hasOwnProperty.call(revealOptions, 'returnFileMetadata') && !validateBooleanOptions(revealOptions.returnFileMetadata)) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BOOLEAN_OPTIONS, ['returnFileMetadata'], true);
}
if (Object.prototype.hasOwnProperty.call(revealOptions, 'format')
|| Object.prototype.hasOwnProperty.call(revealOptions, 'translation')) {
const revealElementMask:any[] = [];
Expand Down
3 changes: 3 additions & 0 deletions core/internal/iframe-form/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ export default class IFrameFormElement extends EventEmitter {

preserveFileName: boolean = true;

// Ask the vault for fileMetadata on upload. Off by default (API default too).
returnFileMetadata: boolean = false;

allowedFileType: any;

blockEmptyFiles: boolean = false;
Expand Down
3 changes: 3 additions & 0 deletions core/internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ export default class FrameElement {
if (Object.prototype.hasOwnProperty.call(options, 'preserveFileName')) {
this.iFrameFormElement.preserveFileName = options?.preserveFileName;
}
if (Object.prototype.hasOwnProperty.call(options, 'returnFileMetadata')) {
this.iFrameFormElement.returnFileMetadata = options?.returnFileMetadata;
}
if (Object.prototype.hasOwnProperty.call(options, 'allowedFileType')) {
this.iFrameFormElement.allowedFileType = options?.allowedFileType;
}
Expand Down
7 changes: 7 additions & 0 deletions core/libs/element-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,10 @@ export const formatOptions = (
if (!Object.prototype.hasOwnProperty.call(formattedOptions, 'preserveFileName')) {
formattedOptions = { ...formattedOptions, preserveFileName: true };
}
// Opt-in: the vault only returns file metadata for an upload when asked.
if (!Object.prototype.hasOwnProperty.call(formattedOptions, 'returnFileMetadata')) {
formattedOptions = { ...formattedOptions, returnFileMetadata: false };
}
break;
}

Expand All @@ -404,6 +408,9 @@ export const formatOptions = (
if (Object.prototype.hasOwnProperty.call(formattedOptions, 'preserveFileName') && !validateBooleanOptions(formattedOptions.preserveFileName)) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BOOLEAN_OPTIONS, ['preserveFileName'], true);
}
if (Object.prototype.hasOwnProperty.call(formattedOptions, 'returnFileMetadata') && !validateBooleanOptions(formattedOptions.returnFileMetadata)) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BOOLEAN_OPTIONS, ['returnFileMetadata'], true);
}
if (elementType === ELEMENTS.FILE_INPUT.name || elementType === ELEMENTS.MULTI_FILE_INPUT.name) {
if (options.allowedFileType) {
if (!Array.isArray(options.allowedFileType)) {
Expand Down
21 changes: 21 additions & 0 deletions core/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,21 @@ export interface EventConfig{
vaultID: string,
}

// Metadata of an uploaded file, returned by the vault for every upload because the
// SDK always sends `returnFileMetadata: true`.
export interface IFileUploadMetadata {
fileName: string,
fileSize: number,
fileType: string,
}

// One entry of `fileUploadResponse`: the record id and, when requested with
// `returnFileMetadata: true`, the uploaded file's `fileMetadata`.
export interface IFileUploadRecord {
skyflow_id: string,
fileMetadata?: IFileUploadMetadata,
}

export interface UploadFilesResponse {
fileUploadResponse?: Record<string, any>,
errorResponse?: Record<string, any>,
Expand Down Expand Up @@ -619,6 +634,10 @@ export interface IRevealElementOptions {
enableCopy?: boolean;
format?: string;
translation?:Record<string, string>
// File render elements only. Ask the vault for `fileMetadata` with the render
// response. Default true: the API default is false, but the SDK has always requested
// it, surfaces it as `success.fileMetadata`, and uses it as a fallback for zip detection.
returnFileMetadata?: boolean;
}

// Internal element/frame types — variant-neutral. Relocated from
Expand Down Expand Up @@ -684,6 +703,8 @@ export interface CVVMap {
// without importing a package symbol; the file branch never runs for flowDB.
export interface IFileCollectElementOptions {
preserveFileName?: boolean,
// Include `fileMetadata` in each upload response entry. Default false.
returnFileMetadata?: boolean,
allowedFileType?: string[],
blockEmptyFiles?: boolean,
maxFileSize?: number,
Expand Down
100 changes: 88 additions & 12 deletions packages/skyflow-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1654,11 +1654,18 @@ container.uploadFiles();
{
fileUploadResponse: [
{
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882"
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882",
"fileMetadata": {
"fileName": "kyc.jpeg",
"fileSize": 1024,
"fileType": "application/jpeg"
}
}
]
}
```

`fileMetadata` is included only when the element was created with `returnFileMetadata: true` (the samples above assume it). It describes the stored file: `fileName` is the name saved in the vault (the original name when `preserveFileName` is true, otherwise the generated one), `fileSize` is in bytes, and `fileType` is the MIME type.
### File upload with options:

Along with fileElementInput, you can define other options in the Options object as described below:
Expand Down Expand Up @@ -1754,7 +1761,12 @@ collectContainer.uploadFiles();
{
"fileUploadResponse": [
{
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882"
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882",
"fileMetadata": {
"fileName": "kyc.jpeg",
"fileSize": 1024,
"fileType": "application/jpeg"
}
}
]
}
Expand Down Expand Up @@ -1840,7 +1852,12 @@ collectContainer.uploadFiles();
{
"fileUploadResponse": [
{
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882"
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882",
"fileMetadata": {
"fileName": "kyc.jpeg",
"fileSize": 1024,
"fileType": "application/jpeg"
}
}
]
}
Expand Down Expand Up @@ -2540,7 +2557,12 @@ container.uploadFiles();
{
fileUploadResponse: [
{
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882"
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882",
"fileMetadata": {
"fileName": "kyc.jpeg",
"fileSize": 1024,
"fileType": "application/jpeg"
}
}
]
}
Expand Down Expand Up @@ -2642,7 +2664,12 @@ collectContainer.uploadFiles();
{
"fileUploadResponse": [
{
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882"
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882",
"fileMetadata": {
"fileName": "kyc.jpeg",
"fileSize": 1024,
"fileType": "application/jpeg"
}
}
]
}
Expand Down Expand Up @@ -2731,7 +2758,12 @@ collectContainer.uploadFiles();
{
"fileUploadResponse": [
{
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882"
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882",
"fileMetadata": {
"fileName": "kyc.jpeg",
"fileSize": 1024,
"fileType": "application/jpeg"
}
}
]
}
Expand Down Expand Up @@ -2842,7 +2874,12 @@ element.uploadMultipleFiles();
{
fileUploadResponse: [
{
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882"
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882",
"fileMetadata": {
"fileName": "kyc.jpeg",
"fileSize": 1024,
"fileType": "application/jpeg"
}
}
]
}
Expand All @@ -2854,15 +2891,17 @@ Along with fileElementInput, you can define other options in the Options object
const options = {
allowedFileType: String[], // Optional. Restricts uploads to the listed file extensions (e.g. [".pdf", ".png"]).
blockEmptyFiles: Boolean, // Optional. When true, rejects files with 0 bytes. Default: false.
preserveFileName: Boolean, // Optional. When true, keeps the original filename on upload. Default: false.
preserveFileName: Boolean, // Optional. When true, keeps the original filename on upload. Default: true.
returnFileMetadata: Boolean, // Optional. When true, each upload response entry includes fileMetadata. Default: false.
maxFileSize: Number, // Optional. Maximum size in bytes for each individual file. Default: 32000000 (32 MB).
maxFileCount: Number, // Optional. Maximum number of files that can be selected at once. Must be a positive integer. Default: 4.
}
```

- `allowedFileType`: An array of strings indicating which file extensions are accepted for upload.
- `blockEmptyFiles`: When `true`, files with a size of 0 bytes are rejected.
- `preserveFileName`: When `true`, the original filename is preserved on upload.
- `preserveFileName`: When `true`, the original filename is preserved on upload. Defaults to `true`.
- `returnFileMetadata`: When `true`, the vault returns `fileMetadata` (`fileName`, `fileSize`, `fileType`) for each uploaded file and it is included in the `fileUploadResponse` entry. Defaults to `false`, matching the vault API default. Supported on `FILE_INPUT` and `MULTI_FILE_INPUT` elements in both collect and composable containers.
- `maxFileSize`: Maximum allowed size **per file**, in bytes. If any file exceeds this limit, a validation error is shown with the filename. Defaults to `32000000` (32 MB). Only applies to `MULTI_FILE_INPUT` elements.
- `maxFileCount`: Maximum number of files that can be selected for a single upload. Must be a positive integer. Defaults to `4`. Only applies to `MULTI_FILE_INPUT` elements.

Expand Down Expand Up @@ -2955,7 +2994,12 @@ fileElement.uploadMultipleFiles();
{
"fileUploadResponse": [
{
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882"
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882",
"fileMetadata": {
"fileName": "kyc.jpeg",
"fileSize": 1024,
"fileType": "application/jpeg"
}
}
]
}
Expand Down Expand Up @@ -3043,10 +3087,20 @@ fileElement.uploadMultipleFiles();
{
"fileUploadResponse": [
{
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882"
"skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882",
"fileMetadata": {
"fileName": "kyc.jpeg",
"fileSize": 1024,
"fileType": "application/jpeg"
}
},
{
"skyflow_id": "546eaa6c-5c15-4513-aa15-29f50babe809"
"skyflow_id": "546eaa6c-5c15-4513-aa15-29f50babe809",
"fileMetadata": {
"fileName": "kyc.jpeg",
"fileSize": 1024,
"fileType": "application/jpeg"
}
}
]
}
Expand Down Expand Up @@ -3658,6 +3712,17 @@ const fileElement = {
```
The inputStyles and errorTextStyles parameters accept a styles object as described in the [previous section](https://github.com/skyflowapi/skyflow-js#step-2-create-a-collect-element) for collecting data. But for render file elements, inputStyles accepts only base variant, global style objects.

#### File render options
`create()` accepts an options object as its second argument. For file render elements it supports:

```javascript
const options = {
returnFileMetadata: true, // Optional. When true, the render response includes fileMetadata (fileName, fileSizeKB, fileType). Default: true.
};

const fileElement = container.create(fileElementInput, options);
```

An example of a inputStyles object:

```javascript
Expand Down Expand Up @@ -4279,6 +4344,17 @@ const fileElement = {
```
The inputStyles and errorTextStyles parameters accept a styles object as described in the [previous section](https://github.com/skyflowapi/skyflow-js#step-2-create-a-collect-element) for collecting data. But for render file elements, inputStyles accepts only base variant, global style objects.

#### File render options
`create()` accepts an options object as its second argument. For file render elements it supports:

```javascript
const options = {
returnFileMetadata: true, // Optional. When true, the render response includes fileMetadata (fileName, fileSizeKB, fileType). Default: true.
};

const fileElement = container.create(fileElementInput, options);
```

An example of a inputStyles object:

```javascript
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ <h3>Collect Composable Elements</h3>
blockEmptyFiles: true, // default is false, if set to true, it will block empty files
maxFileSize: 1000000, // default is 32000000 (32MB), if set, it will block files greater than the specified size
maxFileCount: 3, // default is 5, if set, it will block if the number of files exceeds the specified number
returnFileMetadata: false
}

const fileElement = collectContainer.create({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ <h3>Collect Elements</h3>
const options = {
allowedFileType: ["<allowedFileType1>", "<allowedFileType2>"],
blockEmptyFiles: true, // default is false, if set to true, it will block empty files
returnFileMetadata: false,
}

const fileElement = collectContainer.create({
Expand Down
13 changes: 11 additions & 2 deletions packages/skyflow-js/src/api-utils/collect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import omit from 'lodash/omit';
import { getAccessToken } from '@core/utils/bus-events';
import Client from '@core/client';
import {
IFileUploadRecord,
IInsertRecordInput, IInsertRecord,
MessageType, LogLevel,
InsertResponse,
Expand Down Expand Up @@ -156,9 +157,17 @@ export const constructFinalUpdateRecordResponse = (
};
};

export const constructUploadResponse = (response) => {
// Shapes the vault's file upload response for the client. `fileMetadata` is returned
// by the vault when the request carries `returnFileMetadata: true`, which every upload
// path sets; it is passed through untouched ({ fileName, fileSize, fileType }).
export const constructUploadResponse = (response): IFileUploadRecord => {
const data = typeof response === 'string' ? JSON.parse(response) : response;
return JSON.stringify({ skyflow_id: data.skyflowID }) as any;
// Returns a plain object: a stringified entry would reach the caller with escaped
// quotes inside `fileUploadResponse` and force a second JSON.parse.
return {
skyflow_id: data.skyflowID,
...(data.fileMetadata ? { fileMetadata: data.fileMetadata } : {}),
};
};

// keyify / checkDuplicateColumns / constructElementsInsertReq moved to
Expand Down
Loading
Loading