속성 & 출력 스키마
커스텀 필드의 설정 가능한 속성과 출력 데이터 형식 정의하기
속성 & 출력 스키마
Walla에 커스텀 필드 타입을 등록할 때 두 가지 JSON Schema를 정의합니다:
- 속성 스키마 — 대시보드에서 필드를 설정할 때 표시되는 설정 항목을 제어합니다
- 출력 스키마 — 제출된 값의 형식을 정의하여 응답 시트, CSV 내보내기, API에서 사용합니다
속성 스키마
propertiesSchema는 커스텀 필드의 설정 가능한 항목을 기술하는 표준 JSON Schema입니다. Walla 대시보드는 이 스키마를 기반으로 설정 UI를 자동으로 렌더링합니다.
기본 예제
프리셋 색상과 기본 색상을 설정할 수 있는 색상 선택기:
{
"type": "object",
"properties": {
"defaultColor": {
"type": "string",
"title": "Default Color",
"description": "Initial color value",
"default": "#007EFF"
},
"presets": {
"type": "array",
"title": "Preset Colors",
"description": "Color swatches shown to the user",
"items": {
"type": "string"
},
"default": ["#FF6B6B", "#4ECDC4", "#45B7D1", "#007EFF"]
},
"showCustomPicker": {
"type": "boolean",
"title": "Show Custom Picker",
"description": "Allow users to pick any color",
"default": true
}
}
}이 속성들은 onInit 콜백을 통해 필드에 전달됩니다:
field.onInit(({ properties }) => {
// properties = { defaultColor: '#007EFF', presets: [...], showCustomPicker: true }
renderUI(properties);
});기본 속성
defaultProperties 객체는 폼 빌더가 커스텀 필드 타입을 폼에 추가할 때 사용되는 초기값을 제공합니다. 속성 스키마의 default 값과 일치해야 합니다.
{
"defaultColor": "#007EFF",
"presets": ["#FF6B6B", "#4ECDC4", "#45B7D1", "#007EFF"],
"showCustomPicker": true
}다른 필드 참조 (_observedFields)
수량을 합산하는 계산기, 사용자 선택을 반영하는 가격 미리보기, 이전 답변을 인용하는 요약 — 이런 커스텀 필드는 같은 폼 내 다른 필드의 값이 필요합니다. 시스템 예약 키 _observedFields를 properties 스키마에 선언하면, 폼 작성자가 어떤 필드를 관찰할지 직접 선택할 수 있습니다.
_ 접두사는 시스템이 관리하는 키임을 표시합니다 — 작성자가 직접 정의한 다른 properties와 겹치지 않도록 별도 네임스페이스를 사용합니다. Walla가 각 참조를 검증한 뒤 FieldInitPayload의 타입이 지정된 observedFields 필드로 끌어올려 전달하므로, iframe 코드는 저장 키를 알 필요가 없습니다.
스키마는 tagged-union 배열로 선언합니다. oneOf 두 분기는 각각 일반 폼 필드(id로 참조)와 hidden 필드(작성자가 정의한 label로 참조)에 해당합니다:
{
"type": "object",
"properties": {
"operator": {
"type": "string",
"enum": ["sum", "product", "diff", "min", "max", "avg"],
"default": "sum"
},
"_observedFields": {
"type": "array",
"title": "관찰할 필드",
"description": "이 계산기가 값을 합칠 필드들.",
"items": {
"oneOf": [
{
"type": "object",
"title": "일반 필드 참조",
"properties": {
"kind": { "const": "field" },
"id": { "type": "string", "title": "필드 id" }
},
"required": ["kind", "id"]
},
{
"type": "object",
"title": "Hidden 필드 참조",
"properties": {
"kind": { "const": "hidden" },
"label": { "type": "string", "title": "Hidden 필드 라벨" }
},
"required": ["kind", "label"]
}
]
},
"default": []
}
},
"required": ["operator"]
}선택 모델
폼 작성자는 작성 시점에 참조를 선택합니다 — 새로 추가한 필드를 참조하기 위해 먼저 폼을 게시할 필요는 없습니다. 선택된 항목은 작성자가 고른 순서대로 저장되며, diff나 리스트 기반 연산자처럼 순서를 사용하는 곳에서 의미가 있습니다.
이미 사라진 필드(hidden 필드의 label이 바뀌었거나 일반 필드가 삭제된 경우)를 가리키는 참조는 런타임에 formContext에서 해석되지 않습니다. iframe 코드가 observedFields를 순회할 때 모든 ref가 값으로 매핑된다고 가정하지 말고 방어적으로 작성하세요.
호스트의 작성 UI는 두 oneOf 분기를 그룹으로 나눠 작성자가 JSON을 직접 편집하지 않아도 참조를 선택할 수 있게 해줍니다. 정확한 UI는 호스트마다 다르며, 보장되는 계약은 전송 형태 — 작성자가 고른 순서대로 저장된 ObservedFieldRef[] — 입니다.
iframe이 받는 데이터
버전의 observeFields.mode를 'configured'로 설정하세요. 작성자가 참조를 선택하면, iframe은 payload.observedFields로 참조 목록을, payload.formContext로 해석된 값을 받습니다:
field.onInit(({ observedFields, formContext, properties }) => {
// observedFields: [
// { kind: 'field', id: 'fld_qty' },
// { kind: 'field', id: 'fld_price' },
// { kind: 'hidden', label: 'utm_discount' },
// ]
// formContext.fields: 허용된 필드들의 FieldSummary[]
// formContext.values: { [id]: value } — 허용된 부분집합
const operands = observedFields.map(ref => {
if (ref.kind === 'field') return Number(formContext.values[ref.id] ?? 0);
const f = formContext.fields.find(field => field.label === ref.label);
return Number(formContext.values[f?.id ?? ''] ?? 0);
});
field.setValue(operands.reduce((a, b) => a + b, 0));
});민감 필드 타입(
SECRETS,PHONE_NUMBER,observedFields에 명시적으로 포함되어 있어도formContext에서 항상 제외됩니다. 허용된 집합은 항상 요청한 집합의 부분집합입니다.
출력 스키마
outputSchema는 필드가 제출 시 생성하는 값의 형식을 정의하는 JSON Schema입니다. Walla는 이 스키마를 다음과 같이 활용합니다:
- 응답 시트의 컬럼 생성
- CSV 및 Excel 내보내기 형식 지정
- Open API 응답 및 웹훅 페이로드의 타입 지정
문자열 출력
단일 텍스트 값을 생성하는 필드 (예: 색상 HEX 코드):
{
"type": "string"
}응답 시트: 문자열 값을 표시하는 컬럼 1개.
값 예시: "#FF6B6B"
숫자 출력
숫자 값을 생성하는 필드:
{
"type": "number"
}응답 시트: 숫자를 표시하는 컬럼 1개.
값 예시: 42
불리언 출력
참/거짓 값을 생성하는 필드:
{
"type": "boolean"
}응답 시트: 불리언 값을 표시하는 컬럼 1개.
값 예시: true
객체 출력
이름이 있는 속성으로 구성된 구조화된 데이터를 생성하는 필드입니다. 각 속성이 응답 시트에서 하위 컬럼이 됩니다.
{
"type": "object",
"properties": {
"hex": {
"type": "string",
"title": "Hex Color"
},
"opacity": {
"type": "number",
"title": "Opacity"
}
}
}응답 시트: "Hex Color"와 "Opacity" 2개의 하위 컬럼.
값 예시: { "hex": "#FF6B6B", "opacity": 0.8 }
배열 출력
값 목록을 생성하는 필드입니다. primitive 배열은 응답 시트에서 쉼표로 구분된 문자열로 표시됩니다.
{
"type": "array",
"items": {
"type": "string"
}
}응답 시트: 쉼표로 구분된 값이 들어간 컬럼 1개.
값 예시: ["red", "blue", "green"]
중첩 객체 출력
복잡한 데이터 구조를 위해 객체를 중첩할 수 있습니다:
{
"type": "object",
"properties": {
"address": {
"type": "object",
"title": "Address",
"properties": {
"street": { "type": "string", "title": "Street" },
"city": { "type": "string", "title": "City" },
"zip": { "type": "string", "title": "ZIP Code" }
}
},
"coordinates": {
"type": "object",
"title": "Coordinates",
"properties": {
"lat": { "type": "number", "title": "Latitude" },
"lng": { "type": "number", "title": "Longitude" }
}
}
}
}값 예시:
{
"address": { "street": "123 Main St", "city": "Seoul", "zip": "06100" },
"coordinates": { "lat": 37.5665, "lng": 126.9780 }
}응답 시트 표시 제한
응답 시트와 CSV/Excel 내보내기는 outputSchema에서 자동으로 컬럼을 생성합니다. 표 형태 표시에는 다음 제한이 적용됩니다:
| 제한 | 값 | 설명 |
|---|---|---|
| 최대 중첩 깊이 | 3 | depth 3을 초과하는 속성은 표시되지 않습니다 |
| 객체 배열 | 건너뜀 | primitive 배열(string[], number[])만 표시됩니다 |
Open API와 웹훅의 원본 데이터에는 항상 전체 데이터가 포함됩니다 — 이 제한은 표 형태의 응답 시트와 내보내기에만 적용됩니다.
시트에서 잘 표시되도록 outputSchema를 가능한 평탄하거나 얕은 구조로 설계하세요. 깊은 중첩은 최소화하는 것이 좋습니다.
전체 예제: 서명 패드
펜 설정을 구성할 수 있고 구조화된 출력을 가진 서명 패드 필드:
속성 스키마
{
"type": "object",
"properties": {
"penColor": {
"type": "string",
"title": "Pen Color",
"default": "#000000"
},
"penWidth": {
"type": "number",
"title": "Pen Width",
"default": 2
},
"canvasHeight": {
"type": "number",
"title": "Canvas Height (px)",
"default": 200
}
}
}기본 속성
{
"penColor": "#000000",
"penWidth": 2,
"canvasHeight": 200
}출력 스키마
{
"type": "object",
"properties": {
"imageUrl": {
"type": "string",
"title": "Signature Image"
},
"timestamp": {
"type": "string",
"title": "Signed At"
}
}
}필드 구현
import { WallaField } from '@wallaform/custom-field-sdk';
const field = new WallaField();
field.onInit(({ properties, value, theme }) => {
const { penColor, penWidth, canvasHeight } = properties;
// Set up canvas with configured dimensions
const canvas = document.getElementById('signature');
canvas.height = canvasHeight;
canvas.style.border = `1px solid ${theme.border}`;
// Configure drawing context
const ctx = canvas.getContext('2d');
ctx.strokeStyle = penColor;
ctx.lineWidth = penWidth;
// Restore saved signature if available
if (value?.imageUrl) {
loadImage(canvas, value.imageUrl);
}
field.setHeight(canvasHeight + 60); // Canvas + toolbar
});
// On signature completion, upload and report value
async function onSignatureComplete() {
const canvas = document.getElementById('signature');
const blob = await new Promise(resolve =>
canvas.toBlob(resolve, 'image/png')
);
const buffer = await blob.arrayBuffer();
const { url } = await field.uploadBlob(buffer, 'image/png', 'signature.png');
field.setValue({
imageUrl: url,
timestamp: new Date().toISOString(),
});
}
field.onValidate(() => {
if (!hasSignature()) {
return { valid: false, errors: [{ message: 'Signature is required' }] };
}
return { valid: true };
});전체 예제: 통계 슬라이더
설정 가능한 범위를 가진 통계 슬라이더:
속성 스키마
{
"type": "object",
"properties": {
"min": {
"type": "number",
"title": "Minimum Value",
"default": 0
},
"max": {
"type": "number",
"title": "Maximum Value",
"default": 100
},
"step": {
"type": "number",
"title": "Step Size",
"default": 1
},
"showMarkers": {
"type": "boolean",
"title": "Show Statistical Markers",
"description": "Display Q1, Median, and Q3 markers",
"default": true
}
}
}출력 스키마
{
"type": "number"
}단순 숫자 출력 — 응답 시트에서 컬럼 1개로 표시됩니다.