랜딩 페이지에서 커스텀 폼 블록 만들기
Braze 랜딩 페이지 폼 블록은 텍스트 필드, 체크박스, 드롭다운과 같은 표준 입력을 캡처합니다. 커스텀 폼 블록은 별점 평가, 이모지 감정 선택기, 스크래치 카드 등 자체 인터랙티브 요소를 구축할 수 있도록 하여 가능성을 확장합니다.
방문자가 커스텀 폼을 제출하면 선택한 값이 검증되어 표준 필드와 함께 저장된 후 커스텀 사용자 속성으로 Braze에 전송됩니다. 이를 통해 랜딩 페이지 에디터를 벗어나지 않고도 더 풍부하고 매력적인 입력을 수집할 수 있습니다.
커스텀 폼 블록은 단일 JavaScript 헬퍼인 window.brazeHelpers.forms.registerFormInput을 사용하여 구축하며, 랜딩 페이지의 커스텀 코드 블록에서 호출합니다.

설문조사와 인앱 메시지에는 자체 폼 블록이 있지만, registerFormInput(커스텀 코딩된 UI를 폼 블록에 연결하는 JavaScript API)은 랜딩 페이지에서만 사용할 수 있습니다.
작동 방식
커스텀 폼 입력은 랜딩 페이지에서 값을 캡처하여 폼과 함께 제출하려는 모든 요소입니다. 해당 요소를 등록하여 Braze 폼 시스템에 연결합니다. 등록을 통해 Braze는 어떤 요소를 감시할지, 현재 값을 어떻게 읽을지, 폼이 제출될 때 해당 값을 어떻게 처리할지를 알 수 있습니다.
- 랜딩 페이지의 Custom Code 블록 내에 커스텀 UI를 구성하고
id와 같은 안정적인 CSS 선택자를 지정합니다. - 구성 객체를 사용하여
window.brazeHelpers.forms.registerFormInput을 호출해 요소를 등록합니다. - Braze가 현재 값을 읽어야 할 때 사용자가 제공한
getValue함수를 호출합니다. - 필드가 필수이거나
onValidate함수를 제공한 경우, Braze는 값이 유효할 때까지 제출을 차단하고 스타일을 적용할 수 있는 CSS 클래스로 유효하지 않은 요소를 표시합니다. 유효성 검사 및 필수 필드를 참조하세요. - 폼이 제출되고 유효성 검사가 통과되면 Braze가 사용자가 제공한
onSubmit함수를 호출하며, 여기서 Braze SDK를 호출하여 커스텀 사용자 속성과 같은 정보를 기록할 수 있습니다.
값을 읽고, 유효성을 검사하고, 제출하는 함수를 직접 제공하기 때문에 이 접근 방식은 거의 모든 커스텀 폼 요소에서 작동하며, 편집기의 표준 필드 유형에 제한되지 않습니다.
기본 프레임워크
가장 간단한 등록은 하나의 요소를 대상으로 하여 필수 항목으로 처리하고, 데이터 속성에서 값을 읽은 다음, 양식이 제출될 때 해당 값을 커스텀 사용자 속성에 기록합니다. 이 페이지의 예제처럼 DOMContentLoaded 리스너로 호출을 감싸면 registerFormInput이 실행되기 전에 요소가 존재하게 됩니다:
document.addEventListener("DOMContentLoaded", () => {
window.brazeHelpers.forms.registerFormInput({
selector: "#my-custom-input",
isRequired: true,
getValue: (element) => element.dataset.value ?? null,
onSubmit: (value) => {
window.brazeBridge.getUser().setCustomUserAttribute("my_attribute", value);
},
});
});
페이지의 각 커스텀 입력에 대해 registerFormInput을 한 번씩 호출하세요. 랜딩 페이지 에디터를 통해 배치한 표준 양식 필드는 등록할 필요가 없습니다. 등록은 커스텀 코드 블록에서 직접 구축한 커스텀 입력에만 필요합니다.
구성 참조
registerFormInput은 단일 구성 객체를 받습니다. 함수 시그니처로 표현하면 전체 형태는 다음과 같습니다:
window.brazeHelpers.forms.registerFormInput({
// Provide exactly one of `selector` or `element` to identify the input.
selector?: string,
element?: HTMLElement,
isRequired?: boolean | Promise<boolean>,
getValue: (element: HTMLElement) => value,
onValidate?: (value, element: HTMLElement) => boolean | Promise<boolean>,
onSubmit?: (value, element: HTMLElement) => void | Promise<void>,
});
최소한 요소를 찾기 위한 방법(selector 또는 element)과 getValue 함수를 제공해야 합니다. 나머지는 모두 선택 사항입니다.
| 속성 | 타입 | 필수 | 설명 |
|---|---|---|---|
selector |
string |
예 (또는 element) |
커스텀 요소와 일치하는 CSS SELECTOR입니다. 예를 들어 "#scratch-card"와 같이 지정합니다. Braze는 유효성 검사 및 제출 시점에 querySelector로 지연 해석하므로, registerFormInput 실행 후 DOM에 추가된 요소도 매칭할 수 있습니다. |
element |
HTMLElement |
예 (또는 selector) |
selector 대신 사용하는 요소에 대한 직접 참조입니다. 요소가 페이지에 연결되어 있는 동안에만 사용되며, 두 속성이 모두 제공된 경우 selector보다 우선합니다. |
isRequired |
boolean \| Promise<boolean> |
아니요 | true인 경우, 입력에 비어 있지 않은 값이 있어야 양식을 제출할 수 있습니다. null, undefined, 빈 문자열(공백만 있는 문자열 포함), 빈 배열은 모두 비어 있는 것으로 간주됩니다. boolean으로 해석되는 프로미스도 가능하며, Braze는 입력이 유효성 검사를 받을 때마다 다시 평가하므로 런타임에 필수 상태를 결정할 수 있습니다. 기본값은 false입니다. 전체 유효성 검사 순서는 유효성 검사 및 필수 필드를 참조하세요. |
getValue |
function |
예 | 입력의 현재 값을 반환합니다. Braze는 매칭된 요소를 인수로 전달하므로, element.dataset.sentiment과 같이 DOM에서 값을 읽거나 자체 코드의 변수에서 읽을 수 있습니다. 아직 값이 없으면 null을 반환하세요. |
onValidate |
function |
아니요 | 현재 값과 매칭된 요소를 받으며, isRequired와 동일한 반환 타입인 boolean \| Promise<boolean>을 반환해야 합니다. true는 값이 유효하다는 의미이고, false는 유효하지 않다는 의미입니다. 값이 있는지 여부 외에 추가 규칙을 적용하는 데 사용합니다. 예를 들어 값이 허용된 집합 중 하나인지 확인할 수 있습니다. 생략하면 isRequired와 네이티브 제약 유효성 검사만 적용됩니다. |
onSubmit |
function |
아니요 | 양식이 제출되고 유효성 검사를 통과하면 실행됩니다. 현재 값과 매칭된 요소를 받습니다. 여기서 일반적으로 setCustomUserAttribute를 사용하여 값을 Braze에 기록합니다. |
onSubmit에서 값 활용하기
onSubmit은 일반 JavaScript 콜백이므로, 통합 요구사항에 맞게 캡처된 값을 원하는 방식으로 활용할 수 있습니다. onSubmit은 양식 제출 과정의 일부로 실행되기 때문에, 내부에서 호출하는 brazeBridge 호출은 랜딩 페이지를 익명으로 연 방문자에게도 정상적으로 작동합니다. 브리지 호출이 작동하는 다른 상황에 대해서는 브리지 사용 가능 여부를 참조하세요.
가장 일반적인 패턴은 랜딩 페이지에서 사용할 수 있는 Braze JavaScript 브리지를 사용하여 캡처된 값을 고객 프로필에 기록하는 것입니다:
window.brazeBridge.getUser().setCustomUserAttribute("attribute_name", value);
워크스페이스에 이미 존재하는 커스텀 속성 이름이나 새로 생성하려는 이름을 사용하세요. 전달한 값은 사용자의 프로필에 저장되며, 세분화, 개인화, 후속 메시지 트리거에 활용할 수 있습니다.
커스텀 속성에만 국한되지 않습니다. 동일한 콜백에서 brazeBridge.getUser() 메서드를 모두 호출할 수 있습니다. 예를 들어, 사용자를 구독 그룹에 추가하거나, 표준 속성을 설정하거나, 커스텀 이벤트를 기록하거나, 값을 자체 API 엔드포인트로 전송할 수 있습니다.

onSubmit 내부에서 requestImmediateDataFlush를 호출할 필요는 없습니다. 양식 제출 프로세스는 onSubmit 콜백이 완료된 후 자동으로 모든 데이터를 Braze로 플러시합니다.
예제
다음 예제는 완전하고 독립적으로 작동합니다. 각 예제는 마크업, <script> 태그, <style> 태그가 포함된 하나의 블록으로, 랜딩 페이지의 커스텀 코드(HTML) 블록 하나에 붙여넣기만 하면 됩니다. 각 스크립트 끝 부분의 registerFormInput 호출이 커스텀 입력을 Braze 양식에 연결합니다. 코드 샘플 위에 마우스를 올리고 복사 아이콘을 선택하면 복사할 수 있습니다.

이 예제들은 전적으로 방문자의 브라우저에서 실행됩니다. 스크래치 카드 예제의 경우, “보상”은 방문자의 브라우저에서 JavaScript로 선택되므로, 기술적으로 능숙한 방문자라면 해당 코드를 수정하여 원하는 결과를 얻을 수 있습니다. 리워드, 할인 또는 방문자별로 엄격한 제한이 필요한 기타 성과에 이 패턴을 의존하지 마세요. 보안이나 매출과 관련된 항목은 반드시 자체 서버에서 검증하세요.
목표: 방문자가 행복한 얼굴 또는 불행한 얼굴을 선택하면, 해당 선택이 feedback_sentiment라는 문자열 커스텀 속성에 기록됩니다.
다음을 하나의 커스텀 코드(HTML) 블록에 붙여넣으세요:
<div id="sentiment-picker" class="sentiment-picker">
<button type="button" data-sentiment="positive" aria-label="Happy">🙂</button>
<button type="button" data-sentiment="negative" aria-label="Unhappy">🙁</button>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const picker = document.getElementById("sentiment-picker");
picker.querySelectorAll("button").forEach((button) => {
button.addEventListener("click", () => {
picker.dataset.sentiment = button.dataset.sentiment;
picker.querySelectorAll("button").forEach((b) => b.classList.remove("selected"));
button.classList.add("selected");
});
});
window.brazeHelpers.forms.registerFormInput({
selector: "#sentiment-picker",
isRequired: true,
getValue: (element) => element.dataset.sentiment ?? null,
onSubmit: (value) => {
window.brazeBridge.getUser().setCustomUserAttribute("feedback_sentiment", value);
},
});
});
</script>
<style>
.sentiment-picker {
display: flex;
gap: 16px;
justify-content: center;
font-size: 40px;
}
.sentiment-picker button {
background: none;
border: 2px solid transparent;
border-radius: 12px;
cursor: pointer;
line-height: 1;
padding: 8px;
}
.sentiment-picker button.selected {
border-color: #1f2933;
}
/* Braze adds this class to the registered element when validation fails. */
.sentiment-picker.bz-validation-error {
outline: 3px solid #f94144;
outline-offset: 4px;
border-radius: 12px;
}
</style>
작동 방식: 버튼을 선택하면 해당 버튼의 data-sentiment 값이 컨테이너 요소에 저장됩니다. getValue는 Braze가 전달한 컨테이너 요소에서 해당 값을 다시 읽어옵니다. isRequired가 true이므로, 방문자가 얼굴을 선택할 때까지 양식이 제출되지 않으며, 값이 비어 있는 동안 컨테이너에 bz-validation-error 클래스가 표시됩니다. 제출 시, 선택된 값("positive" 또는 "negative")이 feedback_sentiment에 기록됩니다.
목표: 방문자가 카드를 긁어서 세 가지 할인(10% Off, 20% Off, 25% Off) 중 하나를 확인하면, 해당 할인이 scratch_off_reward라는 문자열 커스텀 속성에 기록됩니다.
이 예제는 HTML Canvas에 스크래치 카드를 그립니다. 페이지가 로드될 때 보상이 무작위로 선택되며, 방문자가 긁어서 벗겨내는 불투명 레이어 아래에 숨겨집니다. Canvas 방식 대신 원하는 스크래치 위젯을 사용할 수 있습니다. registerFormInput 호출만이 Braze에 연결됩니다. 다음을 하나의 커스텀 코드(HTML) 블록에 붙여넣으세요:
<div id="scratch-card" class="scratch-card" data-reward="">
<span class="scratch-card__reward"></span>
<canvas class="scratch-card__surface" width="300" height="150"></canvas>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const rewards = ["10% Off", "20% Off", "25% Off"];
const card = document.getElementById("scratch-card");
const label = card.querySelector(".scratch-card__reward");
const canvas = card.querySelector(".scratch-card__surface");
const ctx = canvas.getContext("2d");
// Randomly assign which reward this visitor will reveal.
const reward = rewards[Math.floor(Math.random() * rewards.length)];
label.textContent = reward;
// Paint the opaque scratch layer over the reward.
ctx.fillStyle = "#b3b3b3";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "destination-out";
let isScratching = false;
function scratchAt(event) {
const rect = canvas.getBoundingClientRect();
ctx.beginPath();
ctx.arc(event.clientX - rect.left, event.clientY - rect.top, 18, 0, Math.PI * 2);
ctx.fill();
}
canvas.addEventListener("pointerdown", () => { isScratching = true; });
canvas.addEventListener("pointermove", (event) => {
if (isScratching) scratchAt(event);
});
canvas.addEventListener("pointerup", () => {
isScratching = false;
// The visitor has scratched the card, so record the revealed reward.
card.dataset.reward = reward;
});
window.brazeHelpers.forms.registerFormInput({
selector: "#scratch-card",
isRequired: true,
getValue: (element) => element.dataset.reward || null,
onValidate: (value) => rewards.includes(value),
onSubmit: (value) => {
window.brazeBridge.getUser().setCustomUserAttribute("scratch_off_reward", value);
},
});
});
</script>
<style>
.scratch-card {
position: relative;
width: 300px;
height: 150px;
margin: 0 auto;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
/* The reward sits underneath and is revealed as the canvas is scratched away. */
.scratch-card__reward {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
font-weight: 700;
color: #1f2933;
}
.scratch-card__surface {
position: absolute;
inset: 0;
border-radius: 12px;
cursor: pointer;
touch-action: none;
}
/* Braze adds this class to the registered element when validation fails. */
.scratch-card.bz-validation-error {
outline: 3px solid #f94144;
outline-offset: 4px;
border-radius: 12px;
}
</style>
작동 방식: 페이지가 로드되면 스크립트가 세 가지 보상 중 하나를 무작위로 선택하고, Canvas 위에 불투명 레이어를 그립니다. 방문자가 Canvas를 드래그하면 "destination-out" 합성 모드가 해당 레이어를 지우고 아래의 보상을 드러냅니다. pointerup 시, 드러난 보상이 카드의 data-reward 속성에 기록됩니다. getValue는 해당 값을 읽어오고, onValidate는 세 가지 정의된 보상 중 하나인지 확인하며, isRequired는 카드를 긁기 전까지 제출을 방지합니다. 제출 시, 드러난 할인(예: "20% Off")이 scratch_off_reward에 기록됩니다.
목표: 방문자를 랜딩 페이지로 유도한 Campaign을 캡처하고, 다운스트림 리포팅 및 기여도 분석을 위해 커스텀 이벤트 속성정보로 기록합니다.
이 예제는 랜딩 페이지 양식 제출을 특정 Campaign에 기여하는 방법을 보여줍니다. 이메일, 단문 메시지 서비스 또는 WhatsApp 메시지에서 랜딩 페이지 URL에 {{campaign.${api_id}}}와 같은 Liquid 변수를 추가하면, Campaign 식별자를 랜딩 페이지로 전달할 수 있습니다. 그런 다음 커스텀 양식 블록이 URL에서 이 매개변수를 읽고, Campaign API ID를 이벤트 속성정보로 포함한 커스텀 이벤트를 기록합니다. 이를 통해 어떤 Campaign이 양식 제출을 유도하는지 더 쉽게 추적할 수 있습니다.
다음을 하나의 커스텀 코드(HTML) 블록에 붙여넣으세요:
<input type="hidden" id="campaign-attribution" value="" />
<script>
document.addEventListener("DOMContentLoaded", () => {
const hiddenInput = document.getElementById("campaign-attribution");
const campaignApiId = new URLSearchParams(window.location.search).get("campaign_api_id");
if (campaignApiId) {
hiddenInput.value = campaignApiId;
}
window.brazeHelpers.forms.registerFormInput({
selector: "#campaign-attribution",
isRequired: false,
getValue: (element) => element.value || null,
onSubmit: async (value) => {
if (!value) {
return;
}
await window.brazeBridge.logCustomEvent("landing_page_form_submitted", {
campaign_api_id: value,
});
},
});
});
</script>
작동 방식: 랜딩 페이지로 연결되는 이메일, 단문 메시지 서비스 또는 WhatsApp 메시지를 작성할 때, Liquid 템플릿을 사용하여 URL에 Campaign 식별자를 추가합니다: https://your-landing-page.com?campaign_api_id={{campaign.${api_id}}}. 방문자가 해당 메시지를 통해 랜딩 페이지에 도착하면, 스크립트가 URL에서 campaign_api_id 매개변수를 읽어 숨겨진 입력 필드에 저장합니다. 양식 제출 시, Campaign ID가 존재하면 onSubmit 콜백이 Campaign API ID를 이벤트 속성정보로 포함한 landing_page_form_submitted라는 커스텀 이벤트를 기록합니다. 이 이벤트는 Currents에 표시되며, 리포팅, 세분화 및 기여도 분석에 활용할 수 있습니다.

이 패턴을 확장하여 메시지 배리언트, 캔버스 단계 또는 기여도 목적으로 랜딩 페이지에 전달하려는 기타 Liquid 변수와 같은 추가 URL 매개변수를 캡처할 수 있습니다.
유효성 검사 및 필수 필드
입력이 폼에 제출되려면 해당 입력에 적용되는 다음 계층을 모두 통과해야 합니다.
isRequired는 비어 있지 않은 값이 있는지를 기준으로 제출을 제어합니다. 입력에 아직 값이 없을 때getValue에서null을 반환하면 Braze가 비어 있음을 인식할 수 있습니다. 빈 문자열(공백만 포함된 문자열 포함)과 빈 배열도 비어 있는 것으로 처리되지만,0과false는 값이 있는 것으로 간주됩니다.isRequired는 불리언이거나 불리언으로 리졸브되는 프로미스일 수 있으며, 입력이 유효성 검사를 거칠 때마다 다시 평가됩니다.- 네이티브 제약 조건 유효성 검사. 매칭된 요소가 표준 HTML
checkValidity()API를 지원하는 경우, 예를 들어required,pattern,min또는max가 있는 네이티브<input>요소라면, Braze가 이를 실행하고 실패 시 제출을 차단합니다. 완전히 커스텀인 비네이티브 요소(div,canvas등)의 경우 이 검사는 항상 통과하므로 사용자 로직에 간섭하지 않습니다. onValidate는 사용자 정의 규칙에 따라 제출을 제어합니다. 현재 값과 매칭된 요소를 받으며,boolean \| Promise<boolean>을 반환해야 합니다. 이는isRequired와 동일한 반환 타입으로,true는 값이 유효함을,false는 유효하지 않음을 의미합니다. 허용 값 검사, 형식 검사, 범위 검사 또는 JavaScript로 표현할 수 있는 모든 로직에 사용할 수 있습니다.
오류 스타일링
입력이 유효성 검사를 통과하지 못하면 Braze는 selector 또는 element로 매칭된 요소에 CSS 클래스 bz-validation-error를 추가하고, 입력이 유효해지면 해당 클래스를 제거합니다. 유효하지 않은 상태는 자유롭게 스타일링할 수 있습니다. 예를 들어 문제가 있는 입력에 주목을 끌기 위한 아웃라인이나 테두리를 적용하려면, 요소와 bz-validation-error 클래스를 결합하여 타겟팅하는 규칙을 추가하세요.
#my-custom-input.bz-validation-error {
outline: 3px solid #f94144;
outline-offset: 4px;
}
오류 상태 스타일링은 선택 사항이지만 권장됩니다. 이를 통해 방문자가 어떤 커스텀 입력이 제출을 차단하고 있는지 확인할 수 있습니다. 이 페이지의 각 예제에는 bz-validation-error 규칙이 포함되어 있습니다.
모범 사례
- 안정적이고 고유한 SELECTOR를 사용하세요.
id가 가장 안전한 선택입니다. 둘 이상의 요소와 일치할 수 있는 SELECTOR는 피하세요. - 값이 없을 때는 빈 문자열이나
undefined가 아닌null을 반환하여 필수 항목 검사가 예측 가능하게 동작하도록 하세요. 빈 문자열과 빈 배열도 비어 있는 것으로 처리되지만,null이 “값 없음”을 나타내는 가장 명확한 신호입니다. getValue는 가볍고 동기적으로 유지하세요. Braze가 이 함수를 여러 번 호출할 수 있으므로, 무거운 작업을 수행하기보다는 현재 값을 읽어서 반환하는 방식이어야 합니다.bz-validation-error상태에 스타일을 적용하여 방문자가 어떤 커스텀 입력이 제출을 차단하고 있는지 확인할 수 있도록 하세요.- 커스텀 속성 이름을 미리 정의하고 일관되게 유지하여 나중에 데이터를 기반으로 안정적으로 세분화할 수 있도록 하세요.
- 전체 제출 과정을 테스트하세요. 제출 후 고객 프로필에 속성이 표시되는지, 필수 항목 및 유효성 검사 규칙이 예상대로 제출을 차단하는지 확인하세요.
문제 해결
아무것도 선택하지 않았는데도 양식이 제출됨
isRequired가 true로 설정되어 있는지 확인하세요. Braze는 null, undefined, 빈 문자열(공백만 포함된 문자열 포함), 빈 배열을 값이 없는 것으로 처리합니다. 아무것도 선택하지 않았을 때 getValue가 다른 값(예: 비어 있지 않은 기본값이나 입력 안내)을 반환하는 경우, 필수 항목 검사에서 이를 감지하지 못합니다.
값이 프로필에 표시되지 않음
onSubmit이 올바른 속성 이름으로 window.brazeBridge.getUser().setCustomUserAttribute를 호출하는지, 그리고 Custom Code 블록이 양식과 동일한 랜딩 페이지에 있는지 확인하세요.
등록이 아무 동작도 하지 않는 것처럼 보임
registerFormInput이 실행될 때 SELECTOR가 DOM에 존재하는 요소와 일치하는지, 그리고 해당 요소가 렌더링된 후에 스크립트가 실행되는지 확인하세요. 그런 다음 브라우저의 개발자 콘솔을 열어보세요. registerFormInput은 구성을 검증하며, 문제가 있는 경우(예: getValue 누락, 유효하지 않은 CSS SELECTOR, 잘못된 타입의 속성정보) 등록을 무시하고 [brazeHelpers.forms.registerFormInput] 접두사가 붙은 경고를 로그에 기록하여 무엇이 유효하지 않았는지 설명합니다.