Chrome 확장 프로그램, 만들고 자동 배포까지

Chrome 확장 프로그램을 개발자 등록부터 결제, 첫 배포, 서비스 계정 기반 자동 배포까지 직접 밟아본 과정을 정리했습니다.

Share
Chrome 확장 프로그램, 만들고 자동 배포까지
Photo by Denny Müller / Unsplash

간단한 Chrome 확장 프로그램을 하나 만들었습니다. Steam 지갑에 원하는 금액을 자유롭게 충전할 수 있게 해주는 도구인데, 코드 자체는 그리 복잡하지 않았습니다. 정작 손이 많이 간 건 그 다음이었습니다. 개발자 등록, 결제, 첫 업로드, 그리고 이후 버전을 매번 손으로 올리지 않기 위한 자동 배포까지. 막상 해보니 곳곳에 작은 함정들이 있었습니다.

이 글은 확장 프로그램을 처음부터 끝까지, 그러니까 개발자 등록 → 결제 → 첫 배포 → CI/CD 자동 배포까지 실제로 밟아본 과정을 정리한 기록입니다. 특히 결제 단계에서 국가 선택이 막히는 문제처럼 문서에는 잘 안 나오는 부분과, 자동 배포를 위해 직접 만든 GitHub Action까지 함께 다룹니다. 비슷한 길을 가실 분들께 참고가 되었으면 합니다.

샘플로 다루는 확장 프로그램은 실제로 스토어에 올라가 있는 Steam 충전 도우미​입니다.

Steam 충전 도우미 - Chrome Web Store
Steam 지갑에 원하는 금액을 직접 입력해 충전할 수 있습니다.

전체 흐름 한눈에 보기

먼저 큰 그림입니다. 한 번만 해두면 되는 일회성 셋업과, 이후 매 릴리스마다 자동으로 도는 배포 흐름이 나뉩니다.

flowchart TD
  subgraph SETUP["일회성 셋업"]
    reg["개발자 등록"] --> pay["$5 결제"]
    pay --> first["첫 버전 수동 업로드 · 심사"]
    first --> sa["서비스 계정 + API 연동"]
  end

  subgraph AUTO["릴리스마다 자동 실행 (CI/CD)"]
    tag["git tag v* push"] --> build["빌드 · zip 생성"]
    build --> action["action-deploy-browser-extension"]
    action --> upload["Web Store API 업로드"]
    upload --> publish["자동 게시"]
  end

  sa -.최초 1회 연결.-> AUTO

핵심은 첫 제출은 반드시 손으로 해야 한다는 점입니다. 자동 배포는 이미 존재하는 확장을 업데이트하는 용도이지, 맨 처음 항목을 만들어주지는 않습니다. 그래서 셋업 단계를 먼저 정리하고, 그 다음 자동화로 넘어가겠습니다.

개발자 등록과 결제 — 그리고 국가 선택의 함정

Chrome 웹 스토어에 확장 프로그램을 올리려면 개발자 계정이 필요하고, 여기에는 일회성 등록비 5달러가 듭니다. 한 번 내면 이후로는 여러 개의 확장을 올릴 수 있습니다.

Chrome Web Store Developer Dashboard
Manage your extensions, check analytics, and publish updates to the Chrome Web Store.

Chrome 웹 스토어 개발자 대시보드에 들어가 계정을 만들고 결제 단계로 가면 됩니다. 여기까지는 평범합니다. 문제는 결제 화면에서 생겼습니다.

결제 시 거주 국가를 선택하는 단계에서, 대한민국이 선택지에 나타나지 않았습니다. 결제 프로필이 만들어지는 방식이나 계정 상태에 따라 일부 국가가 목록에 안 보이는 경우가 있는데, 제 경우가 그랬습니다. 결제 자체가 막히니 등록을 더 진행할 수가 없었습니다.

우회로 택한 방법은 결제 주소를 미군기지(APO/FPO) 주소 체계로 넣는 것이었습니다. 용산 기지의 우편번호 96205를 사용해 주소를 등록했더니 결제가 통과됐고, 5달러 결제 후 개발자 등록을 마칠 수 있었습니다.

주의: 이 방법은 결제 프로필의 국가 선택이 막혔을 때 쓴 임시 우회책입니다. 구글 결제 약관은 실제 거주지 기준 정보를 요구하므로, 실제 거주지와 다른 주소를 넣는 것은 약관상 회색지대일 수 있습니다. 가능하면 결제 수단을 바꾸거나(국내 카드 ↔ 해외 결제 가능 카드), 새 결제 프로필을 만들어 국가가 정상적으로 잡히는지 먼저 시도해 보시길 권합니다. 위 방법은 어디까지나 "저는 이렇게 통과시켰다"는 기록으로 봐주세요.

첫 버전 수동 업로드와 배포

결제가 끝났으면 이제 확장 프로그램을 업로드할 차례입니다. 앞서 말했듯 첫 제출은 수동입니다.

업로드에 필요한 건 빌드 결과물을 압축한 zip 파일입니다. 샘플 확장은 TypeScript로 작성하고 esbuild로 번들하는 구조라, 빌드 과정은 다음과 같습니다.

pnpm install        # 의존성 설치 (최초 1회)
pnpm build          # src/ 를 번들해 dist/ 생성
pnpm build:zip      # 스토어 업로드용 extension.zip 생성

pnpm buildsrc/의 TypeScript를 번들해 dist/에 로드 가능한 확장(content.js, manifest.json, styles.css, _locales/)을 만들어냅니다. 이 dist/를 압축한 게 스토어에 올릴 zip입니다. 참고로 개발 중에는 chrome://extensions/에서 개발자 모드를 켜고 "압축해제된 확장 프로그램을 로드합니다"로 dist/ 폴더를 직접 올려 테스트할 수 있습니다.

만든 zip을 대시보드에서 새 항목으로 업로드하고, 스토어 등록 정보(이름, 설명, 아이콘, 스크린샷)와 개인정보 처리방침을 채운 뒤 제출하면 심사가 시작됩니다. 심사를 통과하면 비로소 스토어에 게시됩니다. 이 시점부터 확장에는 고유한 Extension ID가 부여되는데, 이 ID가 뒤이어 나올 자동 배포에서 핵심 키가 됩니다.

자동 배포를 위한 서비스 계정 준비

여기서부터가 이 글의 진짜 목적입니다. 버전을 올릴 때마다 zip 만들고, 대시보드 들어가서, 업로드하고, 게시 누르고… 이걸 매번 손으로 하는 건 금방 지칩니다. 그래서 서비스 계정을 이용해 자동화합니다.

서비스 계정은 사람이 아니라 서버 간 통신을 위한 특수 계정입니다. 사용자가 직접 참여하는 OAuth 흐름을 거치지 않고 API를 사용할 수 있어서, CI/CD 파이프라인처럼 확장 프로그램 게시를 자동화하는 데 적합합니다. 이 서비스 계정을 Chrome 웹 스토어 개발자 대시보드에 연결하면, 서비스 계정에 게시자 계정 소유 항목을 관리할 권한이 부여됩니다.

Chrome 웹 스토어 API에서 서비스 계정 사용 | Chrome Extensions | Chrome for Developers
Chrome 웹 스토어 API에서 서비스 계정 사용

준비 과정은 크게 네 단계입니다.

먼저 Chrome 웹 스토어 API를 사용 설정합니다. Google Cloud Console로 이동해 새 프로젝트를 만들거나 기존 프로젝트를 선택하고, 검색창에 'Chrome 웹 스토어 API'를 입력해 사용 설정합니다.

다음으로 서비스 계정을 만듭니다. Google Cloud 콘솔에서 서비스 계정을 생성하며, 이 단계에서는 별도의 권한을 추가할 필요가 없습니다.

그리고 개발자 대시보드에 서비스 계정을 연결합니다. 개발자 대시보드의 계정 섹션에서 서비스 계정 이메일을 추가하면 해당 서비스 계정에 Chrome 웹 스토어 API 접근 권한이 부여됩니다. 참고로 현재는 게시자당 서비스 계정을 하나만 추가할 수 있습니다.

마지막으로 인증용 키를 발급합니다. 자동화 환경에서는 JWT(JSON 웹 토큰) 방식이 편한데, Google Cloud 콘솔에서 서비스 계정의 JSON 키를 만든 뒤 이를 JWT로 구성해 액세스 토큰으로 교환하는 방식입니다. 이 JSON 키 파일이 곧 자동 배포에서 사용할 자격 증명이 됩니다.

키 파일을 제대로 관리하지 않으면 보안 위험이 발생할 수 있으니, JSON 키는 절대 저장소에 커밋하지 말고 GitHub Secrets 같은 안전한 곳에만 보관하시기 바랍니다.

GitHub Action으로 배포 자동화하기

서비스 계정과 JSON 키가 준비됐으면, 이제 릴리스 때마다 자동으로 업로드·게시되도록 묶을 차례입니다. 이를 위해 GreedyLabs는 직접 GitHub Action을 만들어 공개했습니다. action-deploy-browser-extension입니다.

GitHub - GreedyLabs/action-deploy-browser-extension: Deploy chrome extension to chrome web store or edge add-ons
Deploy chrome extension to chrome web store or edge add-ons - GreedyLabs/action-deploy-browser-extension

이 Action은 빌드된 zip을 받아 Chrome 웹 스토어와 Microsoft Edge 애드온 스토어에 업로드·게시해줍니다. 두 스토어를 동시에 지정하면 병렬로 배포되고요. 다만 앞서 강조한 것처럼, 각 스토어의 첫 제출은 수동으로 해야 하며 이 Action은 이미 존재하는 확장의 업데이트만 처리합니다.

Chrome만 배포하는 경우

가장 단순한 형태는 이렇습니다.

- uses: GreedyLabs/action-deploy-browser-extension@v1
  with:
    zip-path: dist/extension.zip
    targets: chrome
    chrome-extension-id: ${{ vars.CHROME_EXTENSION_ID }}
  env:
    CHROME_SERVICE_ACCOUNT_KEY: ${{ secrets.CHROME_SERVICE_ACCOUNT_KEY }}

여기서 chrome-extension-id는 첫 수동 배포 때 부여받은 그 Extension ID이고, CHROME_SERVICE_ACCOUNT_KEY는 앞에서 발급한 서비스 계정 JSON 키입니다. 키는 jq -c . key.json으로 한 줄로 압축(minify)한 뒤 Secret에 넣으면 됩니다.

태그 푸시로 도는 전체 워크플로

실제로는 버전 태그를 푸시할 때 자동으로 빌드부터 배포까지 도는 형태가 편합니다.

name: Release Extension

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build
        run: npm ci && npm run build

      - name: Zip extension
        run: zip -r dist/extension.zip dist/ --exclude "*.map"

      - name: Deploy
        uses: GreedyLabs/action-deploy-browser-extension@v1
        with:
          zip-path: dist/extension.zip
          targets: chrome
          chrome-extension-id: ${{ vars.CHROME_EXTENSION_ID }}
          publish: true
        env:
          CHROME_SERVICE_ACCOUNT_KEY: ${{ secrets.CHROME_SERVICE_ACCOUNT_KEY }}

v로 시작하는 태그를 푸시하면 빌드 → zip 생성 → 업로드 → 게시가 한 번에 돕니다. publish: true를 주면 업로드 후 게시까지 진행하고, 빼면 업로드만 하고 게시는 대시보드에서 수동으로 누를 수 있습니다. 아니면, 매번 publish 값을 변수로 받아서 상황에 맞게 대응하셔도 됩니다. 새 언어를 추가하거나 스토어 수정사항이 필요하면 반드시 콘솔에서 관련 작업을 진행해야 하기 때문에, 자동 배포에 실패할 수 있기 때문입니다.

입력값과 출력값

Action이 받는 주요 입력값은 다음과 같습니다.

Input 필수 기본값 설명
zip-path 업로드할 .zip 파일 경로
targets chrome 대상 스토어 (chrome, edge, 쉼표로 복수 지정)
publish false true면 업로드 후 게시까지 진행
chrome-extension-id chrome 대상 시 Chrome 웹 스토어 Extension ID
edge-product-id edge 대상 시 Edge 애드온 Product ID

출력값으로는 chrome-upload-status(업로드 상태), chrome-publish-status(게시 상태) 등이 있어, 후속 스텝에서 결과를 확인하거나 알림에 활용할 수 있습니다.

Edge까지 한 번에

같은 코드베이스를 Edge 애드온 스토어에도 올린다면 targets에 둘 다 지정하면 됩니다. Edge는 서비스 계정 대신 Partner Center에서 발급하는 EDGE_CLIENT_IDEDGE_API_KEY를 사용합니다.

- uses: GreedyLabs/action-deploy-browser-extension@v1
  with:
    zip-path: dist/extension.zip
    targets: chrome, edge
    chrome-extension-id: ${{ vars.CHROME_EXTENSION_ID }}
    edge-product-id: ${{ vars.EDGE_PRODUCT_ID }}
    publish: true
  env:
    CHROME_SERVICE_ACCOUNT_KEY: ${{ secrets.CHROME_SERVICE_ACCOUNT_KEY }}
    EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }}
    EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }}

배포 파이프라인이 도는 순간

모든 조각이 맞물려 도는 흐름을 순서대로 보면 이렇습니다.

sequenceDiagram
  participant Dev as 개발자
  participant GH as GitHub Actions
  participant Action as deploy-action
  participant Google as Chrome Web Store API
  participant Store as 웹 스토어

  Dev->>GH: git tag v1.2.0 push
  GH->>GH: 빌드 · extension.zip 생성
  GH->>Action: zip + 서비스 계정 키 전달
  Action->>Google: JWT로 액세스 토큰 교환
  Action->>Google: zip 업로드 (Extension ID 기준)
  Action->>Google: publish 요청
  Google->>Store: 심사 후 게시
  Google-->>Action: upload/publish 상태 반환
  Action-->>GH: 잡 요약 테이블 출력

한 가지 알아둘 점은, 게시를 눌러도 즉시 반영되지는 않는다는 것입니다. 업데이트도 검토 절차를 거치므로, 게시 요청이 성공했다고 해서 바로 스토어에 새 버전이 뜨는 건 아닙니다. Action의 출력 상태는 "요청이 정상 접수됐는지"까지를 알려준다고 보면 됩니다.

정리하며

확장 프로그램 코드를 짜는 것보다, 그걸 세상에 내보내고 꾸준히 업데이트하는 주변 작업이 의외로 더 번거로웠습니다. 결제 국가 선택이 막혀 한참 헤맸던 것, 첫 제출은 무조건 손으로 해야 한다는 것, 그리고 그 이후를 서비스 계정으로 자동화하기까지. 한 번 길을 닦아두니 이제는 태그 하나 푸시하면 빌드부터 게시까지 알아서 돕니다.

핵심을 다시 짚으면, 첫 제출은 수동, 이후는 서비스 계정 기반 자동 배포입니다. 서비스 계정으로 OAuth 사람 개입 없이 API를 호출하고, 그 위에 GitHub Action을 얹어 릴리스를 자동화하는 구조입니다. 같은 길을 가시는 분들이 결제 함정과 첫 배포의 수동 단계에서 시간을 덜 쓰셨으면 하는 마음으로 정리했습니다. 궁금한 점이나 더 나은 방법이 있으면 편하게 알려주세요.