実績紹介(Portfolio)

【kintone活用法の発明】「マニュアルをいちいち開く」無駄をゼロに。アプリ一覧画面に”更新可能な”説明書を埋め込む方法

majinalife.com
森田ユウゴ
森田ユウゴ

御社のkintoneアプリに関するマニュアル、本当に読まれていますか?

あなた
あなた

せっかくアプリ操作方法マニュアルを作ったのに、現場の社員は誰も見に行かない……

あなた
あなた

入力ルールの変更を周知しても、古いやり方のまま入力されてしまう……

kintoneの運用担当者様から、このようなお悩みをよく伺います。

最大の原因は、「業務アプリ」と「マニュアル」が分断されていることではないでしょうか。 kintoneで独自のアプリを操作中に不明点があっても、「その運用手順」が書かれているドキュメントをわざわざ探す……この「手間」に対してユーザーは拒否感を覚えやすいです。

そこで今回は、「アプリの一覧画面に最新のマニュアルが表示される」仕組みを開発しました。しかも、管理者はマニュアル更新作業にプログラミング知識は一切不要です。

これは単なるデザイン変更ではなく、kintoneの「運用」を劇的に楽にするためのカスタマイズ事例です。

森田ユウゴ
森田ユウゴ

台湾で「面倒解決エンジニア」として活動している森田ユウゴです!

「面倒くさい!」を原動力に、kintoneカスタマイズやChrome拡張機能開発などを通して、日常や業務の課題をテクノロジーで解決することに注力しています。

  • kintone認定アソシエイト取得:2025年11月
kintone認定アソシエイト 取得(2025)
  • kintone認定アプリデザインスペシャリスト:勉強中!

ココナラやクラウドワークスなどでご相談いただけます!
下記のお問い合わせフォームからお気軽にご相談ください▼

アプリの操作説明における「あるある」と限界

まずは、kintone標準機能で「アプリの操作方法」をユーザーに伝えようとした時、多くの担当者がぶつかる壁を整理してみましょう。

1.「アプリの設定」>「アイコンと説明」を使う

kintone標準の「アプリの説明」は一番オーソドックスな方法です。しかし、実際に運用してみるといくつかの「痒いところに手が届かない」点に気づきます。

「アプリの設定」>「アイコンと説明」
  • アプリ1つに対して1種類しか説明を書けない
  • 常に表示されてしまい、画面を占有して邪魔になる
  • 修正するには「アプリ管理者」権限が必要(現場リーダーが気軽に直せない)

2.フォームに「ラベル」を追加する

次に考えられるのは、フォーム自体に「ラベル」パーツを配置して説明を書く方法です。

フォームに「ラベル」を追加する、「グループ」の中に入れる

しかし、全てのフィールドに対して注釈を入れていくと、レコード詳細画面・編集画面が文字だらけになり、肝心の入力フォームが埋もれてしまいます。

標準機能だけで、詳細画面ではラベルを表示しないという制御ができないため、情報を見たいだけの利用者にとってはノイズで溢れています。これでは本末転倒です。

3.「グループ」にまとめる

少しkintoneに慣れてくると「グループパーツの中に説明を入れて、開閉できるようにすればいいじゃん!」と気づくかもしれません。kintone公式ヘルプでも推奨されるテクニックです。

グループに説明をまとめた例

しかし、これにもジレンマがあります。

  • 閉じていると目立たず、読まれない
  • 開いておくと、常に表示されて邪魔(①と同じ問題)

4.「マニュアル管理アプリ」を見てもらう

アプリ内で説明しきれないため、別アプリとして「マニュアル管理アプリ」を作る発想に行き着く方も多いでしょう。

しかし、「アプリを飛び越える」というアクションは、ユーザーにとって想像以上にハードルが高いものです。

結果として、「本来の業務」から離れる小さなストレスが積み重なり、マニュアルは形骸化していきます。

予算が豊富なら「プラグイン」もいいけど…

例えば、「R3 Institute」様の『gusuku Customine』や、「ジョイゾー」様の『Runbook連携プラグイン』などは素晴らしい解決策です。

しかし、導入にはランニングコストがかかります。 「予算が取れるにしても来年度かな……」と先送りになり、その間に運用ルールが崩壊してしまうのが一番もったいないことです。

【ソースコード無料公開】その名も「カスタムビューマニュアル」という大発明

そこで今回開発したのが、「カスタマイズビュー」と「リッチテキスト」を融合させたCMS型システムです。

仕組みの概要

kintoneにはエンジニアでないと活用が難しい「カスタマイズビュー」という機能があります。本来、レコード一覧を自由なHTMLとして表現するための強力な機能ですが、これを一般利用者向けに「マニュアル置き場」にしてしまうという発想です。

具体的には下記のようなアーキテクチャとなっています。

森田ユウゴ発明のカスタムビューマニュアルのアーキテクチャ
  • 表示(Frontend): カスタマイズビューで「枠組み」だけを作る。
  • データ(Backend): マニュアルの中身は、マニュアル専用アプリの「レコード(リッチテキスト)」
  • 連携: JavaScriptでレコードの内容を吸い出し、カスタムビューに表示させる。
森田ユウゴ発明のカスタムビューマニュアルの運用概要

これにより、「エンジニアは枠組みを作り、コンテンツの更新は現場の担当者がリッチエディタで行える」という、持続可能なWikiシステムが完成しました。

カスタムビューを用いたマニュアル管理のサンプル

導入方法

1.マニュアルを用意したいアプリに「カスタムビュー」を追加

アプリ管理者が「アプリ管理」→「一覧」にアクセスし、新たな一覧を追加します。

追加時に「レコード一覧の表示形式」に「カスタマイズ」を選択します。

一覧の「カスタマイズ」を選択したkintoneの画面

一覧の「カスタマイズ」を選択するには、kintoneシステム管理者の権限が必要です。

この「HTML」部分に下記をコピペします。

<!--
 Copyright (c) 2025 森田ユウゴ https://moritayugo.com/
 License: GPL-3.0 (改変・再配布時は同じライセンスで公開してください)
 https://www.gnu.org/licenses/gpl.html
-->

<div class="view-manual">
  
  <div class="wiki-toolbar">
    <button id="wiki-reload-btn" type="button" class="kintone-btn">
      <svg viewBox="0 0 24 24">
        <path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
      </svg>
      最新の情報に更新
    </button>
  </div>

  <div id="wiki-content">
    <div class="intro">
      <p>読み込んでいます...</p>
    </div>
  </div>
  
  <div id="wiki-error" class="error-box" style="display:none;"></div>

  <footer>
    © <a href="https://moritayugo.com/">Yugo Morita Dev.</a>
  </footer>
</div>

この「一覧」はあくまで「表示領域」なので、ここではマニュアルアプリのIDやレコードIDを指定する必要はありません。

2. JavaScriptカスタマイズの追加

次にJavaScriptカスタマイズの準備です。

下記の内容をメモ帳にコピーします。

// 2025/12/13 更新版 from Yugo Morita

/*
 * Copyright (c) 2025 森田ユウゴ https://moritayugo.com/
 * License: GPL-3.0 (改変・再配布時は同じライセンスで公開してください)
 * https://www.gnu.org/licenses/gpl.html
 */

(function() {
  // 【セキュリティ】意図しないグローバル変数の汚染を防ぐためStrictモードを使用
  'use strict';

  const CONFIG = {
    TARGET_APP_ID: XXX,
    TARGET_RECORD_ID: YYY,
    // 【トラブルシューティング】ここが間違っているとデータが取得できても表示が空になります
    RICH_TEXT_FIELD_CODE: 'fieldname', 
    // 変更不要
    TARGET_DOM_ID: 'wiki-content'
  };

  // キャッシュキー(TARGET_APP_IDとTARGET_RECORD_IDから自動生成)
  CONFIG.CACHE_KEY = 'wiki_manual_cache_app' + CONFIG.TARGET_APP_ID + '_record' + CONFIG.TARGET_RECORD_ID;

  kintone.events.on('app.record.index.show', function(event) {
    const container = document.getElementById(CONFIG.TARGET_DOM_ID);
    const reloadBtn = document.getElementById('wiki-reload-btn');
    const errorContainer = document.getElementById('wiki-error');

    if (!container) return;

    const renderContent = function(htmlContent) {
      if (htmlContent) {
        // 【セキュリティ】リッチテキストの仕様上 innerHTML を使用します。
        // ※ 信頼できる管理者が作成したレコードのみを表示対象としています。
        container.innerHTML = htmlContent;
      } else {
        container.innerHTML = '<div class="intro"><p>コンテンツが空です。</p></div>';
      }
      if (errorContainer) errorContainer.style.display = 'none';
    };

    const fetchFromApi = function() {
      container.innerHTML = '<div class="intro"><p>最新データを取得中...</p></div>';
      
      const body = {
        'app': CONFIG.TARGET_APP_ID,
        'id': CONFIG.TARGET_RECORD_ID
      };

      // 【セキュリティ】HTTPS通信 (kintone.api.url利用) と セッション認証 (kintone.api利用) を使用
      // パスワードやAPIトークンをコードに埋め込まないことで安全性を確保しています。
      kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', body, function(resp) {
        // 【トラブルシューティング】指定したフィールドコードが存在するかチェック
        if (resp.record && resp.record[CONFIG.RICH_TEXT_FIELD_CODE]) {
            const richTextValue = resp.record[CONFIG.RICH_TEXT_FIELD_CODE].value || '';
            
            renderContent(richTextValue);

            // 【セキュリティ】永続的なlocalStorageではなく、タブを閉じれば消えるsessionStorageを使用
            // 万が一、共用PC等でログアウトし忘れても、ブラウザを閉じればデータは消去されます。
            try {
              sessionStorage.setItem(CONFIG.CACHE_KEY, richTextValue);
            } catch (e) {
              console.warn('Cache full');
            }
        } else {
            console.error('Field Code Error: ' + CONFIG.RICH_TEXT_FIELD_CODE);
            container.innerHTML = '';
            if (errorContainer) {
                errorContainer.style.display = 'block';
                // 【セキュリティ】XSS対策のため、エラーメッセージ表示には innerText を使用
                errorContainer.innerText = '設定エラー: フィールドコード "' + CONFIG.RICH_TEXT_FIELD_CODE + '" が見つかりません。';
            }
        }

      }, function(error) {
        console.error(error);
        container.innerHTML = '';
        if (errorContainer) {
          errorContainer.style.display = 'block';
          // 【セキュリティ】エラー詳細をHTMLとして解釈させない
          errorContainer.innerText = 'データの取得に失敗しました。権限やID設定を確認してください。';
        }
      });
    };

    // 【セキュリティ】外部からの汚染された入力(URLパラメータ等)を使わず、ブラウザ内キャッシュのみ確認
    const cachedData = sessionStorage.getItem(CONFIG.CACHE_KEY);

    if (cachedData) {
      console.log('Displaying from cache');
      renderContent(cachedData);
    } else {
      fetchFromApi();
    }

    if (reloadBtn) {
      reloadBtn.onclick = function() {
        sessionStorage.removeItem(CONFIG.CACHE_KEY);
        fetchFromApi();
      };
    }

    const createRecordUrl = function() {
      // 【セキュリティ】現在のURLからドメインを取得(外部入力に依存しない)
      const origin = location.origin;
      
      // 【セキュリティ】https://またはhttp://で始まることを確認
      if (!origin || (!origin.startsWith('https://') && !origin.startsWith('http://'))) {
        console.error('Invalid origin:', origin);
        return null;
      }
      
      const recordUrl = origin + '/k/' + CONFIG.TARGET_APP_ID + '/show#record=' + CONFIG.TARGET_RECORD_ID;
      return recordUrl;
    };

    const createRecordLinkButton = function() {
      const recordUrl = createRecordUrl();
      if (!recordUrl) {
        console.warn('Failed to create record URL');
        return;
      }

      // 【セキュリティ】createElementで要素を作成(innerHTMLを使用しない)
      const recordLinkBtn = document.createElement('a');
      recordLinkBtn.href = recordUrl;
      // 【セキュリティ】innerTextを使用してXSSを防ぐ
      recordLinkBtn.innerText = '✎';
      recordLinkBtn.setAttribute('aria-label', 'レコード画面を開く');
      
      // 更新ボタンと同じクラスを適用(CSSでスタイル定義)
      if (reloadBtn && reloadBtn.className) {
        recordLinkBtn.className = reloadBtn.className;
      } else {
        recordLinkBtn.className = 'wiki-record-link-btn';
      }

      if (reloadBtn && reloadBtn.parentNode) {
        reloadBtn.parentNode.insertBefore(recordLinkBtn, reloadBtn.nextSibling);
      } else if (container && container.parentNode) {
        container.parentNode.insertBefore(recordLinkBtn, container);
      }
    };

    createRecordLinkButton();
    
    return event;
  });

})();

コピーをしたら前半の部分でアプリIDやレコードID,フィールドコードを指定します。

変更箇所入力内容入力例
TARGET_APP_ID: XXX,マニュアル管理を行っているアプリのアプリIDを指定します。TARGET_APP_ID: 304,
TARGET_RECORD_ID: YYY,マニュアル管理を行っているアプリのレコードIDを指定します。TARGET_RECORD_ID: 1,
RICH_TEXT_FIELD_CODE: ‘fieldname‘,マニュアル管理を行っているアプリのマニュアルが記載されているリッチテキストのフィールドコードを指定します。RICH_TEXT_FIELD_CODE: ‘リッチテキスト‘,

編集した内容を「customized-view-manual.js」として保存します。

3. CSSカスタマイズの追加

見た目の制御に必要です。

JavaScript同様の手順でメモ帳にコピーして「customized-view-manual.css」として保存します。

/* 2025/12/13 更新版 from Yugo Morita */

/*
 * Copyright (c) 2025 森田ユウゴ https://moritayugo.com/
 * License: GPL-3.0 (改変・再配布時は同じライセンスで公開してください)
 * https://www.gnu.org/licenses/gpl.html
 */

@charset "UTF-8";

/* メインコンテナ */
.view-manual {
  max-width: 900px;
  width: 100%;
  margin: 0 auto;
  background: white;
  border-radius: 8px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
  padding: 40px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  line-height: 1.6;
  color: #333;
  position: relative;
}

.view-manual * {
  box-sizing: border-box;
}

/* ツールバーエリア */
.wiki-toolbar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 20px;
  padding-bottom: 10px;
  border-bottom: 1px solid #e3e7e8;
}

/* kintoneライクなスマートボタン */
.kintone-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background-color: #f7f9fa;
  border: 1px solid #e3e7e8;
  color: #3498db;
  padding: 8px 16px;
  font-size: 13px;
  font-weight: bold;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s ease;
  outline: none;
  box-shadow: 0 1px 2px rgba(0,0,0,0.05);
  margin-left: 8px;
}

.kintone-btn:hover {
  background-color: #e2e6e8;
  color: #217dbb;
}

.kintone-btn:active {
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
  transform: translateY(1px);
}

/* アイコン(SVG)のスタイル */
.kintone-btn svg {
  width: 14px;
  height: 14px;
  fill: currentColor;
}

/* エラー表示エリア */
.error-box {
  color: #e74c3c;
  margin-top: 20px;
  font-weight: bold;
  background: #fde8e7;
  padding: 10px;
  border-radius: 4px;
}

/* 編集ボタン等の追加ボタンスタイル */
.wiki-record-link-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background-color: #f7f9fa;
  border: 1px solid #e3e7e8;
  color: #3498db;
  padding: 8px 16px;
  font-size: 13px;
  font-weight: bold;
  border-radius: 4px;
  cursor: pointer;
  margin-left: 8px;
  text-decoration: none;
}
.wiki-record-link-btn:hover {
  background-color: #e2e6e8;
  color: #217dbb;
}

4. ファイルのアップロードと適用

作成したJSファイルとCSSファイルを、アプリ設定の「JavaScript / CSSでカスタマイズ」画面からアップロードし、アプリを更新すれば完了です!

活用シーン:こんな「困った」が解決します

この機能は、単なるマニュアル表示だけでなく、アイデア次第で様々な業務改善に活用できます。

ケース1:アプリの「トップページ(ランディングページ)」化

あなた
あなた

このアプリ、どこから触ればいいの?

課題

複雑なアプリを開いた瞬間、大量のデータ行(レコード)が目に飛び込んでくると、初心者は「どこから触ればいいの?」と委縮してしまいます。

また、重要な周知事項(「今月の締め日は〇日です」など)も、一覧画面の端っこでは見落とされがちです。

カスタムビューマニュアル導入後

この「マニュアルビュー」をアプリの「デフォルト(一番最初に表示される)一覧」に設定します。 すると、アプリを開いた全社員が必ず最初に「今月の重要なお知らせ」や「操作ガイダンス」を目にすることになります。

データを見たい時は、ワンクリックで標準の一覧に切り替えるだけ。「必ず読んでほしい情報」の到達率が100%になります。

ケース2:入力ルールが複雑な業務の「辞書(Wiki)」として

あなた
あなた

マニュアル探すの面倒だから適当に入力しよう

想定課題

「経費精算」や「法務相談」など、入力ルールが厳格なアプリ。入力中に迷った際、わざわざ別のファイルサーバーにあるPDFマニュアルを探しに行くのは手間がかかり、結局「適当に入力」されてしまいます。

導入後

アプリ内の一覧切り替えドロップダウンに「⚠️入力ルール(Wiki)」というビューを用意します。

迷ったらその場でビューを切り替えるだけで、最新のルールや「勘定科目一覧表」などが確認できます。別ウィンドウを開く必要がなく、アプリ内だけで完結するため、正しいルールの参照頻度が劇的に向上します。

ケース3:新人専用の「ナビゲーションメニュー」

想定課題

kintoneには「レコード追加画面(+ボタン)」や「未処理一覧」など、機能が点在しており、新人には動線が分かりにくいことがあります。

導入後

マニュアルビューの中に、文章だけでなく「新規登録はこちら」「未対応案件のチェックはこちら」といったリンクボタンを大きく配置します(リッチテキスト内のリンク機能を利用)。マニュアル間の連携に使うことで、最終的に必要な情報にたどることが出来ます。

これにより、このビューが「業務のメニュー画面」として機能し、新人はそこから迷わず次の作業へ移動できるようになります。

エンジニアとしてののこだわり:「3つの技術的品質」

ここからは、具体的にどのようなこだわりが反映されているかについてご紹介いたします。

作成したのは単に「動けばいい」というコードではありません。長く安心して使っていただくために、エンジニアとして「保守性」「パフォーマンス」「セキュリティ」の3点に徹底的にこだわりました。

API制限を回避する「超高速キャッシュ機能」

kintoneにはAPIの同時接続数や日次リクエスト数の制限があります。 単純に毎回APIを叩いてマニュアルデータを取得すると、動作が重くなるだけでなく、制限に引っかかるリスクがあります。

そこで、sessionStorage を活用したクライアントサイドキャッシュを実装しました。

  • 初回アクセス時のみAPIを実行し、データを取得。
  • タブを開いている間はキャッシュを利用するため、画面遷移やリロード時のAPI消費は「ゼロ」
  • マニュアル更新時用に、キャッシュを破棄して再取得する「手動更新ボタン」も実装。

これにより、爆速のレスポンスを実現しています。

企業のセキュリティを守る「堅牢な設計」

社内でしか使わないシステムであってもセキュリティは最優先事項です。Cybozu社のセキュアコーディングガイドラインに準拠して設計しました。

  • XSS(クロスサイトスクリプティング)対策: 外部からの悪意あるコード埋め込みを防ぐため、安全なDOM操作メソッドを使用。
  • 認証情報の保護: APIトークンをコードに埋め込まず、kintoneのセッション認証(kintone.api)を正しく利用。

「kintoneライク」なUIデザイン

ユーザーが違和感なく使えるよう、デザインの親和性を重視しました。

  • 更新ボタン等は、kintone標準の「絞り込みボタン」等のデザインを参考にしたCSSを適用。
  • 外部アイコンフォント(FontAwesome等)に依存せず、SVGアイコンをインラインで埋め込むことで、読み込み速度と安定性を確保。

想定される質問(FAQ)

本カスタマイズの導入をご検討いただくにあたり、想定される質問をまとめました。

Q. スマホ(モバイル版アプリ)でも表示されますか?

kintoneの仕様上、PC版とモバイル版ではカスタマイズの挙動が異なります。 今回の基本設計はPCブラウザ向けです

森田ユウゴ
森田ユウゴ

もし、個別にカスタマイズが必要であれば、有償で承ります!

Q. すでに運用中のアプリに追加できますか?

はい、可能です。 既存のアプリに「カスタマイズビュー」の設定を追加し、マニュアル管理用のフィールドを準備するだけで導入できます。今あるデータを消す必要はありません。

さいごに:運用まで考えたカスタマイズを依頼しませんか?

kintoneのカスタマイズ開発において、私は常に「納品した後、お客様だけで運用し続けられるか?」を最優先に考えています。

「コードを書けば何でもできる」のは当たり前です。 しかし、ちょっとした文字修正のたびにエンジニアに連絡し、追加費用を払い、修正を待つ…そんなシステムは、決して「便利」とは言えません。

今回ご紹介した「CMS型ビュー」は、エンジニアの手を離れ、現場の皆様だけでPDCAを回していける仕組みです。

  • マニュアルが読まれなくて困っている
  • アプリの説明をもっとリッチにしたい
  • セキュリティや表示速度もしっかり考慮した開発をしてほしい

このようにお考えの方は、ぜひ一度ご相談ください。 単に「動くもの」を作るのではなく、あなたの会社の業務フローに寄り添った、持続可能なシステムをご提案します。

📩 開発のご依頼・ご相談はこちら

まずは「ブログ記事(CMSビュー)を見た」とお気軽にメッセージをお送りください。現状のアプリ構成を見せていただければ、最適な導入方法をご案内いたします。

スキルプラットフォーム「ココナラ」では、Ankiのカスタマイズを私「森田ユウゴ」に依頼ができます!

ご相談はこちらから▼
森田ユウゴのプロフィール | ココナラ

Ankiユーザーでもあり、「面倒解決エンジニア」の私にご依頼をお待ちしております!

ABOUT ME
森田ユウゴ(Yugo Morita)
森田ユウゴ(Yugo Morita)
面倒解決エンジニア
専門学校卒業後、新卒で大手ITベンダーに入社して約10年勤務、働きながら通信制大学も卒業。その後1年ほどXR/メタバース関連企業で挑戦後、現在は台湾のIT企業でソフトウェアエンジニアとして勤務。プライベートでは「面倒をなくしたい」想いでAI駆動の便利ツール(Chrome拡張/kintone等)開発に熱中しています。尽きない好奇心とインフラ経験、AIであなたの「不便」を解決へ導きます。
記事URLをコピーしました