Soln SDK
Use studioScriptContext when a Studio script needs to read or write records, inspect form metadata, create file assets, query organization directory data, interact with approval runtime data, or send a message from a mounted workspace app.
This SDK is available in Studio scripts configured from places such as Button Design, Layout Design, approval service calls, and integration flows.
Basic Usage
export default async function main(payload: StudioScriptRuntimeContextPayload, context: StudioScriptContext) {
const created = await context.form.create({
formApiName: 'orders',
title: 'SO-1001',
data: {
orderNo: 'SO-1001',
amount: 1200,
},
});
return {
ok: true,
recordId: created.id,
};
}
payload always uses soln.runtime-context/v1; caller-provided values are under payload.input. Every remote SDK method accepts its documented input as one object argument; methods whose input is optional may be called without arguments. Never pass positional or multiple arguments. Use formApiName for APIs that target a form, and recordId for APIs that target an existing record. For example, update with context.form.update({ recordId, data }). Do not hard-code form IDs in scripts.
SDK responses format form data by field metadata. date fields are returned as YYYY-MM-DD, and datetime fields are returned as YYYY-MM-DD HH:mm:ss.
Every paginated SDK method uses cursor pagination only. Omit cursor for the first request, pass the returned nextCursor to the next request, and keep the remaining query conditions and limit unchanged. Legacy page and pageSize inputs are not accepted. A response is complete when hasMore is false and nextCursor is null.
Client
interface StudioScriptContext {
runtime: StudioScriptRuntimeInfo;
logger: StudioScriptLogger;
http: StudioScriptHttpClient;
file: StudioScriptFileClient;
form: StudioScriptFormClient;
formSavedFilter: StudioScriptFormSavedFilterClient;
formSchema: StudioScriptFormSchemaClient;
formTableView: StudioScriptFormTableViewClient;
approval: StudioScriptApprovalClient;
organization: StudioScriptOrganizationClient;
department: StudioScriptDepartmentClient;
member: StudioScriptMemberClient;
iam: StudioScriptIamClient;
}
type StudioScriptRuntimeInfo = {
tenantId: string;
assetId: string;
assetCode: string;
workspaceAppCode: string | null;
executionId: string;
startedAt: string;
traceId: string;
};
type StudioScriptLogger = {
log: (...args: unknown[]) => void;
info: (...args: unknown[]) => void;
warn: (...args: unknown[]) => void;
error: (...args: unknown[]) => void;
debug: (...args: unknown[]) => void;
};
interface StudioScriptHttpClient {
request(input: StudioScriptHttpRequestInput): Promise<StudioScriptHttpResponse>;
requestJson<TData = unknown>(input: StudioScriptHttpRequestInput): Promise<StudioScriptHttpJsonResponse<TData>>;
}
interface StudioScriptFormClient {
list(input: StudioScriptFormListInput): Promise<StudioScriptFormRecordList>;
find(input: StudioScriptFormFindInput): Promise<StudioScriptFormRecord | null>;
get(input: { recordId: string }): Promise<StudioScriptFormRecord | null>;
batchGet(input: { recordIds: string[] }): Promise<StudioScriptFormBatchGetResult>;
create(input: StudioScriptFormCreateInput): Promise<StudioScriptFormRecord | StudioScriptFormCreateResult>;
createWithChildTables(input: StudioScriptFormCreateWithChildTablesInput): Promise<StudioScriptFormCreateWithChildTablesResult>;
batchCreate(input: StudioScriptFormBatchCreateInput): Promise<StudioScriptFormBatchCreateResult>;
batchCreateWithChildTables(input: StudioScriptFormBatchCreateWithChildTablesInput): Promise<StudioScriptFormBatchCreateWithChildTablesResult>;
batchUpdate(input: StudioScriptFormBatchUpdateInput): Promise<StudioScriptFormBatchUpdateResult>;
updateWithChildTables(input: StudioScriptFormUpdateWithChildTablesInput): Promise<StudioScriptFormUpdateWithChildTablesResult | null>;
batchUpdateWithChildTables(input: StudioScriptFormBatchUpdateWithChildTablesInput): Promise<StudioScriptFormBatchUpdateWithChildTablesResult>;
update(input: StudioScriptFormUpdateInput): Promise<StudioScriptFormRecord | null>;
trash(input: StudioScriptFormDeleteInput): Promise<{ trashed: boolean }>;
batchTrash(input: StudioScriptFormBatchDeleteInput): Promise<StudioScriptFormBatchTrashResult>;
restore(input: StudioScriptFormRestoreInput): Promise<StudioScriptFormRecord | null>;
batchDelete(input: StudioScriptFormBatchDeleteInput): Promise<StudioScriptFormBatchDeleteResult>;
delete(input: StudioScriptFormDeleteInput): Promise<{ deleted: boolean }>;
}
interface StudioScriptFormRecordList {
items: StudioScriptFormRecord[];
limit: number;
projectionAsOf: string | null;
hasMore: boolean;
nextCursor: string | null;
}
interface StudioScriptFormSavedFilterClient {
list(input: { formApiName: string }): Promise<{ items: StudioScriptFormSavedFilter[] }>;
save(input: { formApiName: string; id?: string | null; name: string; condition: Record<string, unknown> }): Promise<StudioScriptFormSavedFilter>;
delete(input: { filterId: string }): Promise<{ id: string }>;
}
interface StudioScriptFormTableViewClient {
list(input: { formApiName: string }): Promise<{ items: StudioScriptFormTableView[] }>;
save(input: {
formApiName: string;
id?: string | null;
name: string;
layoutIds?: string[] | null;
groupFieldIds?: string[] | null;
scopes?: string[] | null;
bordered?: boolean | null;
showRowNumber?: boolean | null;
groups?: StudioScriptFormTableViewGroup[] | null;
}): Promise<StudioScriptFormTableView>;
delete(input: { viewId: string }): Promise<{ id: string }>;
}
interface StudioScriptFormSavedFilter {
id: string;
tenantId: string;
formId: string;
ownerType: string;
ownerId: string;
name: string;
condition: unknown;
sortOrder: number;
createdAt: string;
updatedAt: string;
}
interface StudioScriptFormTableViewGroup {
id: string;
name: string;
fieldIds: string[];
level: 1 | 2 | 3;
parentId: string | null;
}
interface StudioScriptFormTableView {
id: string;
tenantId: string;
formId: string;
ownerType: string;
ownerId: string;
name: string;
layoutIds: string[];
groupFieldIds: string[];
scopes: string[];
bordered: boolean;
showRowNumber: boolean;
groups: StudioScriptFormTableViewGroup[];
sortOrder: number;
createdAt: string;
updatedAt: string;
}
Context clients:
| Client | Type | Description |
|---|---|---|
runtime | StudioScriptRuntimeInfo | Current execution metadata, including tenant, asset, verified workspace app, execution, and trace IDs |
logger | StudioScriptLogger | Writes structured execution logs visible in Studio run output |
http | StudioScriptHttpClient | Sends outbound HTTP requests from server-side Studio runtime |
file | StudioScriptFileClient | Resolves and creates file assets |
form | StudioScriptFormClient | Reads and writes form records |
formSavedFilter | StudioScriptFormSavedFilterClient | Reads and manages saved filters for tenant forms |
formSchema | StudioScriptFormSchemaClient | Reads form and field metadata |
formTableView | StudioScriptFormTableViewClient | Reads and manages table views for tenant forms |
approval | StudioScriptApprovalClient | Submits records into approval and handles approval runtime data |
organization | StudioScriptOrganizationClient | Reads organization profile form data and organization tree |
department | StudioScriptDepartmentClient | Reads department profile form data and department tree |
member | StudioScriptMemberClient | Reads member profile form data |
iam | StudioScriptIamClient | Reads current or specified member role summaries |
Form client methods:
| Method | Input | Return | Description |
|---|---|---|---|
list | StudioScriptFormListInput | StudioScriptFormRecordList | Gets records in batches by query conditions |
find | StudioScriptFormFindInput | StudioScriptFormRecord | null | Single-record query by conditions |
get | { recordId: string } | StudioScriptFormRecord | null | Gets one record by record ID |
batchGet | { recordIds: string[] } | { items: StudioScriptFormRecord[]; total: number } | Gets multiple records by record IDs |
create | StudioScriptFormCreateInput | StudioScriptFormRecord | StudioScriptFormCreateResult | Creates one form record and can optionally submit it into approval |
createWithChildTables | StudioScriptFormCreateWithChildTablesInput | StudioScriptFormCreateWithChildTablesResult | Creates one parent record and child-table records in one transaction |
batchCreate | StudioScriptFormBatchCreateInput | StudioScriptFormBatchCreateResult | Creates multiple form records in one transaction |
batchCreateWithChildTables | StudioScriptFormBatchCreateWithChildTablesInput | StudioScriptFormBatchCreateWithChildTablesResult | Creates multiple parent records and their child-table records in one transaction |
batchUpdate | StudioScriptFormBatchUpdateInput | StudioScriptFormBatchUpdateResult | Updates multiple form records in one transaction |
updateWithChildTables | StudioScriptFormUpdateWithChildTablesInput | StudioScriptFormUpdateWithChildTablesResult | null | Updates one parent record and child-table records in one transaction |
batchUpdateWithChildTables | StudioScriptFormBatchUpdateWithChildTablesInput | StudioScriptFormBatchUpdateWithChildTablesResult | Updates multiple parent records and their child-table records in one transaction |
update | StudioScriptFormUpdateInput | StudioScriptFormRecord | null | Updates one form record |
trash | StudioScriptFormDeleteInput | { trashed: boolean } | Moves one form record to the trash |
batchTrash | StudioScriptFormBatchDeleteInput | StudioScriptFormBatchTrashResult | Moves multiple form records to the trash |
restore | StudioScriptFormRestoreInput | StudioScriptFormRecord | null | Restores one trashed form record and may update title/data/status as part of the restore |
batchDelete | StudioScriptFormBatchDeleteInput | StudioScriptFormBatchDeleteResult | Permanently deletes multiple form records |
delete | StudioScriptFormDeleteInput | { deleted: boolean } | Permanently deletes one form record |
trash is the default business delete operation. It moves a record to the trash and can usually be restored by product flows. delete is a hard delete and should be used only when the business script intentionally removes the record permanently.
Saved filter and table view methods:
| Client | Method | Input | Return | Description |
|---|---|---|---|---|
formSavedFilter | list | { formApiName: string } | { items: StudioScriptFormSavedFilter[] } | Lists saved filters for one form |
formSavedFilter | save | { formApiName: string; id?: string; name: string; condition: object } | StudioScriptFormSavedFilter | Creates or updates one saved filter |
formSavedFilter | delete | { filterId: string } | { id: string } | Deletes one saved filter |
formTableView | list | { formApiName: string } | { items: StudioScriptFormTableView[] } | Lists table views for one form |
formTableView | save | { formApiName: string; id?: string; name: string; layoutIds?; groupFieldIds?; scopes?; bordered?; showRowNumber?; groups? } | StudioScriptFormTableView | Creates or updates one table view. There is no columns input |
formTableView | delete | { viewId: string } | { id: string } | Deletes one table view |
Runtime, Logger, And HTTP
Use context.runtime for execution identifiers, context.logger for logs, and context.http for outbound server-side requests.
export default async function main(payload: StudioScriptRuntimeContextPayload, context: StudioScriptContext) {
context.logger.info('execution', context.runtime.executionId);
const response = await context.http.requestJson({
url: 'https://example.com/api/orders',
method: 'POST',
json: payload,
timeoutMs: 10000,
retry: {
maxAttempts: 2,
},
});
return {
ok: response.ok,
status: response.status,
data: response.data,
traceId: context.runtime.traceId,
};
}
HTTP request input:
type StudioScriptHttpRequestInput = {
url: string;
method?: string;
query?: Record<string, unknown>;
headers?: Record<string, unknown>;
json?: unknown;
body?: unknown;
timeoutMs?: number;
retry?: number | {
maxAttempts?: number;
baseDelayMs?: number;
maxDelayMs?: number;
factor?: number;
retryableStatusCodes?: number[];
};
expect?: 'json' | 'text';
throwOnHttpError?: boolean;
idempotency?: boolean | string | {
key?: string;
headerName?: string;
};
};
type StudioScriptHttpResponse = {
ok: boolean;
status: number;
code: string;
url: string;
method: string;
headers: Record<string, string>;
requestId: string;
traceId: string | null;
idempotencyKey: string | null;
durationMs: number;
attempts: number;
bodyText: string;
bodyJson: unknown;
};
type StudioScriptHttpJsonResponse<TData = unknown> = StudioScriptHttpResponse & {
data: TData | null;
};
requestJson is a convenience wrapper that sets expect: 'json' and returns data as the parsed JSON body.
Choose a logger method by severity:
| Method | Purpose |
|---|---|
context.logger.debug | Temporary execution diagnostics |
context.logger.info / log | Normal execution stages and identifiers |
context.logger.warn | Recoverable or non-blocking abnormal states |
context.logger.error | Failed operations or operations about to return failure |
Do not log secrets, Authorization headers, complete personal profiles, or file contents.
HTTP rules:
- Use
context.httpin server scripts, not directfetch - Set a bounded
timeoutMsfor every external request - Prefer
requestJsonfor JSON APIs andrequestfor text or custom responses - Set
throwOnHttpError: falseand inspectresponse.okwhen the script handles 4xx/5xx itself - Use a stable
idempotencykey for external writes; approval and automation scripts can normally usecontext.runtime.traceId - Do not hard-code credentials in scripts; configure external authentication in the OpenLink integration system
Record Shape
type StudioScriptFormRecord = {
id: string;
tenantId: string;
formId: string;
formName: string;
title: string;
data: Record<string, unknown>;
dataStatus: 'draft' | 'active' | 'archived' | 'trashed';
approvalStatus: 'none' | 'pending' | 'approved' | 'rejected' | 'returned' | 'canceled';
approvalFlowInstanceId: string | null;
createdAt: string;
updatedAt: string;
createdBy?: StudioScriptFormRecordPrincipal;
updatedBy?: StudioScriptFormRecordPrincipal;
};
type StudioScriptFormCreateResult = StudioScriptFormRecord & {
approval: {
queued: true;
submitted: false;
jobId: string;
action: 'create';
record: {
recordId: string;
formId: string;
};
};
};
type StudioScriptFormBatchCreateResult = {
items: Array<StudioScriptFormRecord | StudioScriptFormCreateResult>;
total: number;
};
type StudioScriptFormCreateWithChildTablesResult = {
record: StudioScriptFormRecord | StudioScriptFormCreateResult;
childTables: Array<{
childFormApiName: string;
items: Array<StudioScriptFormRecord | StudioScriptFormCreateResult>;
total: number;
}>;
};
type StudioScriptFormBatchCreateWithChildTablesResult = {
items: StudioScriptFormCreateWithChildTablesResult[];
total: number;
};
type StudioScriptFormRecordPrincipal = {
memberId?: string | null;
departmentId?: string | null;
organizationId?: string | null;
};
Record field values are stored in data and keyed by field API name.
Return And Failure Behavior
Form SDK methods resolve with the documented return type when the operation succeeds. They do not return { ok: false } for business or permission errors.
| Method | Success return | Empty or missing target | Failure behavior |
|---|---|---|---|
list | StudioScriptFormRecordList | items: [], hasMore: false, nextCursor: null | Promise rejects |
find | StudioScriptFormRecord | null | Promise rejects |
create | StudioScriptFormRecord or StudioScriptFormCreateResult | Not applicable | Promise rejects |
createWithChildTables | StudioScriptFormCreateWithChildTablesResult | Not applicable | Promise rejects |
batchCreate | StudioScriptFormBatchCreateResult | Not applicable | Promise rejects |
batchCreateWithChildTables | StudioScriptFormBatchCreateWithChildTablesResult | Not applicable | Promise rejects |
batchUpdate | StudioScriptFormBatchUpdateResult | Not applicable | Promise rejects |
updateWithChildTables | StudioScriptFormUpdateWithChildTablesResult | null when the target parent record is not found | Promise rejects |
batchUpdateWithChildTables | StudioScriptFormBatchUpdateWithChildTablesResult | Not applicable | Promise rejects |
update | StudioScriptFormRecord | null when the target record is not found | Promise rejects |
trash | { trashed: boolean } | { trashed: false } when the target record is not found | Promise rejects |
batchTrash | StudioScriptFormBatchTrashResult | Missing targets are reported in failed | Promise rejects only for invalid input or runtime-level failures |
batchDelete | StudioScriptFormBatchDeleteResult | Missing targets are reported in failed | Promise rejects only for invalid input or runtime-level failures |
delete | { deleted: boolean } | { deleted: false } when the target record is not found | Promise rejects |
When a form SDK call fails, it throws an Error inside the script worker. If the script does not catch it, the whole script execution fails with STUDIO_ASSET_EXECUTION_FAILED, and the execution result's errorMessage is the underlying error message such as IAM_PERMISSION_DENIED or FORMS_SCHEMA_NOT_FOUND.
Use try/catch when the script wants to return a controlled result:
export default async function main(payload: StudioScriptRuntimeContextPayload, context: StudioScriptContext) {
try {
const record = await context.form.create({
formApiName: 'orders',
title: 'SO-1002',
data: {
orderNo: 'SO-1002',
},
});
return {
ok: true,
record,
};
} catch (error) {
return {
ok: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
Record Query
Find First Matching Record
const record = await context.form.find({
formApiName: 'orders',
sortFieldApiName: 'order_amount',
sortDirection: 'DESC',
condition: {
logic: 'and',
children: [
{
field: 'so_order_no',
operator: 'eq',
value: 'SO-1001',
},
],
},
});
Use find with query conditions when you need the first matching record. It uses the same filters as list and always includes detailed record data.
When you already have a record ID, filter by recordId:
const record = await context.form.find({
formApiName: 'orders',
condition: {
logic: 'and',
children: [
{
field: 'recordId',
operator: 'eq',
value: 'record-id',
},
],
},
});
Condition query input:
type StudioScriptFormFindInput = Omit<
StudioScriptFormListInput,
'cursor' | 'limit' | 'includeDetail'
>;
const record = await context.form.find({
formApiName: 'orders',
condition: {
logic: 'and',
children: [
{
field: 'recordId',
operator: 'eq',
value: payload.record?.id,
},
],
},
});
Use recordId in conditions when you already have the target record ID. recordId matches the record's primary ID.
Use sortFieldApiName and sortDirection when multiple records may match and you need to control which record is returned first. Sorting supports number, time, date, datetime, week, month, quarter, year, and serial_number fields.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
condition.children[].field | string | Yes | Use recordId to filter by target form record ID |
condition.children[].operator | 'eq' | 'in' | Yes | Use eq for one record ID or in for multiple record IDs |
condition.children[].value | string | string[] | Yes | Target form record ID value or values |
sortFieldApiName | string | null | No | Field API name used for sorting before returning the first matched record |
sortDirection | 'ASC' | 'DESC' | No | Sort direction. Defaults to DESC |
Get Records By Conditions
const records = await context.form.list({
formApiName: 'orders',
limit: 20,
keyword: 'SO-1001',
includeDetail: true,
});
Input:
type StudioScriptFormListInput = {
formApiName: string;
cursor?: string | null;
limit?: number;
keyword?: string | null;
keywords?: string[] | null;
includeDetail?: boolean;
sortFieldApiName?: string | null;
sortDirection?: 'ASC' | 'DESC' | 'asc' | 'desc' | null;
searchFieldApiName?: string | null;
dataStatuses?: Array<'draft' | 'active' | 'archived' | 'trashed'>;
approvalStatuses?: Array<'none' | 'pending' | 'approved' | 'rejected' | 'returned' | 'canceled'>;
visibilityStates?: Array<{
dataStatus: 'draft' | 'active' | 'archived' | 'trashed';
approvalStatus: 'none' | 'pending' | 'approved' | 'rejected' | 'returned' | 'canceled';
}>;
condition?: StudioScriptFormConditionGroup | null;
};
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
formApiName | string | Yes | Form API name. The runtime resolves it to the tenant-side form ID |
cursor | string | null | No | Opaque nextCursor returned by the previous call. Keep the remaining query conditions unchanged |
limit | number | No | Maximum number of records to return. Defaults to 20; maximum is 200 |
keyword | string | null | No | Single keyword for runtime search |
keywords | string[] | null | No | Multiple keywords for runtime search |
includeDetail | boolean | No | Whether list rows should include detailed record data. Defaults to false |
sortFieldApiName | string | null | No | Field API name used for sorting. Supports number, time, date, datetime, week, month, quarter, year, and serial_number fields |
sortDirection | 'ASC' | 'DESC' | No | Sort direction. Defaults to DESC |
searchFieldApiName | string | null | No | Field API name used as the keyword search target |
dataStatuses | Array<'draft' | 'active' | 'archived' | 'trashed'> | No | Filters by record data status |
approvalStatuses | Array<'none' | 'pending' | 'approved' | 'rejected' | 'returned' | 'canceled'> | No | Filters by approval status |
visibilityStates | Array<object> | No | Filters by combined data status and approval status states |
condition | StudioScriptFormConditionGroup | null | No | Structured field condition |
Supported operators:
| Operator | Meaning |
|---|---|
eq | Equals |
neq | Not equals |
contains | Contains |
not_contains | Does not contain |
gt | Greater than |
gte | Greater than or equal |
lt | Less than |
lte | Less than or equal |
in | In list |
empty | Empty |
not_empty | Not empty |
Response:
type StudioScriptFormRecordList = {
items: StudioScriptFormRecord[];
limit: number;
projectionAsOf: string | null;
hasMore: boolean;
nextCursor: string | null;
};
type StudioScriptFormBatchGetResult = {
items: StudioScriptFormRecord[];
total: number;
};
Read Records By ID
When the record ID is already known, call get directly instead of building a recordId condition for find:
const record = await context.form.get({
recordId: payload.record?.id || '',
});
if (!record) {
return { ok: false, message: 'Record not found.' };
}
get returns the full record or null and enforces the current actor's form, row, and field permissions.
Use batchGet when several record IDs are known. One call accepts at most 100 IDs:
const result = await context.form.batchGet({
recordIds: selectedRecordIds,
});
batchGet returns only readable records. Do not assume its result has the same length as the input. Build a Map by returned record id when correspondence matters.
Traverse All Cursor Pages
Request one page at a time with nextCursor, keeping all other query inputs unchanged:
const items: StudioScriptFormRecord[] = [];
let cursor: string | null = null;
do {
const page = await context.form.list({
formApiName: 'orders',
cursor,
limit: 200,
includeDetail: true,
dataStatuses: ['active'],
sortFieldApiName: 'orderDate',
sortDirection: 'ASC',
});
items.push(...page.items);
cursor = page.hasMore ? page.nextCursor : null;
} while (cursor);
Traverse every page only for explicitly bounded data sets. Use an OpenLink paged flow or data-processing job for large synchronization work instead of performing an unbounded read in one Studio script.
Record Creation
Create A Single Record
const record = await context.form.create({
formApiName: 'orders',
title: 'SO-1002',
data: {
orderNo: 'SO-1002',
status: 'draft',
},
submitApproval: true,
});
Input:
type StudioScriptFormCreateInput = {
formApiName: string;
title: string;
data: Record<string, unknown>;
buttonKey?: string | null;
submitApproval?: boolean;
};
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
formApiName | string | Yes | Form API name. The runtime resolves it to the tenant-side form ID |
title | string | Yes | Record title used by the runtime list and display layer |
data | Record<string, unknown> | Yes | Field values keyed by field API name. Must be an object |
buttonKey | string | null | No | Optional button key passed to the runtime write chain |
submitApproval | boolean | No | When true, creates the record and queues an approval submission job in the same transaction |
When submitApproval is true, the returned record includes an approval field with the queued approval submission jobId. The background approval submission job uses action create, and the approval runtime only uses a published approval definition whose create trigger is enabled. If queueing the job fails, the SDK call rejects and the record creation is rolled back.
Batch Create Records
const result = await context.form.batchCreate({
formApiName: 'orders',
records: [
{
title: 'SO-1003',
data: {
orderNo: 'SO-1003',
status: 'draft',
},
},
{
title: 'SO-1004',
data: {
orderNo: 'SO-1004',
status: 'draft',
},
submitApproval: true,
},
],
});
Input:
type StudioScriptFormBatchCreateInput = {
formApiName: string;
records: Array<{
title: string;
data: Record<string, unknown>;
buttonKey?: string | null;
submitApproval?: boolean;
}>;
};
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
formApiName | string | Yes | Form API name. The runtime resolves it to the tenant-side form ID |
records | Array<object> | Yes | Batch data to create. Each batch must contain 1 to 100 records |
records[].title | string | Yes | Record title used by the runtime list and display layer |
records[].data | Record<string, unknown> | Yes | Field values keyed by field API name. Must be an object |
records[].buttonKey | string | null | No | Optional button key passed to the runtime write chain |
records[].submitApproval | boolean | No | When true, queues an approval submission job for that created record in the same transaction |
Response:
type StudioScriptFormBatchCreateResult = {
items: Array<StudioScriptFormRecord | StudioScriptFormCreateResult>;
total: number;
};
batchCreate creates all records in one transaction. If any record fails validation, permission checks, auto-number allocation, file reference handling, formula calculation, post-create job enqueueing, or approval job enqueueing, the SDK call rejects and all records in the batch are rolled back.
Create Parent And Child Table Records
const result = await context.form.createWithChildTables({
formApiName: 'orders',
title: 'SO-1005',
data: {
orderNo: 'SO-1005',
status: 'draft',
},
childTables: [
{
childFormApiName: 'order_items',
records: [
{
title: 'SO-1005-1',
data: {
productName: 'Keyboard',
quantity: 2,
},
},
{
title: 'SO-1005-2',
data: {
productName: 'Mouse',
quantity: 1,
},
},
],
},
],
});
Input:
type StudioScriptFormCreateWithChildTablesInput = {
formApiName: string;
title: string;
data: Record<string, unknown>;
buttonKey?: string | null;
submitApproval?: boolean;
childTables: Array<{
childFormApiName: string;
records: Array<{
title: string;
data: Record<string, unknown>;
buttonKey?: string | null;
submitApproval?: boolean;
}>;
}>;
};
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
formApiName | string | Yes | Parent form API name. The runtime resolves it to the tenant-side parent form ID |
title | string | Yes | Parent record title used by the runtime list and display layer |
data | Record<string, unknown> | Yes | Parent field values keyed by field API name. Must be an object |
childTables | Array<object> | Yes | Child-table batches to create. Must contain at least one child table |
childTables[].childFormApiName | string | Yes | Child form API name. It must be a child table of the parent form |
childTables[].records | Array<object> | Yes | Child-table batch data. Each child-table batch must contain 1 to 100 records |
childTables[].records[].title | string | Yes | Child record title used by the runtime list and display layer |
childTables[].records[].data | Record<string, unknown> | Yes | Child field values keyed by field API name. Do not include the parent relation field; the SDK fills it with the created parent record ID |
childTables[].records[].buttonKey | string | null | No | Optional button key passed to the runtime write chain |
childTables[].records[].submitApproval | boolean | No | When true, queues an approval submission job for that created child record in the same transaction |
Response:
type StudioScriptFormCreateWithChildTablesResult = {
record: StudioScriptFormRecord | StudioScriptFormCreateResult;
childTables: Array<{
childFormApiName: string;
items: Array<StudioScriptFormRecord | StudioScriptFormCreateResult>;
total: number;
}>;
};
createWithChildTables first creates the parent record, then creates each child record with the parent relation field set to the created parent record ID. The whole operation runs in one transaction. If the parent record, any child-table lookup, any child record, post-create job enqueueing, or approval job enqueueing fails, the SDK call rejects and the whole operation is rolled back.
Batch Create Parent And Child Table Records
const result = await context.form.batchCreateWithChildTables({
formApiName: 'orders',
records: [
{
title: 'SO-1006',
data: {
orderNo: 'SO-1006',
status: 'draft',
},
childTables: [
{
childFormApiName: 'order_items',
records: [
{
title: 'SO-1006-1',
data: {
productName: 'Keyboard',
quantity: 2,
},
},
],
},
],
},
{
title: 'SO-1007',
data: {
orderNo: 'SO-1007',
status: 'draft',
},
childTables: [
{
childFormApiName: 'order_items',
records: [
{
title: 'SO-1007-1',
data: {
productName: 'Mouse',
quantity: 1,
},
},
],
},
],
},
],
});
Input:
type StudioScriptFormBatchCreateWithChildTablesInput = {
formApiName: string;
records: Array<{
title: string;
data: Record<string, unknown>;
buttonKey?: string | null;
submitApproval?: boolean;
childTables: Array<{
childFormApiName: string;
records: Array<{
title: string;
data: Record<string, unknown>;
buttonKey?: string | null;
submitApproval?: boolean;
}>;
}>;
}>;
};
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
formApiName | string | Yes | Parent form API name. The runtime resolves it to the tenant-side parent form ID |
records | Array<object> | Yes | Parent batch data to create. Each batch must contain 1 to 100 parent records |
records[].title | string | Yes | Parent record title used by the runtime list and display layer |
records[].data | Record<string, unknown> | Yes | Parent field values keyed by field API name. Must be an object |
records[].childTables | Array<object> | Yes | Child-table batches for this parent record. Must contain at least one child table |
records[].childTables[].childFormApiName | string | Yes | Child form API name. It must be a child table of the parent form |
records[].childTables[].records | Array<object> | Yes | Child-table batch data. Each child-table batch must contain 1 to 100 records |
Response:
type StudioScriptFormBatchCreateWithChildTablesResult = {
items: StudioScriptFormCreateWithChildTablesResult[];
total: number;
};
batchCreateWithChildTables is for import-style scenarios where each parent row has its own child-table rows. The whole batch runs in one transaction. If any parent record, child-table lookup, child record, post-create job enqueueing, or approval job enqueueing fails, the SDK call rejects and the whole batch is rolled back.
Update Records
Update One Record
const updated = await context.form.update({
recordId: record.id,
data: {
status: 'confirmed',
},
});
Input:
type StudioScriptFormUpdateInput = {
recordId: string;
title?: string;
data?: Record<string, unknown>;
buttonKey?: string | null;
submitApproval?: boolean;
};
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
recordId | string | Yes | Target form record ID |
title | string | No | New record title. Omit it to keep the current title |
data | Record<string, unknown> | No | Field values to merge into the existing record data |
buttonKey | string | null | No | Optional button permission key. When provided, button execution permission is checked |
submitApproval | boolean | No | When true, queues an approval submission job for this updated record in the same transaction |
data is merged into the existing record data. Read-only calculated fields such as summary and dynamic formula fields are ignored by the existing form-data write flow.
Batch Update Records
const result = await context.form.batchUpdate({
records: [
{
recordId: 'record-id-1',
data: {
status: 'confirmed',
},
},
{
recordId: 'record-id-2',
title: 'Updated Title',
data: {
status: 'approved',
},
submitApproval: true,
},
],
});
Input:
type StudioScriptFormBatchUpdateInput = {
records: StudioScriptFormUpdateInput[];
};
Return:
type StudioScriptFormBatchUpdateResult = {
items: StudioScriptFormRecord[];
total: number;
};
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
records | StudioScriptFormUpdateInput[] | Yes | Records to update. Minimum 1, maximum 100 |
batchUpdate runs in one transaction. If any record fails validation, permission checks, auto-generated field recalculation, post-update job enqueueing, or approval job enqueueing, the SDK call rejects and all updates in the batch are rolled back.
Update Parent And Child Records
const result = await context.form.updateWithChildTables({
recordId: 'parent-record-id',
title: 'SO-1005',
data: {
status: 'confirmed',
},
submitApproval: true,
childTables: [
{
childFormApiName: 'order_items',
records: [
{
recordId: 'child-record-id-1',
data: {
quantity: 2,
},
},
{
recordId: 'child-record-id-2',
data: {
quantity: 1,
},
submitApproval: true,
},
],
},
],
});
Input:
type StudioScriptFormChildTableUpdateInput = {
childFormApiName: string;
records: StudioScriptFormUpdateInput[];
};
type StudioScriptFormUpdateWithChildTablesInput = StudioScriptFormUpdateInput & {
childTables: StudioScriptFormChildTableUpdateInput[];
};
Return:
type StudioScriptFormUpdateWithChildTablesResult = {
record: StudioScriptFormRecord;
childTables: Array<{
childFormApiName: string;
items: StudioScriptFormRecord[];
total: number;
}>;
};
updateWithChildTables updates one parent record and the specified child-table records in one transaction. Child rows are updated by recordId; the SDK does not create missing child rows in this API. If the parent record is missing, the method returns null. If any parent update, child-table lookup, child update, post-update job enqueueing, or approval job enqueueing fails, the SDK call rejects and the whole transaction is rolled back.
Batch Update Parent And Child Records
const result = await context.form.batchUpdateWithChildTables({
records: [
{
recordId: 'parent-record-id-1',
data: {
status: 'confirmed',
},
childTables: [
{
childFormApiName: 'order_items',
records: [
{
recordId: 'child-record-id-1',
data: {
quantity: 3,
},
},
],
},
],
},
{
recordId: 'parent-record-id-2',
data: {
status: 'approved',
},
submitApproval: true,
childTables: [
{
childFormApiName: 'order_items',
records: [
{
recordId: 'child-record-id-2',
data: {
quantity: 5,
},
submitApproval: true,
},
],
},
],
},
],
});
Input:
type StudioScriptFormBatchUpdateWithChildTablesInput = {
records: Array<StudioScriptFormUpdateInput & {
childTables: StudioScriptFormChildTableUpdateInput[];
}>;
};
Return:
type StudioScriptFormBatchUpdateWithChildTablesResult = {
items: StudioScriptFormUpdateWithChildTablesResult[];
total: number;
};
batchUpdateWithChildTables updates multiple parent records and their child-table records in one transaction. Every parent and child update still follows the same permission checks, auto-generated field recalculation rules, post-update job enqueueing, and optional approval job enqueueing as the single-record APIs. If any parent record is missing or any update step fails, the SDK call rejects and the whole batch is rolled back.
Trash Or Delete A Record
await context.form.trash({
recordId: record.id,
submitApproval: true,
});
await context.form.delete({
recordId: record.id,
});
const trashResult = await context.form.batchTrash({
recordIds: selectedRecordIds,
});
const deleteResult = await context.form.batchDelete({
recordIds: staleRecordIds,
});
Input:
type StudioScriptFormDeleteInput = {
recordId: string;
buttonKey?: string | null;
submitApproval?: boolean;
};
type StudioScriptFormRestoreInput = {
recordId: string;
buttonKey?: string | null;
title?: string;
data?: Record<string, unknown>;
dataStatus?: 'draft' | 'active' | 'archived';
};
type StudioScriptFormBatchDeleteInput = {
recordIds: string[];
buttonKey?: string | null;
submitApproval?: boolean;
};
type StudioScriptFormBatchTrashResult = {
requestedCount: number;
trashedCount: number;
trashedRecordIds: string[];
failed: Array<{
recordId: string;
code: string;
}>;
};
type StudioScriptFormBatchDeleteResult = {
requestedCount: number;
deletedCount: number;
deletedRecordIds: string[];
failed: Array<{
recordId: string;
code: string;
}>;
};
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
recordId | string | Yes | Target form record ID |
recordIds | string[] | Yes | Target form record IDs for batchTrash or batchDelete. Minimum 1, maximum 100 |
buttonKey | string | null | No | Optional button permission key. When provided, button execution permission is checked |
submitApproval | boolean | No | For trash and batchTrash, when true, tries to submit delete approval first and falls back to direct trash when no delete approval matches. For delete and batchDelete, true is not supported and returns FORM_DATA_DELETE_APPROVAL_NOT_SUPPORTED |
Batch methods return partial results. Records that cannot be moved or deleted are listed in failed with the runtime error code, while successful records are listed in trashedRecordIds or deletedRecordIds.
Prefer trash for normal business flows. Use trash or batchTrash with submitApproval: true when the script explicitly needs delete approval. Use delete only for scripts that intentionally perform a hard delete.
Restore A Trashed Record
Use restore for a soft-deleted record. It can update the title, field data, and restored data status in the same operation:
const restored = await context.form.restore({
recordId: trashedRecordId,
dataStatus: 'active',
data: {
restoreReason: 'Confirmed by operations',
},
});
| Parameter | Type | Required | Description |
|---|---|---|---|
recordId | string | Yes | ID of the record in the trash |
buttonKey | string | null | No | Optional product button key whose execution permission should be enforced |
title | string | No | New title applied during restore |
data | Record<string, unknown> | No | Field values merged into the restored record |
dataStatus | 'draft' | 'active' | 'archived' | No | Data status after restore |
restore only applies to soft-deleted records. A permanently deleted record cannot be restored.
Saved Filter Client
context.formSavedFilter manages personal saved filters owned by the current runtime actor. It does not create tenant-wide shared filters or modify another member's filters.
const saved = await context.formSavedFilter.save({
formApiName: 'orders',
name: 'High-value orders awaiting action',
condition: {
logic: 'and',
children: [
{ field: 'status', operator: 'eq', value: 'pending' },
{ field: 'amount', operator: 'gte', value: 10000 },
],
},
});
condition must be a root condition group and uses field API names. Omit id to create a filter. Pass an existing filter's real id to update it:
const filters = await context.formSavedFilter.list({
formApiName: 'orders',
});
const current = filters.items.find(
(item) => item.name === 'High-value orders awaiting action',
);
if (current) {
await context.formSavedFilter.save({
formApiName: 'orders',
id: current.id,
name: current.name,
condition: {
logic: 'and',
children: [{ field: 'status', operator: 'eq', value: 'pending' }],
},
});
}
Delete with filterId. The result is { id }, not { deleted }:
await context.formSavedFilter.delete({ filterId: saved.id });
Table View Client
context.formTableView manages personal table views owned by the current runtime actor. It stores layout, grouping, and display preferences without modifying form design or another member's views.
const view = await context.formTableView.save({
formApiName: 'orders',
name: 'Grouped by customer',
layoutIds: [listLayoutId],
groupFieldIds: [customerFieldId],
scopes: ['list'],
bordered: true,
showRowNumber: true,
groups: [
{
id: 'customer-group',
name: 'Customer',
fieldIds: [customerFieldId],
level: 1,
parentId: null,
},
],
});
| Parameter | Description |
|---|---|
formApiName | Target form API name, not an internal form ID |
id | Existing view ID when updating; omit when creating |
layoutIds | Layout IDs used by the view |
groupFieldIds | Field IDs used for grouping, not field API names |
scopes | list, detail, new, edit, or relation; defaults to list |
bordered | Whether table borders are shown |
showRowNumber | Whether row numbers are shown |
groups | Up to three group levels, referencing field IDs through fieldIds |
There is no columns input. Resolve real layout and field IDs from form configuration; do not substitute API names for IDs.
const views = await context.formTableView.list({ formApiName: 'orders' });
await context.formTableView.delete({ viewId: view.id });
Delete returns { id }. All three operations are scoped to views owned by the current runtime actor.
Form Schema Client
Use studioScriptContext.formSchema when a script needs form or field metadata before reading or writing records.
interface StudioScriptFormSchemaClient {
get(input: StudioScriptFormSchemaGetInput): Promise<StudioScriptFormSchema>;
fields(input: StudioScriptFormSchemaFieldsInput): Promise<StudioScriptFormFieldList>;
field(input: StudioScriptFormSchemaFieldInput): Promise<StudioScriptFormField | null>;
}
Client methods:
| Method | Input | Return | Description |
|---|---|---|---|
get | StudioScriptFormSchemaGetInput | StudioScriptFormSchema | Gets form metadata by form API name |
fields | StudioScriptFormSchemaFieldsInput | StudioScriptFormFieldList | Lists fields for a form, with an optional keyword filter |
field | StudioScriptFormSchemaFieldInput | StudioScriptFormField | null | Gets one field by form API name and field API name |
Get Form Metadata
const form = await context.formSchema.get({
formApiName: 'orders',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
formApiName | string | Yes | Form API name. The runtime resolves and returns the accessible form metadata |
Form Shape
type StudioScriptFormSchema = {
id: string;
name: string;
apiName: string;
type: string;
description: string | null;
icon: string | null;
isDefault: boolean;
enabled: boolean;
};
List Fields
const fields = await context.formSchema.fields({
formApiName: 'orders',
keyword: 'amount',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
formApiName | string | Yes | Form API name |
keyword | string | null | No | Optional keyword used to filter fields by name or API name |
Get One Field
const amountField = await context.formSchema.field({
formApiName: 'orders',
fieldApiName: 'amount',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
formApiName | string | Yes | Form API name |
fieldApiName | string | Yes | Field API name to resolve within the target form |
Field Shape
type StudioScriptFormField = {
id: string;
formId: string;
groupId: string | null;
name: string;
apiName: string;
type: string;
dataType: string;
sortOrder: number;
required: boolean;
readonly: boolean;
disabled: boolean;
hidden: boolean;
isDefault: boolean;
isIdentifier: boolean;
defaultValue: unknown;
options: unknown;
validation: unknown;
display: unknown;
specialConfig: unknown;
config: unknown;
};
Field metadata is filtered by the current actor's form and field metadata permissions.
Built-in form, field, and option labels are returned as fallback English display text in the Studio SDK. Product pages still translate the stored i18n: keys with the current page locale.
File Client
Use studioScriptContext.file when a script needs to resolve file assets from form file fields or create a new file asset.
interface StudioScriptFileClient {
get(input: StudioScriptFileGetInput): Promise<StudioScriptFileAsset | null>;
resolve(input: StudioScriptFileResolveInput): Promise<StudioScriptFileAsset[]>;
create(input: StudioScriptFileCreateInput): Promise<StudioScriptFileAsset>;
}
Client methods:
| Method | Input | Return | Description |
|---|---|---|---|
get | StudioScriptFileGetInput | StudioScriptFileAsset | null | Gets one file asset by file ID |
resolve | StudioScriptFileResolveInput | StudioScriptFileAsset[] | Resolves multiple file assets by file IDs |
create | StudioScriptFileCreateInput | StudioScriptFileAsset | Creates one file asset from base64 content |
Use file.previewUrl || file.url when a script needs a display URL for an existing file asset. previewUrl is available only for file types that the current preview viewer supports, such as common images, PDFs, text/code files, and common audio/video files. Files that are valid for upload and download can still have previewUrl: null.
Get One File
const file = await context.file.get({
fileId: '6d4b0e70-8a36-4c11-a6f2-6ad1a7f6aa01',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
fileId | string | Yes | File asset ID |
Resolve Multiple Files
const files = await context.file.resolve({
fileIds: ['file-id-1', 'file-id-2'],
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
fileIds | string[] | Yes | File asset IDs to resolve |
Create A File
const csv = 'orderNo,amount\\nSO-1001,1200\\n';
const file = await context.file.create({
name: 'orders.csv',
mimeType: 'text/csv',
size: csv.length,
contentBase64: btoa(csv),
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | File display name |
originalName | string | No | Original file name. Defaults to name |
mimeType | string | Yes | MIME type, such as text/csv or application/pdf |
size | number | Yes | Expected file size in bytes |
contentBase64 | string | Yes | File content encoded as base64 |
storagePolicyKey | string | null | No | Optional storage policy key. Omit it to use the default policy |
File APIs are scoped to the current tenant. They do not expose a hard-delete operation to Studio scripts.
Approval Client
Use studioScriptContext.approval when a script needs to submit records into approval, inspect approval instances, or act on the current actor's approval tasks.
type StudioScriptApprovalRecordListInput = {
cursor?: string | null;
limit?: number;
status?: StudioScriptApprovalStatus | null;
definitionId?: string | null;
initiatorMemberId?: string | null;
currentApproverMemberId?: string | null;
startedAtFrom?: string | null;
startedAtTo?: string | null;
keyword?: string | null;
};
type StudioScriptApprovalRecordListItem = {
id: string;
definitionId: string;
definitionName: string;
recordId: string;
recordTitle: string;
formApiName: string;
formName: string;
initiatorType: string;
initiatorId: string;
initiatorName: string;
initiatorDisplayName: string | null;
currentApproverPrincipals: Array<{ principalType: string; principalId: string; displayName: string | null }>;
taskIds: string[];
currentNodeName: string;
currentApproverName: string;
taskStatus: string;
instanceStatus: string;
instanceResult: string | null;
startedAt: string;
completedAt: string | null;
};
type StudioScriptApprovalRecordListResult = {
items: StudioScriptApprovalRecordListItem[];
hasMore: boolean;
nextCursor: string | null;
limit: number;
};
interface StudioScriptApprovalClient {
getSubmissionConfig(input: { formApiName: string; action?: 'create' | 'update' | 'delete' }): Promise<{
enabled: boolean;
allowWithdraw: boolean;
definitionId: string | null;
definitionVersionId: string | null;
definitionName: string | null;
}>;
submitRecord(input: { recordId: string; action?: 'create' | 'update' | 'delete'; beforeRecordData?: Record<string, unknown> | null }): Promise<StudioScriptApprovalSubmitRecordResult>;
listRecords(input?: StudioScriptApprovalRecordListInput): Promise<StudioScriptApprovalRecordListResult>;
listRecordInstances(input: { recordId: string }): Promise<StudioScriptApprovalRecordInstanceSummary[]>;
getInstance(input: { instanceId: string }): Promise<StudioScriptApprovalInstance | null>;
listTasks(input?: { cursor?: string | null; limit?: number; status?: string | null; memberId?: string | null; timeRange?: { from: string; to: string } | null }): Promise<{ items: StudioScriptApprovalTaskListItem[]; hasMore: boolean; nextCursor: string | null; limit: number; timeRange: { from: string; to: string } }>;
getTask(input: { taskId: string }): Promise<StudioScriptApprovalTaskDetail | null>;
decideTask(input: { taskId: string; decision: 'approved' | 'rejected' | 'returned'; comment?: string | null; returnTarget?: StudioScriptApprovalReturnTarget | null }): Promise<StudioScriptApprovalTaskDecisionResult>;
withdraw(input: { instanceId: string; comment?: string | null }): Promise<{
instance: StudioScriptApprovalInstance;
record: {
recordId: string;
formId: string;
dataStatus: string;
approvalStatus: string;
approvalFlowInstanceId: string | null;
} | null;
}>;
}
Client methods:
| Method | Input | Return | Description |
|---|---|---|---|
getSubmissionConfig | { formApiName: string; action?: 'create' | 'update' | 'delete' } | { enabled; allowWithdraw; definitionId; definitionVersionId; definitionName } | Gets approval submission configuration for a form action |
submitRecord | { recordId: string; action?: 'create' | 'update' | 'delete'; beforeRecordData?: Record<string, unknown> | null } | StudioScriptApprovalSubmitRecordResult | Submits a record into approval and returns a compact submission summary |
listRecords | StudioScriptApprovalRecordListInput | StudioScriptApprovalRecordListResult | Lists compact records visible through the approval runtime using cursor pagination |
listRecordInstances | { recordId: string } | StudioScriptApprovalRecordInstanceSummary[] | Lists compact approval instance summaries related to one record |
getInstance | { instanceId: string } | StudioScriptApprovalInstance | null | Gets one approval instance by instance ID |
listTasks | { cursor?: string; limit?: number; status?: string; memberId?: string; timeRange?: { from: string; to: string } } | { items: StudioScriptApprovalTaskListItem[]; hasMore: boolean; nextCursor: string | null; limit: number; timeRange: { from: string; to: string } } | Lists compact approval task summaries using cursor pagination |
getTask | { taskId: string } | StudioScriptApprovalTaskDetail | null | Gets one compact approval task summary by task ID |
decideTask | { taskId: string; decision: 'approved' | 'rejected' | 'returned'; comment?: string | null; returnTarget?: StudioScriptApprovalReturnTarget | null } | StudioScriptApprovalTaskDecisionResult | Approves, rejects, or returns one approval task |
withdraw | { instanceId: string; comment?: string | null } | { instance: StudioScriptApprovalInstance; record: object | null } | Withdraws a pending approval instance when the current actor is allowed |
Get Submission Config
const config = await context.approval.getSubmissionConfig({
formApiName: 'orders',
action: 'update',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
formApiName | string | Yes | Form API name. The runtime resolves the form ID internally |
action | 'create' | 'update' | 'delete' | No | Trigger action. Defaults to update |
Submit Record
const result = await context.approval.submitRecord({
recordId: 'record-id',
action: 'update',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
recordId | string | Yes | Form record ID |
action | 'create' | 'update' | 'delete' | No | Trigger action. Defaults to update |
beforeRecordData | Record<string, unknown> | null | No | Previous record data snapshot for update/delete approval scenarios |
Return shape:
type StudioScriptApprovalSubmitRecordResult = {
submitted: boolean;
config: {
enabled: boolean;
definitionName: string | null;
allowWithdraw: boolean;
};
record: {
recordId: string;
dataStatus: string;
approvalStatus: string;
approvalFlowInstanceId: string | null;
};
instance: {
instanceId: string;
status: string;
result: string | null;
startedAt: string;
completedAt: string | null;
} | null;
currentTask: {
taskId: string;
status: string;
nodeKey: string;
nodeName: string;
assigneeType: string;
assigneeId: string;
assigneeDisplayName: string | null;
} | null;
};
submitRecord returns a compact summary for scripts. It does not include full instance snapshots, subject records, node history, child table data, or form field data. Use listRecordInstances or getInstance when the script needs full approval runtime details.
List Records
const records = await context.approval.listRecords({
limit: 20,
status: 'pending',
currentApproverMemberId: 'member-id',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
cursor | string | null | No | Opaque nextCursor returned by the previous call. Keep the remaining query conditions unchanged |
limit | number | No | Maximum number of records to return. Defaults to 20; maximum is 100 |
status | StudioScriptApprovalStatus | null | No | Filters by task status |
definitionId | string | null | No | Filters by approval definition |
initiatorMemberId | string | null | No | Filters by initiating member |
currentApproverMemberId | string | null | No | Filters by current approver member |
startedAtFrom / startedAtTo | string | null | No | Filters by started time range |
keyword | string | null | No | Keyword search |
listRecords uses the same approval runtime visibility and permission rules as the approval record page.
List Record Instances
const instances = await context.approval.listRecordInstances({
recordId: 'record-id',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
recordId | string | Yes | Form record ID |
Return shape:
type StudioScriptApprovalRecordInstanceSummary = {
instanceId: string;
status: string;
result: string | null;
definitionName: string;
allowWithdraw: boolean;
subjectId: string;
startedAt: string;
completedAt: string | null;
currentTasks: Array<{
taskId: string;
status: string;
nodeKey: string;
nodeName: string;
assigneeType: string;
assigneeId: string;
assigneeDisplayName: string | null;
}>;
};
listRecordInstances returns compact instance summaries and current pending tasks for scripts. It does not include full instance snapshots, subject records, node history, child table data, or form field data. Use getInstance when the script needs full approval runtime details.
Get Instance
const instance = await context.approval.getInstance({
instanceId: 'approval-instance-id',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
instanceId | string | Yes | Approval instance ID |
getInstance returns full approval instance details, including instance snapshots, node history, task list, and subject records. Prefer listRecordInstances when the script only needs compact summaries.
List Tasks
const tasks = await context.approval.listTasks({
status: 'pending',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
cursor | string | null | No | Cursor returned by the previous task-list call |
limit | number | No | Maximum number of tasks returned per call. Defaults to 20; maximum is 100 |
status | string | null | No | Optional task status filter |
memberId | string | null | No | Member ID to query. When provided, the SDK lists tasks visible/actionable through that member's principals |
timeRange | { from: string; to: string } | null | No | Optional inclusive time range for task search. When omitted, the approval runtime applies its default range |
Return shape:
type StudioScriptApprovalTaskListItem = {
taskId: string;
instanceId: string;
status: string;
nodeKey: string;
nodeName: string;
assigneeType: string;
assigneeId: string;
assigneeDisplayName: string | null;
subjectId: string;
instanceStatus: string;
createdAt: string;
};
listTasks returns compact task summaries for scripts. It does not include tenantId, approval policy fields, comments, full subjectSnapshot, child table data, or form field data. Use getTask when the script needs one task's decision result.
Get Task
const task = await context.approval.getTask({
taskId: 'task-id',
});
Return shape:
type StudioScriptApprovalTaskDetail = StudioScriptApprovalTaskListItem & {
decidedAt: string | null;
decision: string | null;
comment: string | null;
};
getTask returns a compact task summary for scripts. It does not include full subjectSnapshot, child table data, or form field data. Use subjectId with the form SDK when the script needs the related business record.
Decide Task
await context.approval.decideTask({
taskId: 'task-id',
decision: 'approved',
comment: 'Looks good.',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
taskId | string | Yes | Approval task ID |
decision | 'approved' | 'rejected' | 'returned' | Yes | Task decision |
comment | string | null | No | Decision comment. Some approval nodes may require it |
returnTarget | { type: 'initiator' } | { type: 'node'; nodeKey: string } | null | No | Required when returning to a specific node or initiator |
Return shape:
type StudioScriptApprovalTaskDecisionResult = {
task: {
taskId: string;
status: string;
decision: string | null;
comment: string | null;
decidedAt: string | null;
} | null;
instance: {
instanceId: string;
status: string;
result: string | null;
completedAt: string | null;
currentTasks: StudioScriptApprovalTaskSummary[];
};
};
decideTask uses the same approval runtime permissions and node decision rules as the product UI. Nodes may reject unsupported decisions, missing comments, or missing return targets.
Withdraw Instance
const result = await context.approval.withdraw({
instanceId: 'approval-instance-id',
comment: 'Withdraw by script.',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
instanceId | string | Yes | Pending approval instance ID to withdraw |
comment | string | null | No | Withdraw comment |
withdraw only succeeds when the current actor is allowed to withdraw the instance and the instance is still pending.
Organization Client
Use studioScriptContext.organization when a script needs read-only organization profile form data from the organization table.
interface StudioScriptOrganizationClient {
get(input: StudioScriptOrganizationGetInput): Promise<StudioScriptOrganizationProfileData | null>;
list(input?: StudioScriptOrganizationListInput): Promise<StudioScriptOrganizationList>;
tree(input?: Record<string, never>): Promise<StudioScriptOrganizationTree>;
}
type StudioScriptOrganizationProfileData = {
data: Record<string, unknown>;
};
type StudioScriptOrganizationList = {
items: StudioScriptOrganizationProfileData[];
};
type StudioScriptOrganizationTree = {
items: StudioScriptOrganizationTreeNode[];
};
interface StudioScriptOrganizationTreeNode extends StudioScriptOrganizationProfileData {
children: StudioScriptOrganizationTreeNode[];
}
Client methods:
| Method | Input | Return | Description |
|---|---|---|---|
get | StudioScriptOrganizationGetInput | StudioScriptOrganizationProfileData | null | Gets one organization's profile form data by organization ID |
list | StudioScriptOrganizationListInput | StudioScriptOrganizationList | Lists organization profile form data, with an optional keyword filter |
tree | Record<string, never> | StudioScriptOrganizationTree | Gets the organization profile form data tree. No parameters are required |
List Organizations
const organizations = await context.organization.list({
keyword: 'Sales',
condition: {
logic: 'and',
children: [{ field: 'so_status', operator: 'eq', value: 'active' }],
},
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
keyword | string | null | No | Filters by organization name or code |
condition | StudioScriptFormConditionGroup | null | No | Filters by organization profile field API names using the form-record condition operators |
Get Organization Tree
const tree = await context.organization.tree();
tree does not require parameters.
Get One Organization
const organization = await context.organization.get({
organizationId: 'organization-id',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
organizationId | string | Yes | Organization ID |
The Organization Client is read-only in Studio scripts. It does not expose organization create, update, move, deactivate, or delete operations.
Department Client
Use studioScriptContext.department when a script needs read-only department profile form data.
interface StudioScriptDepartmentClient {
get(input: StudioScriptDepartmentGetInput): Promise<StudioScriptDepartmentProfileData | null>;
list(input?: StudioScriptDepartmentListInput): Promise<StudioScriptDepartmentList>;
tree(input?: { organizationId?: string | null }): Promise<StudioScriptDepartmentTree>;
}
type StudioScriptDepartmentProfileData = {
data: Record<string, unknown>;
};
type StudioScriptDepartmentList = {
items: StudioScriptDepartmentProfileData[];
};
type StudioScriptDepartmentTree = {
items: StudioScriptDepartmentTreeNode[];
};
interface StudioScriptDepartmentTreeNode extends StudioScriptDepartmentProfileData {
children: StudioScriptDepartmentTreeNode[];
}
Client methods:
| Method | Input | Return | Description |
|---|---|---|---|
get | StudioScriptDepartmentGetInput | StudioScriptDepartmentProfileData | null | Gets one department's profile form data by department ID |
list | StudioScriptDepartmentListInput | StudioScriptDepartmentList | Lists department profile form data, with optional keyword and organization filters |
tree | { organizationId?: string | null } | StudioScriptDepartmentTree | Gets the department profile form data tree, optionally filtered by organization ID |
List Departments
const departments = await context.department.list({
keyword: 'Sales',
condition: {
logic: 'and',
children: [{ field: 'so_status', operator: 'eq', value: 'active' }],
},
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
keyword | string | null | No | Filters by department name or code |
organizationId | string | null | No | Filters departments by organization ID |
condition | StudioScriptFormConditionGroup | null | No | Filters by department profile field API names using the form-record condition operators |
Get Department Tree
const tree = await context.department.tree();
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
organizationId | string | null | No | Filters the tree by organization ID |
Get One Department
const department = await context.department.get({
departmentId: 'department-id',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
departmentId | string | Yes | Department ID |
The Department Client is read-only in Studio scripts. It does not expose department create, update, move, deactivate, or delete operations.
Member Client
Use studioScriptContext.member when a script needs read-only member data.
interface StudioScriptMemberClient {
current(input?: Record<string, never>): Promise<StudioScriptCurrentMember | null>;
get(input: StudioScriptMemberGetInput): Promise<StudioScriptMemberProfileData | null>;
list(input?: StudioScriptMemberListInput): Promise<StudioScriptMemberList>;
}
Client methods:
| Method | Input | Return | Description |
|---|---|---|---|
current | Record<string, never> | StudioScriptCurrentMember | null | Gets the profile form data for the member that triggered the current script. No parameters are required |
get | StudioScriptMemberGetInput | StudioScriptMemberProfileData | null | Gets one member's profile form data by member ID |
list | StudioScriptMemberListInput | StudioScriptMemberList | Lists member profile form data, with optional keyword, organization, department, and pagination filters |
Get Current Member
const member = await context.member.current();
current does not require parameters. It returns null when the script is triggered without a logged-in member context.
Return shape:
type StudioScriptCurrentMember = {
data: Record<string, unknown>;
};
data is the current member's profile form data.
List Members
const members = await context.member.list({
departmentId: 'department-id',
includeDescendants: true,
limit: 20,
condition: {
logic: 'and',
children: [{ field: 'so_avatar', operator: 'not_empty' }],
},
});
items contains only member profile form data:
type StudioScriptMemberList = {
items: Array<{ data: Record<string, unknown> }>;
hasMore: boolean;
nextCursor: string | null;
limit: number;
summary: {
active: number;
inactive: number;
};
};
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
keyword | string | null | No | Filters by member name, email, phone, or employee code |
organizationId | string | null | No | Filters members by organization ID |
departmentId | string | null | No | Filters members by department ID |
includeDescendants | boolean | No | Includes members from child departments when departmentId is provided |
cursor | string | null | No | Opaque nextCursor returned by the previous call. Keep the remaining query conditions unchanged |
limit | number | No | Maximum number of members to return. Defaults to 20; maximum is 100 |
condition | StudioScriptFormConditionGroup | null | No | Filters by member profile field API names using the form-record condition operators |
Get One Member
const member = await context.member.get({
memberId: 'member-id',
});
get returns the same { data } shape as current.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
memberId | string | Yes | Member ID |
The Member Client is read-only in Studio scripts. It does not expose member create, update, deactivate, or delete operations.
IAM Client
Use studioScriptContext.iam when a script needs to check the current runtime actor's effective roles. This is a read-only runtime SDK, not a role management API.
interface StudioScriptIamClient {
currentRoleSummary(input?: Record<string, never>): Promise<{
roles: StudioScriptIamRole[];
roleCodes: string[];
}>;
memberRoleSummary(input: { memberId: string }): Promise<{
memberId: string;
roles: StudioScriptIamRole[];
roleCodes: string[];
}>;
memberRoleSummaries(input: { memberIds: string[] }): Promise<{
items: Array<{
memberId: string;
roles: StudioScriptIamRole[];
roleCodes: string[];
}>;
}>;
}
Client methods:
| Method | Input | Return | Description |
|---|---|---|---|
currentRoleSummary | Record<string, never> | { roles: StudioScriptIamRole[]; roleCodes: string[] } | Gets the current runtime actor's role summary. No parameters are required |
memberRoleSummary | { memberId: string } | { memberId: string; roles: StudioScriptIamRole[]; roleCodes: string[] } | Gets one specified member's role summary |
memberRoleSummaries | { memberIds: string[] } | { items: Array<MemberRoleSummary> } | Gets role summaries for multiple specified members |
Get Current Role Summary
const summary = await context.iam.currentRoleSummary();
currentRoleSummary does not require parameters. It returns active roles matched through the current actor's member, department, and organization principals, plus de-duplicated roleCodes for simple script branching.
Get Member Role Summary
const summary = await context.iam.memberRoleSummary({
memberId: 'member-id',
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
memberId | string | Yes | Member ID in the current tenant |
memberRoleSummary returns active roles matched through the target member's member, department, and organization principals, plus de-duplicated roleCodes.
Get Member Role Summaries
const summaries = await context.iam.memberRoleSummaries({
memberIds: ['member-id-1', 'member-id-2'],
});
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
memberIds | string[] | Yes | Member IDs in the current tenant. Maximum 100 per call |
memberRoleSummaries returns one summary per requested member ID, preserving request order.
Role Shape
type StudioScriptIamRole = {
id: string;
code: string;
name: string;
description: string | null;
builtIn: boolean;
systemProtected: boolean;
status: 'active';
bindings: Array<{
id: string;
principalType: 'MEMBER' | 'DEPARTMENT' | 'ORGANIZATION';
principalId: string;
precedence: number;
startAt: string | null;
endAt: string | null;
}>;
};
Role fields:
| Field | Type | Description |
|---|---|---|
id | string | Role ID. Use it when you need to correlate with role bindings or other role records |
code | string | Stable role code for script branching, such as space_admin or a custom role code |
name | string | Display name of the role |
description | string | null | Optional role description |
builtIn | boolean | Whether the role is a system built-in role |
systemProtected | boolean | Whether the role is protected from normal user-side modification |
status | 'active' | Role status. The SDK only returns active roles |
bindings | StudioScriptIamRoleBinding[] | Active bindings that matched the queried member/current actor |
Binding fields:
| Field | Type | Description |
|---|---|---|
id | string | Role binding ID |
principalType | 'MEMBER' | 'DEPARTMENT' | 'ORGANIZATION' | Principal type that grants the role |
principalId | string | ID of the member, department, or organization principal |
precedence | number | Matching priority. Lower numbers are more specific; member bindings are 0, department bindings are 1, organization bindings are 2 |
startAt | string | null | Binding start time. null means it is effective immediately |
endAt | string | null | Binding end time. null means it does not expire |
Only enabled bindings in their valid time window are returned. Disabled roles and inactive bindings are filtered out.
Custom login page
Install
npm i @soln/custom-login-sdk
Use the complete demo
Download the demo from GitHub or GitCode. SDK calls are already integrated; English comments SOLN INTEGRATION 1–9 mark their locations.
Edit examples/config.js:
export const config = {
issuer: 'https://console.example.com', // Your trusted Soln control-plane origin.
workspaceLoginUrl: 'https://work.example.com/auth/start', // Your workspace login entry.
brandName: 'Your brand',
faviconUrl: './assets/favicon-192x192.png',
locale: 'en',
};
Replace the logo in examples/index.html, text in examples/messages.js, and styles in examples/styles.css as needed. Deploy examples/ and dist/ together to an HTTPS site, preserving their relative paths.
In System Management → Organization Profile → Edit, enter the deployed /examples/ URL in Custom Login Page URL and save. Leave it empty to restore the default page. Start login from the workspace, or open the demo directly with workspaceLoginUrl configured. No existing login session is needed.
Connect your own page
Create a client when the page opens, then bind its methods to your form. Set issuer to your trusted control-plane origin.
import {
createSolnLoginClient,
type LoginResult,
} from '@soln/custom-login-sdk';
const login = createSolnLoginClient({
issuer: 'https://console.example.com',
locale: 'en',
});
const context = await login.getContext();
// If context.passwordSetup is present, show the new-password form.
function finish(result: LoginResult) {
if (result.nextStep === 'set_password') {
// Show the new-password form for result.email.
return;
}
login.complete(result);
}
// Bind to the password-login form.
async function submitPassword(email: string, password: string) {
finish(await login.loginWithPassword({ email, password }));
}
// Bind to the send-code button.
async function sendCode(email: string) {
await login.sendEmailCode({ email });
}
// Bind to the email-code form. The code has six digits.
async function submitCode(email: string, code: string) {
finish(await login.verifyEmailCode({ email, code }));
}
// Bind to the new-password form. Use at least eight characters.
async function submitNewPassword(newPassword: string) {
finish(await login.setupPassword({ newPassword }));
}
Show errors through your page's notification component and restore button loading state in finally. Human verification opens automatically through the SDK when needed. The complete demo includes these behaviors.
Always call complete() for a complete result to finish login and return to the workspace. If the login request expires, start again from the workspace /auth/start entry.
Permissions And Errors
Studio SDK calls run as the current script actor and reuse the same tenant-side permission checks as the product runtime. They do not bypass form visibility, row access, field access, button permissions, approval task permissions, or tenant file boundaries.
When an SDK call throws and the script does not catch it, the script execution fails with STUDIO_ASSET_EXECUTION_FAILED. The execution result's errorMessage is the underlying runtime code, such as IAM_PERMISSION_DENIED, FORMS_SCHEMA_NOT_FOUND, or FORM_DATA_RECORD_ID_REQUIRED.
Permission Rules
| Client | Permission behavior |
|---|---|
form | Resolves forms by formApiName, checks form visibility, applies row and field access for reads, and uses the normal create/update/delete runtime flows for writes. buttonKey triggers the same button execution permission checks as product buttons |
formSchema | Returns only form and field metadata visible to the current actor. Disabled or inaccessible forms are rejected |
file | Resolves and creates file assets only inside the current tenant. File IDs from another tenant are not accessible |
approval | Uses the normal approval runtime visibility and action rules. Task decisions and withdrawals must be actionable by the current actor or the member context supported by that method |
organization / department / member | Read-only. Returns profile form data visible in the current tenant; it does not expose create, update, deactivate, move, or delete operations |
iam | Read-only. Returns role summaries for current or specified members; it does not expose role management or binding mutation APIs |
Error Handling
Use try/catch when the script should convert SDK failures into a controlled return value or a user-visible runtime effect.
export default async function main(_payload: StudioScriptRuntimeContextPayload, context: StudioScriptContext) {
try {
const record = await context.form.find({
formApiName: 'orders',
condition: {
logic: 'and',
children: [
{
field: 'recordId',
operator: 'eq',
value: 'record-id',
},
],
},
});
return {
ok: true,
record,
};
} catch (error) {
const code = error instanceof Error ? error.message : String(error);
return {
ok: false,
message: code,
notification: {
type: 'error',
title: 'Operation failed',
description: code,
},
};
}
}
Batch methods have two different failure styles:
batchTrashandbatchDeletereturn partial results. Per-record failures are listed infailedwith acode- Transactional batch create/update methods reject the SDK call when any record fails, and the transaction is rolled back
Common Error Codes
General Studio SDK errors:
| Code | Meaning |
|---|---|
STUDIO_ASSET_EXECUTION_FAILED | The script threw an uncaught error. The underlying SDK error is returned as errorMessage |
STUDIO_FORM_API_INPUT_INVALID | The form SDK input must be an object |
STUDIO_FORM_SCHEMA_API_INPUT_INVALID | The form schema SDK input must be an object |
STUDIO_FORM_SAVED_FILTER_API_INPUT_INVALID | Saved filter SDK input must be an object |
STUDIO_FORM_TABLE_VIEW_API_INPUT_INVALID | Table view SDK input must be an object |
STUDIO_FILE_API_INPUT_INVALID | The file SDK input must be an object |
STUDIO_APPROVAL_API_INPUT_INVALID | The approval SDK input must be an object |
STUDIO_ORG_API_INPUT_INVALID | The organization, department, or member SDK input must be an object |
STUDIO_FORM_API_OPERATION_INVALID | Unknown form SDK operation |
STUDIO_FORM_SCHEMA_API_OPERATION_INVALID | Unknown form schema SDK operation |
STUDIO_FORM_SAVED_FILTER_API_OPERATION_INVALID | Unknown saved filter SDK operation |
STUDIO_FORM_TABLE_VIEW_API_OPERATION_INVALID | Unknown table view SDK operation |
STUDIO_FILE_API_OPERATION_INVALID | Unknown file SDK operation |
STUDIO_APPROVAL_API_OPERATION_INVALID | Unknown approval SDK operation |
STUDIO_ORG_API_OPERATION_INVALID | Unknown organization, department, or member SDK operation |
STUDIO_IAM_API_OPERATION_INVALID | Unknown IAM SDK operation |
Form and form schema errors:
| Code | Meaning |
|---|---|
FORMS_SCHEMA_API_NAME_REQUIRED | formApiName is missing |
FORMS_SCHEMA_NOT_FOUND | The form API name cannot be resolved or is not accessible |
FORMS_SCHEMA_DISABLED | The target form is disabled |
FORMS_FIELD_API_NAME_REQUIRED | fieldApiName is missing |
FORM_DATA_RECORD_ID_REQUIRED | recordId is missing |
FORM_DATA_NOT_FOUND | The target record cannot be found or is not accessible |
FORM_DATA_APPROVAL_PENDING_READ_ONLY | The record is in a pending approval state and cannot be modified directly |
FORM_DATA_APPROVAL_FLOW_REQUIRED | The operation requires an approval flow instance |
FORM_DATA_APPROVAL_TRANSITION_INVALID | The requested approval status transition is not allowed |
FORM_DATA_DELETE_APPROVAL_NOT_SUPPORTED | Hard delete does not support approval submission; use trash or batchTrash with submitApproval: true |
FORM_DATA_FOREIGN_KEY_RESTRICTED | A referenced record or relation is restricted by data permissions or relation rules |
IAM_PERMISSION_DENIED | The current actor does not have the required permission |
STUDIO_FORM_API_DATA_INVALID | data is not an object |
STUDIO_FORM_API_RECORDS_REQUIRED | Batch create or update requires at least one record |
STUDIO_FORM_API_RECORDS_LIMIT_EXCEEDED | Batch create or update exceeded the maximum record count |
STUDIO_FORM_API_RECORD_IDS_REQUIRED | Batch delete requires at least one record ID |
STUDIO_FORM_API_RECORD_IDS_LIMIT_EXCEEDED | Batch delete exceeded the maximum record ID count |
STUDIO_FORM_API_CHILD_TABLES_REQUIRED | A create/update-with-child-tables call requires child table input |
STUDIO_FORM_API_CHILD_TABLE_NOT_FOUND | The child form API name is not a child table of the parent form |
STUDIO_FORM_API_CHILD_RECORDS_REQUIRED | A child table operation requires at least one child record |
STUDIO_FORM_API_CHILD_RECORDS_LIMIT_EXCEEDED | A child table operation exceeded the maximum child record count |
FORM_DATA_BATCH_UPDATE_RECORD_NOT_FOUND | A batch update target record was not found or not accessible |
File errors:
| Code | Meaning |
|---|---|
FILES_ID_REQUIRED | fileId is missing |
FILES_IDS_REQUIRED | fileIds is missing or empty |
FILES_NAME_REQUIRED | File display name is missing |
FILES_ORIGINAL_NAME_REQUIRED | Original file name is missing |
FILES_MIME_TYPE_REQUIRED | MIME type is missing |
FILES_CONTENT_REQUIRED | Base64 file content is missing |
FILES_NOT_FOUND | The file asset cannot be found or is not accessible |
Approval errors:
| Code | Meaning |
|---|---|
APPROVAL_TRIGGER_ACTION_INVALID | Approval action must be create, update, or delete |
APPROVAL_TASK_ID_REQUIRED | taskId is missing |
APPROVAL_INSTANCE_ID_REQUIRED | instanceId is missing |
APPROVAL_TASK_DECISION_INVALID | Task decision must be approved, rejected, or returned |
APPROVAL_TASK_NOT_FOUND | The task cannot be found or is not visible to the actor |
APPROVAL_TASK_NOT_ACTIONABLE | The task cannot be acted on by the current actor or in its current state |
APPROVAL_TASK_ALREADY_DECIDED | The task has already been decided |
APPROVAL_TASK_COMMENT_REQUIRED | The current approval policy requires a comment |
APPROVAL_INSTANCE_NOT_FOUND | The approval instance cannot be found or is not visible |
APPROVAL_INSTANCE_ALREADY_PENDING | The record already has a pending approval instance |
APPROVAL_INSTANCE_NOT_ACTIONABLE | The approval instance cannot be acted on in its current state |
APPROVAL_INSTANCE_WITHDRAW_DISABLED | Withdrawal is disabled for this approval definition |
APPROVAL_INSTANCE_WITHDRAW_FORBIDDEN | The current actor cannot withdraw this approval instance |
APPROVAL_RETURN_TARGET_INVALID | The return target is invalid |
APPROVAL_RETURN_TARGET_NODE_REQUIRED | A node return target requires nodeKey |
Organization, department, member, and IAM errors:
| Code | Meaning |
|---|---|
ORGANIZATIONS_ID_REQUIRED | organizationId is missing |
DEPARTMENTS_ID_REQUIRED | departmentId is missing |
MEMBERS_ID_REQUIRED | memberId is missing |
STUDIO_IAM_MEMBER_ID_REQUIRED | memberId is missing for a role summary lookup |
STUDIO_IAM_MEMBER_IDS_REQUIRED | memberIds is missing or empty |
STUDIO_IAM_MEMBER_IDS_LIMIT_EXCEEDED | memberIds exceeds the maximum count of 100 |