SuitsDocs
콘텐츠블록

아티팩트 SDK

아티팩트 블록에서 사용할 수 있는 Suits SDK API를 알아보세요.

개요

Artifact 블록의 iframe 안에서는 window.suits 객체를 통해 Suits SDK를 사용할 수 있습니다. 별도의 import나 설치 없이, 코드 작성 즉시 사용 가능합니다.

// iframe 내부 어디서든 바로 사용
const rows = await suits.fetchRows("dm_abc123");
suits.toast.success("데이터를 불러왔습니다.");

Suits SDK는 Artifact 블록의 iframe에 자동으로 주입됩니다. 별도의 스크립트 로드나 초기화가 필요하지 않습니다.


초기화 데이터

window.suits 객체에는 블록이 로드될 때 다음 데이터가 자동으로 주입됩니다.

속성타입설명
suits.user{ id: string, name: string | null }현재 로그인한 사용자 정보
suits.theme{ mode: 'light' | 'dark' }현재 테마 모드
suits.block{ id: string, workspaceId: string, pageId: string }현재 블록 정보
suits.allowedWorkflows[{ id: string, alias: string }]블록에 등록된 워크플로우 목록
suits.globalFilters[{ id, name, type, value }]페이지의 글로벌 필터 현재 값
// 초기화 데이터 접근 예시
console.log(suits.user.name);          // "홍길동"
console.log(suits.theme.mode);         // "light" 또는 "dark"
console.log(suits.block.workspaceId);  // "ws_abc123"

데이터 읽기 API

데이터를 읽으려면 블록 설정에서 해당 데이터모델 또는 통계 정의를 데이터소스로 등록해야 합니다. read 이상의 접근 권한이 필요합니다.

suits.fetchRows(dataModelId, options?)

데이터모델에서 행 데이터를 조회합니다.

매개변수

이름타입필수설명
dataModelIdstring데이터모델 ID
optionsobject아니오조회 옵션

반환값

Promise<{ rows: [...] }>

예시

const result = await suits.fetchRows("dm_abc123");
console.log(result.rows); // [{ id: "row_1", data: { ... } }, ...]

suits.fetchStatData(statDefId, options?)

통계 정의의 실행 결과 데이터를 조회합니다.

매개변수

이름타입필수설명
statDefIdstring통계 정의 ID
optionsobject아니오조회 옵션

반환값

Promise<{ ... }> // 통계 정의에 따른 결과 데이터

예시

const stats = await suits.fetchStatData("stat_monthly_revenue");
console.log(stats);

suits.fetchDataModelSchema(dataModelId)

데이터모델의 프로퍼티(컬럼) 정보를 조회합니다. 컬럼 이름, 타입, 옵션 등의 스키마 정보를 확인할 때 사용합니다.

매개변수

이름타입필수설명
dataModelIdstring데이터모델 ID

반환값

Promise<{ properties: [{ id, name, type, ... }] }>

예시

const schema = await suits.fetchDataModelSchema("dm_abc123");
schema.properties.forEach((prop) => {
  console.log(`${prop.name} (${prop.type})`);
});
// 회사명 (TEXT)
// 매출 (CURRENCY)
// 상태 (SELECT)

suits.getGlobalFilters()

페이지에 설정된 글로벌 필터의 현재 값을 조회합니다.

반환값

Promise<[{ id: string, name: string, type: string, value: any }]>

예시

const filters = await suits.getGlobalFilters();
filters.forEach((f) => {
  console.log(`${f.name}: ${f.value}`);
});
// 기간: 2026-03
// 팀: 영업1팀

데이터 쓰기 API

데이터를 쓰려면 블록 설정에서 해당 데이터모델을 readwrite 권한으로 데이터소스에 등록해야 합니다.

read 권한으로 등록된 데이터소스에 쓰기 API를 호출하면 "Unauthorized data source" 에러가 발생합니다.

suits.addRow(dataModelId, values)

데이터모델에 새 행을 추가합니다.

매개변수

이름타입필수설명
dataModelIdstring데이터모델 ID
valuesobject프로퍼티 ID를 키로 하는 값 객체

예시

const result = await suits.addRow("dm_abc123", {
  prop_001: "신규 고객사",
  prop_002: "[email protected]",
  prop_003: 50000000,
  prop_004: "lead",
});
console.log(result.rowId); // 생성된 행 ID

suits.updateRow(dataModelId, rowId, values)

기존 행의 데이터를 수정합니다. 변경할 프로퍼티만 포함하면 됩니다.

매개변수

이름타입필수설명
dataModelIdstring데이터모델 ID
rowIdstring수정할 행 ID
valuesobject변경할 프로퍼티 ID와 값

예시

await suits.updateRow("dm_abc123", "row_111", {
  prop_004: "active",
  prop_003: 75000000,
});
suits.toast.success("상태가 업데이트되었습니다.");

suits.deleteRow(dataModelId, rowId)

행을 삭제합니다.

매개변수

이름타입필수설명
dataModelIdstring데이터모델 ID
rowIdstring삭제할 행 ID

예시

await suits.deleteRow("dm_abc123", "row_111");
suits.toast("행이 삭제되었습니다.");

워크플로우 API

워크플로우를 실행하려면 블록 설정에서 해당 워크플로우를 허용 워크플로우(allowedWorkflows)에 등록해야 합니다.

등록되지 않은 워크플로우를 실행하려 하면 "Unauthorized workflow" 에러가 발생합니다.

suits.executeWorkflow(workflowId, triggerData?)

워크플로우를 실행합니다. Fire-and-forget 방식으로, 실행을 요청한 뒤 결과를 기다리지 않습니다.

매개변수

이름타입필수설명
workflowIdstring워크플로우 ID
triggerDataobject아니오워크플로우에 전달할 데이터

예시

const workflowId = suits.getWorkflowId("sendNotification");
await suits.executeWorkflow(workflowId, {
  recipient: "[email protected]",
  message: "주문이 접수되었습니다.",
});
suits.toast.info("알림을 발송 중입니다.");

suits.executeWorkflowAndWait(workflowId, triggerData?, options?)

워크플로우를 실행하고, 완료될 때까지 대기합니다. SSE 스트림을 통해 결과를 실시간으로 수신하며, 마지막 노드의 실행 결과가 반환됩니다.

매개변수

이름타입필수설명
workflowIdstring워크플로우 ID
triggerDataobject아니오워크플로우에 전달할 데이터
optionsobject아니오실행 옵션

options 속성

이름타입기본값설명
timeoutnumber360000 (6분)타임아웃 (밀리초)

반환값

Promise<lastNodeResult> // 워크플로우 마지막 노드의 실행 결과

예시

const workflowId = suits.getWorkflowId("analyzeData");
const result = await suits.executeWorkflowAndWait(workflowId, {
  dataModelId: "dm_abc123",
  period: "2026-03",
});
console.log(result); // 마지막 노드의 실행 결과

// 타임아웃 지정
const result2 = await suits.executeWorkflowAndWait(workflowId, null, {
  timeout: 120000, // 2분
});

장시간 실행되는 워크플로우의 경우, 기본 타임아웃(6분)을 초과하면 에러가 발생합니다. 필요에 따라 timeout 옵션을 조정하세요.


suits.getWorkflowId(alias)

워크플로우의 별칭(alias)으로 ID를 조회합니다. allowedWorkflows에 등록된 워크플로우 중에서 검색합니다.

매개변수

이름타입필수설명
aliasstring워크플로우 별칭

반환값

string | null // 워크플로우 ID 또는 null (일치하는 별칭이 없을 때)

예시

const workflowId = suits.getWorkflowId("processOrder");
if (workflowId) {
  await suits.executeWorkflow(workflowId, { orderId: "12345" });
} else {
  suits.toast.error("워크플로우를 찾을 수 없습니다.");
}

getWorkflowId는 동기 함수입니다. await 없이 바로 사용할 수 있습니다.


UI API

suits.toast(message, options?)

토스트 알림을 표시합니다. 4가지 변형을 제공합니다.

메서드설명
suits.toast(message, options?)기본 토스트
suits.toast.success(message, options?)성공 토스트
suits.toast.error(message, options?)에러 토스트
suits.toast.info(message, options?)정보 토스트
suits.toast.warning(message, options?)경고 토스트

options 속성

이름타입설명
descriptionstring토스트 하단에 표시할 부가 설명

예시

suits.toast("저장되었습니다.");
suits.toast.success("주문이 완료되었습니다.", {
  description: "주문번호: ORD-2026-001",
});
suits.toast.error("저장에 실패했습니다.");
suits.toast.info("데이터를 불러오는 중입니다.");
suits.toast.warning("입력값을 확인해주세요.");

suits.openModal(options)

모달 창을 엽니다. 모달 내부도 독립된 iframe으로 실행되며, Suits SDK를 동일하게 사용할 수 있습니다.

options 속성

이름타입필수기본값설명
titlestring-모달 제목
htmlstring-모달 본문 HTML
cssstring아니오""모달 스타일 CSS
jsstring아니오""모달 동작 JavaScript
widthnumber아니오640모달 너비 (px)
cdnDependenciesstring[]아니오[]CDN 라이브러리 URL 목록

반환값

Promise<any> // suits.closeModal(result)로 전달된 값

예시

const result = await suits.openModal({
  title: "고객 정보 입력",
  html: `
    <form id="form">
      <label>이름</label>
      <input type="text" id="name" class="bc-input" />
      <label>이메일</label>
      <input type="email" id="email" class="bc-input" />
      <button type="submit" class="bc-button">확인</button>
    </form>
  `,
  js: `
    document.getElementById("form").addEventListener("submit", (e) => {
      e.preventDefault();
      suits.closeModal({
        name: document.getElementById("name").value,
        email: document.getElementById("email").value,
      });
    });
  `,
  width: 480,
});

// result: { name: "홍길동", email: "[email protected]" }
if (result) {
  await suits.addRow("dm_customers", {
    prop_001: result.name,
    prop_002: result.email,
  });
}

모달 내에서 suits.openModal을 다시 호출할 수 없습니다. 모달 중첩은 지원되지 않습니다.


suits.closeModal(result?)

현재 열린 모달을 닫습니다. 모달 iframe 내부에서만 호출할 수 있습니다.

매개변수

이름타입필수설명
resultany아니오openModal의 Promise에 전달할 반환값

예시

// 모달 내부의 JS에서 호출
suits.closeModal({ confirmed: true, selectedId: "row_123" });

// 취소 버튼
document.getElementById("cancel").addEventListener("click", () => {
  suits.closeModal(null);
});

이벤트 리스너

suits.onGlobalFiltersChanged

페이지의 글로벌 필터 값이 변경될 때 호출되는 콜백입니다. 함수를 할당하면 필터가 변경될 때마다 자동으로 호출됩니다.

suits.onGlobalFiltersChanged = (filters) => {
  console.log("필터 변경:", filters);
  // filters: [{ id, name, type, value }, ...]

  // 필터 값에 따라 데이터를 다시 조회
  const periodFilter = filters.find((f) => f.name === "기간");
  if (periodFilter) {
    loadData(periodFilter.value);
  }
};

테마 변경 감지

테마가 변경되면 suits.theme.mode가 자동으로 업데이트되고, <html> 태그에 dark 클래스가 토글됩니다. CSS 변수를 사용하고 있다면 별도 처리 없이 자동으로 테마가 반영됩니다.

커스텀 로직이 필요한 경우에는 CSS의 .dark 선택자를 활용하세요.

/* 라이트 모드 */
.chart-line { stroke: #333; }

/* 다크 모드 */
.dark .chart-line { stroke: #eee; }
// JavaScript에서 현재 테마 확인
if (suits.theme.mode === "dark") {
  // 다크 모드 전용 로직
}

사용자 정보

suits.getCurrentUser()

현재 로그인한 사용자의 정보를 반환합니다.

반환값

Promise<{ id: string, name: string | null } | null>

예시

const user = await suits.getCurrentUser();
if (user) {
  console.log(`${user.name}님, 환영합니다.`);
}

Rate Limiting

SDK API 호출에는 속도 제한이 적용됩니다. 제한을 초과하면 "Rate limit exceeded" 에러가 발생합니다.

카테고리해당 API제한
READfetchRows, fetchStatData, fetchDataModelSchema, getGlobalFilters5초당 최대 10회
WRITEaddRow, updateRow, deleteRow, executeWorkflow, executeWorkflowAndWait10초당 최대 5회
TOASTtoast, toast.success, toast.error, toast.info, toast.warning5초당 최대 5회 (전역)

TOAST의 Rate Limit은 전역으로 적용됩니다. 같은 페이지의 여러 Artifact 블록이 공유하므로, 한 페이지에 여러 블록이 있을 경우 전체 합산으로 제한됩니다.


예제: 고객 목록 대시보드

데이터모델에서 고객 데이터를 조회하여 테이블로 표시하고, 행 추가 기능을 제공하는 예제입니다.

HTML

<div class="container">
  <h2>고객 목록</h2>
  <button id="add-btn" class="bc-button">고객 추가</button>
  <table class="bc-table">
    <thead>
      <tr>
        <th>회사명</th>
        <th>이메일</th>
        <th>상태</th>
      </tr>
    </thead>
    <tbody id="table-body"></tbody>
  </table>
</div>

CSS

.container {
  max-width: 800px;
  margin: 0 auto;
}

h2 {
  margin-bottom: 12px;
}

#add-btn {
  margin-bottom: 16px;
}

JS

const DATA_MODEL_ID = "dm_abc123";

// 데이터 조회 및 테이블 렌더링
async function loadCustomers() {
  try {
    const { rows } = await suits.fetchRows(DATA_MODEL_ID);
    const tbody = document.getElementById("table-body");
    tbody.innerHTML = rows
      .map(
        (row) => `
      <tr>
        <td>${row.data.prop_001 || ""}</td>
        <td>${row.data.prop_002 || ""}</td>
        <td>${row.data.prop_004 || ""}</td>
      </tr>
    `
      )
      .join("");
  } catch (err) {
    suits.toast.error("데이터를 불러오지 못했습니다.", {
      description: err.message,
    });
  }
}

// 고객 추가 버튼
document.getElementById("add-btn").addEventListener("click", async () => {
  const result = await suits.openModal({
    title: "고객 추가",
    html: `
      <form id="form" style="display:flex;flex-direction:column;gap:12px;">
        <input id="name" class="bc-input" placeholder="회사명" required />
        <input id="email" class="bc-input" placeholder="이메일" type="email" required />
        <div style="display:flex;gap:8px;justify-content:flex-end;">
          <button type="button" id="cancel" class="bc-button bc-button-outline">취소</button>
          <button type="submit" class="bc-button">추가</button>
        </div>
      </form>
    `,
    js: `
      document.getElementById("form").addEventListener("submit", (e) => {
        e.preventDefault();
        suits.closeModal({
          name: document.getElementById("name").value,
          email: document.getElementById("email").value,
        });
      });
      document.getElementById("cancel").addEventListener("click", () => {
        suits.closeModal(null);
      });
    `,
    width: 400,
  });

  if (result) {
    try {
      await suits.addRow(DATA_MODEL_ID, {
        prop_001: result.name,
        prop_002: result.email,
        prop_004: "lead",
      });
      suits.toast.success("고객이 추가되었습니다.");
      loadCustomers(); // 테이블 새로고침
    } catch (err) {
      suits.toast.error("고객 추가에 실패했습니다.");
    }
  }
});

// 글로벌 필터 변경 시 데이터 새로고침
suits.onGlobalFiltersChanged = () => {
  loadCustomers();
};

// 초기 로드
loadCustomers();

다음 단계