kintone のカスタマイズを JavaScript ファイルのまま配っていると、アプリごとに「どのフィールドを使うか」をコードに書き換える必要があります。プラグインにすると、その設定を管理画面で選べるようになります。
この記事では、日付フィールドの期限切れを赤、期限が近いものを黄色で表示する小さなプラグイン(設定画面つき、JavaScript 約110行)を例に、次の4つを順に見ます。
- ファイルの構成と
manifest.json - 設定画面:フィールドの一覧を取り、選んだ値を保存する
- 動く側(desktop.js):保存した設定を読んで画面に反映する
- 署名して zip にする
1. ファイルの構成と manifest.json
expiry-alert/src/
├── manifest.json
├── LICENSE.txt
├── image/icon.png
├── html/config.html
├── css/config.css
├── css/desktop.css
├── js/config.js
└── js/desktop.js
manifest.json は次のとおりです(kintone/expiry-alert/src/manifest.json)。
{
"manifest_version": 1,
"version": 1,
"type": "APP",
"name": {"ja": "現場の期限アラート", "en": "Site Expiry Alert"},
"icon": "image/icon.png",
"homepage_url": {"ja": "https://genbakoe.com/kintone/expiry-alert/", "en": "https://genbakoe.com/kintone/expiry-alert/"},
"desktop": {"js": ["js/desktop.js"], "css": ["css/desktop.css"]},
"config": {"html": "html/config.html", "js": ["js/config.js"], "css": ["css/config.css"], "required_params": ["fields"]}
}
(description は省きました)
押さえる所は3つです。
-
desktopは、レコードの一覧や詳細の画面で読み込むファイル。 -
configは、アプリの設定 →「プラグイン」→ 歯車から開く設定画面のファイル。 -
required_paramsに書いたキーが保存されるまで、kintone はプラグインを「設定が済んでいない」として扱います。ここでは、見張る日付フィールドの一覧fieldsを必須にしています。
2. 設定画面:フィールドの一覧を取って選ばせる
設定画面の HTML は、kintone の画面の中に差し込まれる断片です。<html> や <body> は書きません。
<div class="gbk-config">
<p>期限を見張る日付フィールドを選びます。複数選べます。</p>
<div id="gbk-fields"></div>
<label>「期限が近い」とする日数<input id="gbk-days" type="number" min="1" max="365" value="30"></label>
<p class="gbk-actions"><button type="button" id="gbk-save">保存</button> <button type="button" id="gbk-cancel">キャンセル</button></p>
</div>
チェックボックスは、REST API の「フォームのフィールド一覧の取得」で、そのアプリにある日付と日時のフィールドから作ります(kintone/expiry-alert/src/js/config.js)。
(function(PLUGIN_ID) {
'use strict';
var saved = kintone.plugin.app.getConfig(PLUGIN_ID);
var chosen = saved.fields ? JSON.parse(saved.fields) : [];
var box = document.getElementById('gbk-fields');
// (中略)
kintone.api(kintone.api.url('/k/v1/app/form/fields.json', true), 'GET', {app: kintone.app.getId()}).then(function(response) {
var found = 0;
Object.keys(response.properties).forEach(function(code) {
var field = response.properties[code];
if (field.type !== 'DATE' && field.type !== 'DATETIME') return;
found++;
var label = document.createElement('label');
var input = document.createElement('input');
input.type = 'checkbox';
input.value = code;
input.checked = chosen.indexOf(code) >= 0;
label.appendChild(input);
label.appendChild(document.createTextNode(' ' + field.label + '(' + code + ')'));
box.appendChild(label);
});
if (!found) box.textContent = 'このアプリには、サブテーブルの外に日付フィールドがありません。';
});
- ファイル全体を
(function(PLUGIN_ID) { ... })(kintone.$PLUGIN_ID);で包みます。kintone.$PLUGIN_IDに、そのプラグインの ID が入っています。 -
kintone.api.url(パス, true)の第2引数をtrueにすると、ゲストスペースのアプリでは/k/guest/<スペースID>/v1/...の URL を返します。ゲストスペースでも動かしたいときは付けておきます。 -
response.propertiesの直下に出るのはサブテーブルの外のフィールドだけです。サブテーブルの中はtype: 'SUBTABLE'のfieldsの下にあります。このプラグインは一覧の列に色を付けるので、外のフィールドだけを対象にしています。
保存は kintone.plugin.app.setConfig です。値は文字列しか保存できないので、配列は JSON.stringify で文字列にします。
document.getElementById('gbk-save').addEventListener('click', function() {
var fields = Array.prototype.map.call(box.querySelectorAll('input:checked'), function(i) { return i.value; });
var limit = Number(days.value);
if (!fields.length) {
window.alert('日付フィールドを1つ以上選んでください。');
return;
}
if (!(limit >= 1 && limit <= 365)) {
window.alert('日数は1から365で入れてください。');
return;
}
kintone.plugin.app.setConfig({fields: JSON.stringify(fields), days: String(Math.floor(limit))});
});
document.getElementById('gbk-cancel').addEventListener('click', function() {
window.location.href = '../../' + kintone.app.getId() + '/plugin/';
});
})(kintone.$PLUGIN_ID);
入力の確かめ(1つ以上選んだか、日数が1〜365か)は保存の前に済ませます。動く側では「設定は正しい」と思って書けるので、コードが短くなります。
3. 動く側:設定を読んで一覧に色を付ける
desktop.js の頭で設定を読みます(kintone/expiry-alert/src/js/desktop.js)。
var config = kintone.plugin.app.getConfig(PLUGIN_ID);
if (!config.fields) return;
var fields = JSON.parse(config.fields);
var limit = Number(config.days) || 30;
設定がまだなら何もしません。プラグインを入れただけで画面が壊れることを避けます。
一覧の画面では、app.record.index.show で、選んだフィールドのセルを kintone.app.getFieldElements で取ります。返る配列の順番は event.records と同じなので、添字で対応させます。
kintone.events.on('app.record.index.show', function(event) {
if (event.viewType !== 'list') return event;
var expired = 0;
var soon = 0;
fields.forEach(function(code) {
var cells = kintone.app.getFieldElements(code);
if (!cells) return;
event.records.forEach(function(record, index) {
if (!record[code]) return;
var state = mark(cells[index], daysLeft(record[code].value));
if (state === 1) expired++;
if (state === 2) soon++;
});
});
// (中略:ヘッダーに件数を出す)
return event;
});
- カレンダー表示やカスタマイズビューではセルがないので、
event.viewType !== 'list'で抜けます。 - そのフィールドが一覧の列に出ていなければ
getFieldElementsはnullを返すので、そこでも抜けます。
日付の残り日数の数え方には、1つ落とし穴があります。
// 残り日数。日付だけの値は端末の暦のその日として数える
function daysLeft(value) {
if (!value) return null;
var date = /^\d{4}-\d{2}-\d{2}$/.test(value) ? new Date(value + 'T00:00:00') : new Date(value);
var time = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
if (isNaN(time)) return null;
return Math.round((time - startOfToday()) / DAY);
}
日付フィールドの値は "2026-12-01" の形です。これをそのまま new Date("2026-12-01") に渡すと、JavaScript は UTC の0時 として読みます。日本時間では同じ日の9時なので気づきにくいのですが、UTC より遅れた時間帯の端末では前の日になります。T00:00:00 を付けると、端末の時間帯の0時として読まれます。日時フィールド(UTC の ...Z で届く)は、そのまま new Date に渡してから、端末の暦の日に丸めています。
4. 署名して zip にする
kintone に読み込ませるには、ソースを zip にし、RSA の鍵で署名した形にします。公式のツール(@kintone/plugin-packer)もありますが、ここでは同じことを Python で書いた例を見ます(tools/kintone_pack.py)。
def main(name):
src = ROOT / "kintone" / name / "src"
# (中略)
contents = zip_bytes((str(p.relative_to(src)), p.read_bytes()) for p in sorted(src.rglob("*")) if p.is_file() and p.name != ".DS_Store")
key = load_key(name)
public_der = key.public_key().public_bytes(serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo)
signature = key.sign(contents, padding.PKCS1v15(), hashes.SHA1())
out = ROOT / "static" / ("genbakoe-%s.zip" % name)
out.write_bytes(zip_bytes([("contents.zip", contents), ("PUBKEY", public_der), ("SIGNATURE", signature)]))
できる zip の中身は3つです。
| ファイル | 中身 |
|---|---|
| contents.zip |
src/ 以下をまとめたもの |
| PUBKEY | 公開鍵(DER) |
| SIGNATURE | contents.zip の署名 |
プラグイン ID は公開鍵から決まります。
def plugin_id(public_der):
digest = hashlib.sha256(public_der).hexdigest()[:32]
return "".join(chr(ord("a") + int(c, 16)) for c in digest)
公開鍵の SHA-256 の先頭32桁の16進を、0〜f → a〜p に置き換えた32文字です。鍵を変えると ID が変わり、別のプラグインとして扱われます。 すでに入れている所で「更新」できなくなるので、秘密鍵はリポジトリの外に置いて、なくさないようにします。このスクリプトも鍵を ~/.config/genbakoe/ に置き、Git に入れていません。
zip の中のファイルの日時を (2026, 1, 1, 0, 0, 0) に揃えているのは、中身が同じなら同じ zip になるようにするためです。
まとめ
-
manifest.jsonのrequired_paramsで「設定が済むまで動かない」を kintone に任せる。 - 設定画面ではフィールドの一覧を REST API で取り、
setConfigには文字列で渡す。 - 日付だけの値は
T00:00:00を付けて端末の暦の日として読む。 - 署名の鍵がプラグイン ID を決める。鍵はなくさない。
ソースと配布先
この記事のコードは、建設向けの無料の kintone プラグイン「現場の期限アラート」の一部です。
- ソース(MIT License):https://github.com/Genbacoe/kintone-plugins/tree/main/expiry-alert/src
- 入れ方と設定の画面:https://genbakoe.com/kintone/expiry-alert/?utm_source=qiita&utm_medium=referral&utm_campaign=kintone-plugin-manifest-config-pack
※ kintone はサイボウズ株式会社の登録商標です。このプラグインと記事は、サイボウズ株式会社の公式のものではありません。