kintoneの添付ファイルをJSZipで一括ダウンロードしよう

目次

はじめに

kintoneにファイルをいっぱい詰め込んだんだけど、ファイルのダウンロードはひとつずつしかできないと嘆いている、そこのアナタに朗報です!
Cybozu CDN に登録されている JSZip (External link) を用いて、添付ファイルをZipファイルで一括ダウンロードする方法を紹介します。

添付ファイルの一括ダウンロード/アップロードなどの操作について、kintoneコマンドラインツールを使ったやり方の説明記事もあります。
興味のある方は 添付ファイルをダウンロード・アップロード・削除してみよう を確認してください。

ライブラリ

今回は一括ダウンロードをより簡単に実装するため、Cybozu CDN外のライブラリを利用します。
そのような場合は、いったんファイルをダウンロードしてからアプリに適用しましょう。
また、今回紹介する記事で確認したライブラリのバージョンは以下のとおりです。

JSZipUtilsと、FileSaver.jsはgithub上で公開されているものを取得する方法をおすすめします。

デモ環境

デモ環境で実際に動作を確認できます。
https://dev-demo.cybozu.com/k/264/ (External link)

ログイン情報は cybozu developer networkデモ環境 で確認してください。

一括ダウンロードのしくみ

今回kintoneからファイルを一括ダウンロードする処理順序は以下のとおりです。

kintone上の添付ファイルをダウンロードするためには、レコードの取得とURL生成が必要です。

この点に関しては以下のサンプルで紹介していますので、参考にしましょう。

ファイルダウンロードで必須となる2つの手順

また、「レコードの取得」、「URLの作成」、「ファイルのダウンロード」は非同期通信で行われる点に注意しましょう。

JavaScriptソースコード

ソースコード

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/* global JSZip */
/* global JSZipUtils */
/* global saveAs */
//
// JSZip sample program
// Copyright (c) 2026 Cybozu
//
// Licensed under the MIT License
// FileSaver.js MIT https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
// jszip MIT or GPLv3 https://github.com/Stuk/jszip/blob/master/LICENSE.markdown
// jszip-utils MIT or GPLv3 https://github.com/Stuk/jszip-utils/blob/master/LICENSE.markdown
//
(() => {
  'use strict';
  const fieldCode = '添付ファイル'; // 添付ファイルのフィールドコード
  const isGuestSpace = false;

  // レコード取得関数(非同期)100件まで
  const getAppRecords = () => {
    const url = kintone.api.url('/k/v1/records.json', isGuestSpace);
    const appId = kintone.app.getId();
    const condition = kintone.app.getQueryCondition() || '';
    const query = `${condition} order by $id asc`;
    const body = {
      app: appId,
      query,
      fields: [fieldCode]
    };
    return kintone.api(url, 'GET', body);
  };

  // ファイルキーの取得関数
  const getFileKeys = (json) => {
    const keys = [];
    json.records.forEach(record => {
      const files = record[fieldCode];
      files.value.forEach(file => {
        keys.push(file);
      });
    });
    return keys;
  };

  // ファイルサイズチェック関数
  const checkFileSize = async (filekeys) => {
    if (filekeys.length === 0) {
      throw new Error('添付ファイルが見つかりませんでした。');
    }
    const totalsizeNum = filekeys.reduce((sum, file) => sum + parseInt(file.size, 10), 0);
    let totalsize;
    if (totalsizeNum < 999) { // 1KB未満
      totalsize = `${totalsizeNum}`;
    } else if (totalsizeNum < 999999) { // 1MB未満
      totalsize = `${parseInt(totalsizeNum / 1000, 10)}K`;
    } else if (totalsizeNum < 999999999) { // 1GB未満
      totalsize = `${parseInt(totalsizeNum / 1000000, 10)}M`;
    } else {
      throw new Error('ファイルサイズが大きすぎます。');
    }
    const result = await kintone.showConfirmDialog({
      title: '一括ダウンロード',
      body: `${totalsize}バイトダウンロードします。よろしいですか?`,
      showOkButton: true,
      okButtonText: 'ダウンロード',
      showCancelButton: true,
      cancelButtonText: 'キャンセル'
    });
    if (result !== 'OK') {
      return null;
    }
    return filekeys;
  };

  // ファイルのblob URLを取得する関数(非同期)
  const getFileBlobUrl = async (fileKey) => {
    const params = {fileKey};
    const url = kintone.api.urlForGet('/k/v1/file.json', params, isGuestSpace);
    const resp = await fetch(url, {
      method: 'GET',
      headers: {
        'X-Requested-With': 'XMLHttpRequest'
      }
    });
    if (!resp.ok) {
      throw new Error('ファイルのダウンロードに失敗しました。');
    }
    const blob = await resp.blob();
    const wurl = window.URL || window.webkitURL;
    return wurl.createObjectURL(blob);
  };

  // 複数ファイルのblob URLを取得する関数(Promise.allで並列化)
  const addfileURLs = (filekeys) => {
    return Promise.all(
      filekeys.map(async (file) => {
        const fileKey = file.fileKey;
        const name = file.name;
        const blobUrl = await getFileBlobUrl(fileKey);
        return {
          name,
          blobUrl
        };
      })
    );
  };

  // 非同期で1ファイルダウンロードし、zipへ組み込みする関数
  const downloadFile = (zip, url, filename) => new Promise((resolve, reject) => {
    JSZipUtils.getBinaryContent(url, (err, data) => {
      if (err) {
        reject(err);
        return;
      }
      zip.file(filename, data, {binary: true});
      resolve(data);
    });
  });

  // 複数ファイルのダウンロードを実行する関数(Promise.allで並列化)
  const downloadFiles = (files, zip = new JSZip()) => {
    return Promise.all(
      files.map(file => downloadFile(zip, file.blobUrl, file.name))
    ).then(() => zip);
  };

  // ファイルをzip化する関数
  const doZipFile = (zip) => zip.generateAsync({type: 'blob'});

  // ファイルを保存する関数
  const saveZipFile = (content) => saveAs(content, 'example.zip');

  // ボタンクリック時に呼び出される関数
  const getZipFile = async () => {
    try {
      const records = await getAppRecords();
      const fileKeys = getFileKeys(records);
      const checkedKeys = await checkFileSize(fileKeys);
      if (!checkedKeys) {
        return;
      }
      const filesWithUrls = await addfileURLs(checkedKeys);
      const zip = await downloadFiles(filesWithUrls);
      const content = await doZipFile(zip);
      await saveZipFile(content);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      await kintone.showNotification('ERROR', message);
    }
  };

  // レコード一覧画面にボタンを配置
  kintone.events.on('app.record.index.show', () => {
    if (document.getElementById('menuButton')) return;
    const menuButton = document.createElement('button');
    menuButton.id = 'menuButton';
    menuButton.textContent = '一括ダウンロード!!';
    menuButton.addEventListener('click', getZipFile);
    kintone.app.getHeaderMenuSpaceElement().appendChild(menuButton);
  });
})();

ソースコード解説

一覧画面に一括ダウンロードボタンを配置し、ボタンの押されたタイミングでgetZipFile関数が呼ばれます。
getZipFile関数では、async/awaitで非同期処理を実装しています。
まずgetAppRecordsでレコードを一括取得し、その結果をgetFileKeysに渡して添付ファイルの情報を抽出します。
続けて、確認ダイアログでのファイルサイズ確認、各ファイルのblob URL取得、Zip化、保存と処理をつなげていきます。
エラーが発生した場合は、画面上部の通知バナーでメッセージを表示します。

使い方

ソース上部のfieldCodeの値を添付ファイルのフィールドコードに変更する必要があります。

変更した後、JSファイル(以下例ではdownload.js)をアップロードしましょう。

注意事項

  • ファイル名に日本語を含んでいる場合、文字化けすることがあります。
  • 本サンプルではレコードの取得は100件までとなっています。
  • 本サンプルはサブテーブルの添付ファイルには対応していません。
  • FileSaver.jsの制限事項として、ブラウザーによってファイルサイズに上限があります。
  • エラー処理が不十分のため、実装の際はご留意ください。

おわりに

今回は一括ダウンロードの手法を紹介しました。

最低限の機能実装でも結構なボリュームがありますが、この後レコードごとにフォルダーを分けたり、ファイル名を可変にしたり、いろいろな拡張ができると思います。

いろいろなカスタマイズを行い、ぜひともコミュニティへご投稿ください!

information

このTipsは、2026年8月版kintoneで動作を確認しています。

公式コミュニティ

kintone開発者同士で質問や知見を共有し、学び合うことができます。