본문 바로가기

플러그인 API

플러그인은 매니페스트와 HTML 문서 한 장으로 이루어집니다. 에디터는 이 문서를 떠 있는 패널에 열고, 문서는 이 페이지의 함수로만 프로젝트를 읽고 고칩니다.

참조고급약 15분웹데스크톱

플러그인이 돌아가는 곳

플러그인은 .sshowplugin 파일 하나입니다. 이름만 바꾼 zip 파일이고, 안에는 매니페스트 plugin.json과 화면 문서 하나가 들어갑니다. 아이콘은 넣어도 되고 빼도 됩니다. 에디터는 이 파일들만 꺼내 씁니다. 플러그인 13종도 같은 형식입니다.

목록에서 플러그인을 누르면 에디터는 목록 옆에 따로 떠 있는 패널을 열고, 그 안의 샌드박스 프레임에 화면 문서를 띄웁니다. 문서의 스크립트보다 먼저 SSHOWPlugin SDK와 에디터의 테마 스타일을 넣어 두므로, 스크립트는 처음부터 SSHOWPlugin.connect()를 부를 수 있습니다.

플러그인은 아래 표의 함수로만 에디터에 닿습니다. 모든 함수는 Promise를 돌려주고, 문서를 바꾸는 길은 applyActions 하나입니다. AI 어시스턴트와 AI 앱 연결 (MCP)도 같은 액션으로 문서를 고칩니다.

SSHOWPlugin.connect()가 돌려주는 핸들의 내용
핸들의 이름담긴 것
apiVersion이 에디터의 플러그인 API 버전입니다. 지금은 1입니다.
engineVersion에디터 엔진의 버전입니다. 보여 주는 데만 쓰세요.
document문서 읽기 · 선택 · 씬 바꾸기 · 쓰기. 읽기와 선택 · 쓰기: applyActions
assets프로젝트 파일의 바이트 읽기와 등록. 에셋
storage플러그인 설정 보관. 저장소
events문서 · 선택 · 시계가 바뀔 때의 알림. 이벤트
ui패널 크기와 테마. 패널 화면과 키보드

첫 플러그인 쓰기

텍스트 편집기와 zip 압축 도구가 있으면 됩니다. 버튼을 누르면 캔버스에 텍스트 하나를 넣는 Hello 플러그인을 만듭니다.

  1. plugin.json 쓰기

    빈 폴더를 만들고, 그 안에 plugin.json을 만드세요. 아래 표의 첫 줄을 그대로 옮겨 적으세요. 항목마다의 규칙은 매니페스트에 있습니다.

  2. ui.html 쓰기

    같은 폴더에 ui.html을 만드세요. id가 insert인 button 요소 하나를 두세요. 그 아래에 script 요소를 두고, 아래 표의 스크립트 1 · 2를 차례로 적으세요. 다른 파일은 불러올 수 없으므로 스타일과 스크립트는 모두 이 파일 안에 씁니다.

Hello 플러그인의 두 파일에 쓸 내용
파일쓸 내용
plugin.json{ "id": "com.example.hello", "name": "Hello", "version": "1.0.0", "api": 1, "main": "ui.html", "author": "Example" }
ui.html · 화면button 요소 하나입니다. id는 insert, 글자는 Insert입니다. script 요소는 이 버튼 아래에 둡니다. html · head · body 요소는 없어도 됩니다.
ui.html · 스크립트 1const action = { op: 'create_object', type: 'text', config: { name: 'Hello', data: { text: 'Hello, SSHOW', fontSize: 48, autoSize: true }, transform: { x: 200, y: 200 } } };
ui.html · 스크립트 2(async () => { const api = await SSHOWPlugin.connect(); document.querySelector('#insert').onclick = () => api.document.applyActions([action], 'Hello'); })();

가져와 실행하기

  1. .sshowplugin으로 묶기

    폴더를 열고 plugin.json과 ui.html을 골라 zip으로 압축하세요. 만든 파일의 이름을 hello.sshowplugin으로 바꾸세요. 가져올 때는 .sshowplugin으로 끝나는 파일만 고를 수 있습니다. 확장자를 숨긴 Windows에서는 끝에 .zip이 남지 않았는지 확인하세요.

  2. 에디터로 가져오기

    에디터 오른쪽 위에서 전구 모양 플러그인 버튼을 누르세요. 패널 제목 줄의 + 모양 플러그인 가져오기 버튼을 누르고 hello.sshowplugin을 고르세요. 목록 맨 아래에 Hello가 생깁니다. 보이지 않으면 목록을 끝까지 내리세요.

    + 모양 버튼으로 hello.sshowplugin을 고른 뒤 목록을 끝까지 내리자 맨 아래에 Hello가 보입니다. 영상: 전구 모양 플러그인 버튼으로 패널을 열고 제목 줄의 + 버튼으로 hello.sshowplugin을 고른 뒤, 목록을 끝까지 내리자 맨 아래에 Hello가 보입니다.
  3. 실행하기

    목록에서 Hello를 누르세요. 목록 옆에 떠 있는 패널이 열리고 ui.html이 보입니다. Insert를 누르면 캔버스에 'Hello, SSHOW'가 생깁니다.

    Hello를 누르자 패널이 열리고, Insert를 누르자 캔버스에 'Hello, SSHOW'가 생깁니다. 영상: 플러그인 목록에서 Hello를 누르자 떠 있는 패널에 플러그인 화면이 열리고, Insert 버튼을 누르자 캔버스에 'Hello, SSHOW' 텍스트가 생깁니다.

두 파일은 zip의 맨 위에 있어야 합니다

에디터는 zip 안에서 plugin.json이라는 이름을 그대로 찾습니다. 폴더째 압축하면 이름이 'hello/plugin.json'이 되어 '이 파일을 .sshowplugin 패키지로 읽을 수 없습니다.'가 나옵니다. 폴더를 열고 안의 파일들을 골라 압축하세요.

매니페스트 (plugin.json)

다섯 항목은 꼭 있어야 하고, 세 항목은 넣지 않아도 됩니다. 모르는 항목은 무시하므로, 새 항목을 더한 매니페스트도 예전 에디터에서 그대로 열립니다.

실행 중인 플러그인 패널의 제목 줄에서 동그라미 안에 i가 있는 플러그인 정보 버튼을 누르면 버전 · 작성자 · ID와 설명이 보입니다.

매니페스트 항목과 규칙
항목필수규칙
id필수소문자 역도메인 형식입니다(com.example.hello). a–z · 0–9 · 마침표 · 하이픈만 쓰고, 첫 글자는 영문 소문자나 숫자입니다. 마침표 둘을 잇따라 쓸 수 없습니다. 게시할 때는 100자까지이고, 처음 제출한 계정이 계속 소유합니다.
name필수목록과 패널 제목에 보이는 이름입니다. 게시할 때는 100자까지입니다.
version필수숫자 셋을 마침표로 이은 x.y.z입니다(1.0.0).
api필수플러그인 API 버전입니다. 지금은 1이고, 에디터와 다르면 가져오지 않습니다.
main필수화면 문서의 파일 이름입니다(ui.html).
description선택목록과 플러그인 정보에 보이는 설명입니다. 게시할 때는 2000자까지입니다.
author선택목록과 플러그인 정보에 보이는 작성자입니다. 게시할 때는 100자까지이고, 게시된 목록의 작성자는 이 값이 아니라 제출한 계정입니다.
icon선택패키지 안 아이콘 파일의 이름입니다. png · svg · jpg · jpeg · webp를 씁니다.

읽기와 선택

읽기 함수는 모두 그 순간의 사본을 돌려줍니다. 사본을 고쳐도 문서는 바뀌지 않습니다. 바꾸려면 applyActions로 보내세요.

이미지 · 영상 · 오디오의 data.src는 프로젝트 에셋의 asset:// 주소로 오고, 바이트는 assets.get으로 읽습니다. 에셋이 아닌 긴 src(data: 주소 등)는 길이만 적은 자리표시(src len=… kind=…)로 오므로 다시 보내지 마세요. 인터랙션의 웹 요청은 주소에 담긴 값과 헤더 값이 가려진 채로 옵니다.

읽기와 선택 함수
함수돌려주는 것 · 하는 일
document.getState(){ canvas, activeSceneId, scenes, fonts }입니다. 캔버스 크기, 지금 씬의 id, 씬 목록, 문서에 든 폰트 이름입니다. 지금 씬은 오브젝트 전체와 씬의 motion · interaction을 담고, 다른 씬은 오브젝트마다 id · type · name만 담습니다.
document.getObject(id)지금 씬에 있는 오브젝트 하나의 사본입니다. 다른 씬에 있거나 없으면 null입니다. 다른 씬의 것은 setActiveScene으로 그 씬을 연 뒤 읽으세요.
document.getSelection()지금 선택한 오브젝트들의 사본 배열입니다.
document.getTextLayout(id)텍스트 오브젝트의 { char, word, line }입니다. 각각 { text, line, word, localX, localY, width, height }의 배열입니다. 화면에 그린 것과 같은 측정이라 자간과 줄바꿈이 맞고, 영역은 변형 전 오브젝트 자기 좌표입니다. text에는 변수 값이 채워진 글자가 오고, line · word는 읽는 순서의 번호입니다. 지금 씬의 텍스트가 아니면 null, 아직 한 번도 그리지 않았으면 빈 배열입니다.
document.getTimelineTime()에디터의 애니메이션 시계(ms)입니다. 애니메이션 모드에서는 플레이헤드의 자리, 디자인 모드에서는 0입니다. 타임라인에 쓰는 작업은 이 값에서 시작하고, 쓰기 직전에 다시 읽으세요.
document.setSelection(ids)지금 씬의 오브젝트를 에디터에서 선택합니다. 방금 만든 오브젝트를 선택된 채로 넘길 때 씁니다. 없는 id는 조용히 빠지고, 실행 취소 기록에는 남지 않습니다.
document.setActiveScene(sceneId)지금 씬을 바꿉니다. 모르는 id는 거부하므로, 엉뚱한 씬에 계속 쓰는 일이 생기지 않습니다.

쓰기: applyActions

document.applyActions(actions, label)은 문서를 바꾸는 하나뿐인 길입니다. 배열의 액션을 적힌 순서대로 적용하고, 그 전체가 실행 취소 한 단계(⌘Z 한 번)가 됩니다. label은 그 단계의 이름이고, 비우면 플러그인 이름이 들어갑니다.

돌려받는 값은 { applied, skipped, ids }입니다. applied는 적용한 액션의 수입니다. skipped는 건너뛴 액션마다 { index, op, id, code, reason }을 담습니다. 잘못된 액션이 있어도 나머지는 그대로 적용됩니다.

만드는 액션의 config.id는 이번 호출에서만 쓰는 별칭입니다. 같은 배열의 뒤쪽 액션은 이 별칭으로 그 오브젝트를 가리킬 수 있습니다. 실제 id는 엔진이 정하고, ids.objects · ids.scenes · ids.variables에 별칭과 짝지어 옵니다. 다음 호출부터는 실제 id를 쓰세요. 별칭은 'bar'처럼 지으세요. o1 · s2처럼 엔진 id와 같은 꼴로 지으면, 마침 같은 id가 만들어질 때 ids에서 빠집니다.

쓰기는 에디터에서 손으로 고친 것과 같은 길로 들어갑니다. 애니메이션 모드에서 자동 키프레임이 켜져 있으면, 위치 · 크기 · 불투명도 같은 값은 플레이헤드 자리의 키프레임으로 기록됩니다. 키프레임을 고치는 법은 키프레임 다루기에 있습니다.

막대 셋과 이름표 셋을 한 번에 넣은 호출이 ⌘Z 한 번에 모두 사라집니다. 영상: Chart 플러그인에서 Insert chart를 누르자 막대 셋과 이름표 셋이 한 번에 생기고, ⌘Z를 한 번 누르자 여섯 개가 함께 사라집니다.

액션 25가지

applyActions에 담을 수 있는 op와 그 항목
op항목 (+ 선택)하는 일
create_objecttype, config (+ sceneId, options)오브젝트를 만듭니다. type은 rect · circle · path · text · image · video · audio · group · frame · table입니다. options.parentObjectId를 주면 그룹이나 프레임 안에 만들고, options.index로 목록의 자리를 정합니다.
update_objectid, set (+ sceneId)set에 담은 키만 바꿉니다. set 키와 합치는 방식
delete_objectid (+ sceneId)오브젝트를 지웁니다. 표 칸의 텍스트를 지우면 칸은 남고 글자만 비워집니다.
duplicate_objectid (+ sceneId, options)오브젝트를 복제합니다.
move_objectid (+ sceneId, options)options.parentObjectId로 다른 그룹 · 프레임 안에 넣고, options.index로 그 안의 자리를 정합니다. options.to는 front · back · forward · backward로 앞뒤 순서를 바꿉니다.
group_objectsids (+ config, sceneId)둘 이상의 오브젝트를 그룹으로 묶습니다. 같은 호출에서 만든 별칭도 넣을 수 있습니다.
ungroupid (+ sceneId)그룹을 풉니다.
convert_to_pathid (+ sceneId)오브젝트를 패스로 바꿉니다.
insert_table_rowsid (+ index, count, values, sceneId)표에 행을 넣습니다. index를 빼면 마지막 행 뒤에 넣고, values로 새 칸의 글자를 줍니다.
insert_table_columnsid (+ index, count, values, sceneId)표에 열을 넣습니다.
delete_table_rowsid, index (+ count, sceneId)행을 지웁니다. count는 기본 1입니다.
delete_table_columnsid, index (+ count, sceneId)열을 지웁니다.
merge_table_cellsid, range (+ sceneId)range { rowIndex, colIndex, rowSpan, colSpan }의 칸을 합칩니다. 두 칸 이상이고, 모두 텍스트 칸이어야 합니다.
unmerge_table_cellsid, range (+ sceneId)range 안의 합친 칸을 나눕니다.
create_sceneconfig (+ options)빈 씬을 만들고, 에디터는 그 씬으로 넘어갑니다. 오브젝트는 create_object의 sceneId에 이 씬의 id나 별칭을 주어 넣습니다.
update_sceneset (+ sceneId)씬의 키를 바꿉니다.
delete_scene(+ sceneId)씬을 지웁니다.
duplicate_scene(+ sceneId)씬을 복제하고, 에디터는 복제한 씬으로 넘어갑니다.
move_scenenewIndex (+ sceneId)씬의 순서를 바꿉니다.
set_scene_sizesize: { width, height }캔버스 크기를 바꿉니다. 크기는 문서에 하나뿐이라 모든 씬에 적용됩니다.
create_variableconfig (+ options)변수를 만듭니다.
update_variablevariableId, set변수의 키를 바꿉니다.
delete_variablevariableId변수를 지웁니다.
move_variablevariableId, newIndex변수의 순서를 바꿉니다.
set_documentset문서의 이름 · 설명 · 노트를 바꿉니다.

set 키와 합치는 방식

set에 모르는 키가 하나라도 있으면 그 액션 전체를 건너뛰고, 이유를 skipped에 담습니다. sceneId를 빼면 어느 액션이든 호출할 때의 지금 씬이 대상입니다. create_scene · duplicate_scene 뒤에는 지금 씬이 새 씬으로 바뀌므로, 다음 호출에서 원래 씬에 쓰려면 sceneId를 주세요.

액션마다 set에 쓸 수 있는 키
set쓸 수 있는 키
update_objectname · description · size · transform · distort · layout · style · opacity · blendMode · locked · visible · motion · interaction · data
update_scenename · description · notes · style · visible · motion · interaction · data · clip
update_variablename · description · type · value
set_documentname · description · notes
set 키마다 지금 값과 합치는 방식
키합치는 방식
transform · size · data · layout보낸 하위 키만 바뀌고, 나머지는 그대로 남습니다.
style · distort통째로 바뀝니다. style을 보낼 때는 fills · strokes · effects를 언제나 모두 보내세요.
motiontransitions와 animations 단위로 바뀝니다. animations만 보내면 transitions는 그대로 남고, 반대도 같습니다. 키프레임 하나를 고치려면 그 묶음 전체를 읽어 고친 뒤 통째로 보내세요.
interaction규칙 전체가 바뀝니다. 지금 규칙을 읽어 고친 뒤 보내세요. 웹 요청 액션은 새로 쓸 수 없고, 인터랙션 패널에서 손으로 만듭니다. 만드는 법은 요청과 변수에 있습니다. 이미 있는 요청은 읽은 그대로 두면 지켜집니다.

보낸 값이 바뀌거나 거부되는 경우

applyActions가 보낸 값을 고치거나 거부하는 경우
보낸 것받는 방식
transform.rotateX · rotateY · rotateZ도 단위로 받아 라디안으로 저장합니다. 예전 이름 rotate는 rotateZ로 읽습니다. 읽기 결과와 motion 트랙의 각도는 라디안이므로, 읽은 각도를 set으로 돌려보낼 때는 도로 바꾸세요.
type 없이 color만 있는 칠type을 'solid'로 채웁니다. 고칠 수 없는 칠과 효과는 버립니다.
data.text 안의 \n · \t 두 글자실제 줄바꿈과 탭이 됩니다.
data.src · 이미지 채우기의 src프로젝트 에셋의 asset:// 주소만 받습니다. http(s) 주소나 data: 주소를 주면 그 액션을 건너뜁니다. 파일은 에셋으로 먼저 등록하세요.

예: 만들고, 이어서 고치고, 선택하기

만드는 액션과 그 오브젝트를 고치는 액션을 한 호출에 담는 예입니다. 'bar'는 이 호출에서만 쓰는 별칭이고, 호출이 끝나면 ids.objects.bar가 실제 id입니다.

아래 코드는 Hello의 스크립트 2처럼 async 함수 안, connect()로 받은 api가 있는 곳에 씁니다. await를 함수 밖 맨 위에 쓰면 문법 오류가 납니다.

transform.x · y는 오브젝트 앵커의 자리이고, 도형의 앵커는 기본으로 가운데(anchorX · anchorY 0.5)입니다. 그래서 이 막대는 (340, 180)부터 (460, 420)까지 놓입니다.

액션 두 개를 한 호출로 보내고 결과를 선택하는 코드
순서코드
액션 만들기const actions = [ { op: 'create_object', type: 'rect', config: { id: 'bar', name: 'bar', size: { width: 120, height: 240 }, transform: { x: 400, y: 300 }, style: { fills: [ { type: 'solid', color: '#8A8A8E' } ], strokes: [], effects: [] } } }, { op: 'update_object', id: 'bar', set: { opacity: 0.8 } } ];
보내기const { applied, skipped, ids } = await api.document.applyActions(actions, 'Add bar');
건너뛴 것 보기if (skipped.length) console.warn(skipped);
선택해 넘기기await api.document.setSelection([ids.objects.bar]);

에셋

이미지 · 영상 · 오디오의 바이트는 asset:// 주소로 읽고, 새 바이트는 등록해 주소를 받습니다. 선택한 이미지를 고치는 플러그인은 먼저 getSelection으로 이미지의 data.src를 읽습니다. assets.get으로 바이트를 받아 고친 뒤 assets.register로 등록하고, update_object로 data.src를 새 주소로 바꿉니다.

받은 주소는 바로 액션에 쓰세요. 어디에도 쓰지 않은 에셋은 정리될 수 있습니다.

에셋 함수
함수돌려주는 것 · 하는 일
assets.get(uri)asset:// 주소의 { bytes, mimeType, originalName }입니다. bytes는 ArrayBuffer이고, 없으면 null입니다.
assets.register(bytes, { mimeType, originalName })바이트를 프로젝트 에셋으로 등록하고 asset:// 주소를 돌려줍니다. bytes는 Uint8Array가 아니라 ArrayBuffer여야 하고, 하나에 10MB까지입니다. 내용이 같으면 같은 주소가 옵니다.

저장소

패널을 닫으면 플러그인 문서도 사라지므로, 변수에 둔 값은 다시 열 때 남아 있지 않습니다. 옵션처럼 남겨야 할 값은 저장소에 두세요. 프레임 안에서는 localStorage를 쓸 수 없어서, 에디터가 플러그인 id마다 기록 하나를 대신 보관합니다.

기록은 문서가 아니라 앱에 남습니다. 이 앱에서 여는 모든 프로젝트가 같은 기록을 보고, .sshow 파일에는 들어가지 않습니다. 웹 · 데스크톱 · 태블릿은 따로 보관하고, 같은 id를 다시 가져와도 기록은 지워지지 않습니다. 문서에 남아야 하는 것은 applyActions로 오브젝트에 담으세요.

저장소 함수
함수돌려주는 것 · 하는 일
storage.get(key)그 항목의 값입니다. 저장한 적이 없으면 null입니다.
storage.set(key, value)JSON 값을 저장합니다. 플러그인의 기록 전체가 64KB까지이고, 넘으면 거부하고 기록은 그대로 둡니다. undefined를 저장하면 그 항목이 지워집니다.
storage.remove(key)항목 하나를 지웁니다.
storage.keys()이 플러그인이 저장한 항목의 키 배열입니다.

이벤트

events.on(type, callback)으로 듣고 events.off(type, callback)로 그칩니다. 콜백에는 아무 값도 오지 않습니다. 다시 읽으라는 신호이므로, 받으면 읽기 함수로 다시 읽으세요. 아래 셋 밖의 type은 거부하고, 플러그인이 멈추면 구독은 모두 풀립니다.

플러그인이 들을 수 있는 이벤트
type언제 오나
history:update문서가 바뀔 때입니다. 편집 · 실행 취소 · 다시 실행 모두 해당합니다.
ui:modes:edit:changeSelectedObjects선택이 바뀔 때입니다.
motion:animation:timeUpdate애니메이션 시계가 움직일 때입니다. 재생 중에는 프레임마다 오므로 처리 간격을 조절하세요.

패널 화면과 키보드

ui.resize(size)로 화면 크기를 요청합니다. 숫자 하나는 높이, { width, height }는 원하는 쪽의 크기입니다. 패널은 처음에 너비 340px · 창 높이의 52%이고, 280×240px부터 창의 90%까지만 바뀝니다. 요청한 크기는 사용자가 패널 크기를 바꾼 것처럼 남습니다.

화면 문서에는 에디터의 디자인 값이 아래 CSS 변수로 들어 있고, 에디터와 같은 라이트 · 다크 설정을 따라 바뀝니다. var(--sshow-primary)처럼 쓰면 테마가 바뀔 때 코드 없이 패널이 함께 바뀝니다. 스크롤바도 에디터와 같은 6px 막대이고, ::-webkit-scrollbar 규칙을 직접 쓰면 그 규칙이 우선합니다.

ui.getTheme()은 { mode, colors, borderRadius, fontSize }를 돌려줍니다. mode는 'light' 또는 'dark'입니다. 캔버스에 그릴 때처럼 스크립트에 색 값이 필요할 때만 쓰세요.

플러그인 화면에 포커스가 있어도 에디터 단축키는 그대로 듣습니다. 플러그인이 처리하지 않은 키는 에디터로 전달되어, ⌘Z는 실행 취소, Tab 키는 편집 모드 바꾸기, Space 키는 화면 옮기기(애니메이션 모드에서 짧게 누르면 타임라인 재생)가 됩니다. 그래서 화면 안에서 Tab 키로 포커스를 옮기거나 Space 키로 버튼을 누를 수 없습니다.

키를 플러그인에서 쓰려면, 그 키가 window에 닿기 전에 막으세요. 스크립트로 직접 처리할 키는 preventDefault()를 부르세요. 포커스 이동이나 버튼 누르기 같은 브라우저의 기본 동작을 살릴 키는 keydown과 keyup 둘 다에서 stopPropagation()을 부르세요. preventDefault()는 그 기본 동작까지 멈춥니다. SDK가 window에서 키를 전달하므로, 리스너는 window가 아니라 document나 요소에 다세요. 입력란 · 텍스트 영역 · contenteditable에 포커스가 있을 때는 어떤 키도 전달되지 않습니다.

플러그인 문서에 들어 있는 CSS 변수
CSS 변수값
--sshow-primary강조색 #2196F3
--sshow-primary-strong켜졌거나 눌린 버튼의 면. 강조색을 조금 어둡게 한 색입니다.
--sshow-primary-soft버튼에 마우스를 올렸을 때의 면. 강조색을 어둡게 하고 조금 비치게 한 색입니다.
--sshow-primary-foreground강조색 위의 글자 #ffffff
--sshow-secondary보조색 #FFC107
--sshow-foreground본문 글자. 라이트 #30353e · 다크 #f5f5f5
--sshow-background반투명한 패널 면
--sshow-background-solid불투명한 패널 면
--sshow-border-color테두리
--sshow-radius13px
--sshow-font-size12px. 에디터 패널 안의 글자 크기입니다.
--sshow-scrollbar-size6px
--sshow-scrollbar-radius3px

샌드박스와 제한

플러그인은 에디터와 떨어진 샌드박스 프레임에서 돌아갑니다. 아래 제한은 플러그인을 쓰는 사람의 문서와 계정을 지키기 위한 것입니다.

플러그인의 제한
항목제한
네트워크막혀 있습니다. fetch · 외부 스크립트 · 외부 이미지 · 웹 폰트를 쓸 수 없습니다. 스크립트와 스타일은 화면 문서 안에 직접 쓰고, 이미지와 폰트는 data: 주소로 넣으세요. 이미지는 blob: 주소도 됩니다.
스크립트문서 안의 스크립트와 WebAssembly는 됩니다. eval과 new Function은 안 됩니다.
프레임샌드박스는 allow-scripts 하나만 허용합니다. 프레임 안에서는 localStorage와 쿠키를 쓸 수 없고, alert · confirm 대화상자와 새 창은 열리지 않습니다.
패키지zip 항목은 64개까지입니다. plugin.json · 화면 문서 · 아이콘은 압축을 푼 크기로 각각 5MB까지이고, 이 셋 말고는 쓰이지 않습니다. 게시할 때는 패키지 전체가 10MB까지이고, 쓰이지 않는 항목도 압축을 푼 크기가 5MB를 넘으면 거부됩니다.
에셋 · 저장소assets.register 하나에 10MB, 저장소는 플러그인마다 64KB까지입니다.
실행한 번에 플러그인 하나만 열립니다. 다른 플러그인을 열거나 패널을 닫으면 멈춥니다.
API 버전plugin.json의 api가 에디터의 API 버전과 다르면 가져오지 않습니다. 버전은 기존 플러그인이 동작하지 않게 되는 변경에만 올라갑니다. 새 함수는 버전을 올리지 않고 더해질 수 있으니, api.storage?.get처럼 있는지 확인하며 쓰세요.
오류실패한 호출은 Error로 거부(reject)되고, message에 이유가 담깁니다.
언어플러그인에 에디터의 UI 언어를 알려 주는 값이 없습니다. 기본 플러그인 13종도 영어로만 보입니다.

고치고 다시 확인하기

플러그인을 고친 뒤 에디터에서 다시 확인하는 길은 둘입니다. 어디서나 파일을 다시 가져올 수 있고, Windows 데스크톱 앱에서는 폴더를 지켜보게 할 수도 있습니다.

다시 가져오기

웹 · 데스크톱

고친 파일을 다시 묶어 플러그인 가져오기로 가져오세요. 같은 id의 플러그인은 새 파일로 바뀌므로, 올리기 · 되돌리기 · 다시 설치가 모두 같은 동작입니다. 목록 아래에 'Hello 업데이트: 1.0.0 → 1.1.0.'이나 'Hello 1.0.0을(를) 다시 설치했습니다.'가 나오고, 실행 중이던 플러그인은 새 버전으로 다시 열립니다. 파일을 읽지 못해 거부되면 있던 플러그인은 그대로 남습니다. api 값이 달라 거부되면 있던 것도 목록에서 빠지므로, 고친 파일을 다시 가져오세요.

다시 가져오기1.1.0 파일을 가져오자 'Hello 업데이트: 1.0.0 → 1.1.0.'이 나오고 패널이 새 버전으로 다시 열립니다. 영상: Hello 1.0.0이 실행 중일 때 + 버튼으로 1.1.0 파일을 가져오자 목록 아래에 'Hello 업데이트: 1.0.0 → 1.1.0.'이 뜨고, 실행 패널이 새 버튼으로 다시 열립니다.

폴더 지켜보기

데스크톱 앱 (Windows)

파일 메뉴에서 설정 › settings.json 직접 편집을 누르세요. 열린 파일에서 "plugins.devPath"의 값을 플러그인 폴더의 절대 경로로 바꿔 저장하세요. 키가 없으면 더하세요. 경로는 C:/Users/me/hello처럼 / 로 적으세요. 역슬래시를 하나씩 쓰면 파일을 읽지 못해 다른 설정까지 기본값으로 돌아갑니다. 저장한 뒤 앱을 다시 실행하세요. 그다음부터는 폴더의 파일을 저장할 때마다 열린 모든 에디터에 플러그인이 다시 등록되고, 실행 중이던 플러그인이 다시 열립니다. zip으로 묶지 않아도 됩니다. 끄려면 값을 null로 바꾸고 앱을 다시 실행하세요. macOS 앱은 샌드박스 때문에 앱 밖의 폴더를 읽지 못하므로, macOS에서는 다시 가져오기를 쓰세요.

파일로 가져온 플러그인은 새로 고치면 빠집니다

가져온 플러그인은 지금 연 에디터에만 들어 있습니다. 에디터를 새로 고치거나 다시 열면 목록에서 빠지므로 다시 가져오세요. 데스크톱 앱의 폴더 지켜보기는 앱을 다시 열어도 이어집니다.

게시하기

게시한 플러그인은 스튜디오의 플러그인 목록에 올라가 누구나 설치할 수 있습니다. 모든 버전은 게시 전에 사람이 검수합니다.

  1. 개발자 콘솔 열기

    개발자 콘솔을 여세요. 로그인해야 열립니다.

  2. 패키지 고르기

    .sshowplugin 선택을 누르고 패키지를 고르세요. 매니페스트는 서버가 패키지에서 읽으므로 따로 적을 것이 없습니다. 리뷰어에게 할 말은 메모 칸에 적으세요.

  3. 검수 요청하기

    동의 칸을 켜고 검수 요청을 누르세요. 아래 '내 플러그인' 목록에 '검수 중'으로 나타납니다.

  4. 결과 확인하기

    검수가 끝나면 알림과 이메일이 옵니다. 승인된 버전은 바로 목록에 게시됩니다. 수정 요청을 받으면 '내 플러그인' 목록의 '리뷰어 피드백'을 읽으세요. 고친 뒤 더 높은 버전으로 다시 제출하세요.

게시 규칙
규칙내용
id처음 제출한 계정이 계속 소유합니다. 내가 관리하는 도메인을 거꾸로 쓴 id를 쓰세요. installed · mine · submit은 쓸 수 없습니다.
버전제출할 때마다 마지막 제출보다 높아야 합니다. 거절된 버전을 고쳐 낼 때도 번호를 올리세요.
검수한 플러그인에 검수 중인 버전은 하나뿐입니다. 결과가 나온 뒤 다음 버전을 내세요.
전달검수한 zip 파일이 그대로 사용자에게 갑니다. 서버는 패키지를 고치지 않습니다.
내리기'내 플러그인' 목록에서 게시 내리기를 누르면 스튜디오의 플러그인 목록에서 사라집니다. 새 버전이 승인되면 다시 게시됩니다.

잘 안 될 때

  • 가져올 때 '이 파일을 .sshowplugin 패키지로 읽을 수 없습니다.'가 나오나요?

    zip을 읽지 못했거나 매니페스트가 규칙에 맞지 않는다는 뜻입니다. 두 파일이 zip의 맨 위에 있는지, plugin.json이 올바른 JSON인지, id · version · main이 규칙에 맞는지 확인하세요.
  • '이 플러그인은 다른 플러그인 API 버전용으로 만들어졌습니다.'가 나오나요?

    plugin.json의 api가 에디터의 API 버전과 다릅니다. 있던 같은 id의 플러그인도 목록에서 빠집니다. api를 1로 고쳐 다시 묶은 뒤 가져오세요.
  • applyActions를 불렀는데 아무것도 바뀌지 않나요?

    돌려받은 skipped의 reason에 액션마다 건너뛴 이유가 있습니다. 흔한 원인은 다른 씬에 있는 오브젝트의 id, 모르는 set 키, asset:// 주소가 아닌 src입니다. 다른 씬의 오브젝트라면 sceneId를 함께 주세요.
  • 회전시킨 각도가 엉뚱하게 커지나요?

    읽은 transform의 각도는 라디안이고, set으로 보내는 각도는 도로 읽힙니다. 읽은 값을 돌려보낼 때는 180 / Math.PI를 곱하세요.
  • 스크립트 파일이나 그림, 폰트가 불러와지지 않나요?

    플러그인 화면은 네트워크와 외부 파일을 쓸 수 없습니다. 스크립트와 스타일은 화면 문서 안에 직접 쓰고, 그림과 폰트는 data: 주소로 넣으세요.
  • 플러그인 안에서 Space 키나 Tab 키가 듣지 않나요?

    두 키는 에디터의 단축키로 전달됩니다. 포커스 이동이나 버튼 누르기를 살리려면 document에 단 keydown · keyup 리스너에서 두 키의 stopPropagation()을 부르세요. preventDefault()를 부르면 에디터로 가지 않지만 브라우저의 기본 동작도 멈춥니다. 입력란에 포커스가 있을 때는 전달되지 않습니다.
  • 스크립트 오류는 어디서 보나요?

    웹 에디터에서는 브라우저 개발자 도구의 콘솔에 플러그인 프레임의 오류도 나옵니다. 데스크톱 앱에서는 개발자 도구가 열리지 않습니다. 폴더 지켜보기로 고친 플러그인이 목록에 나타나지 않거나 동작하지 않으면, 같은 파일을 묶어 웹 에디터에서 가져와 브라우저 개발자 도구로 확인하세요.
이 페이지가 도움이 되었나요?

2026년 10월 11일에 고침 · 이 페이지의 영상은 현재 에디터로 녹화했습니다.

많이 찾는 것