GAS×プロパティサービス|設定値・APIキーの安全な管理方法
GASでAPIキーをコードに直接書くのは危険だ。スクリプトを共有した瞬間にキーが漏洩する。
設定値やAPIキーを安全に保管する手段として、GASにはPropertiesServiceが用意されている。キーと値のペアで永続保存でき、スクリプトエディタからも中身を直接見られない仕組みになっている。
ここでは、PropertiesServiceの基本操作から3種類のプロパティの使い分け、設定画面の作成まで、実用的なコードとあわせて解説する。
プロパティサービスとは
概要
PropertiesServiceは、GASでキーと値のペアを永続的に保存する機能だ。
// 保存
PropertiesService.getScriptProperties().setProperty('API_KEY', 'sk-xxxxx');
// 取得
const apiKey = PropertiesService.getScriptProperties().getProperty('API_KEY');
保存したデータはスクリプトを閉じても消えない。APIキーや設定値など、コードに直書きしたくない情報の保管場所として使える。
なぜプロパティサービスを使うべきか
| 方法 | セキュリティ | 共有時 |
|---|---|---|
| コードに直書き | ❌ 危険 | APIキーが漏洩 |
| スプレッドシートに記載 | ⚠️ 注意 | 閲覧権限で見える |
| PropertiesService | ✅ 安全 | 値は見えない |
PropertiesServiceに保存した値は、スクリプトエディタからも直接見ることができない。プロパティを取得する関数を実行しない限り、値にアクセスできない。
3種類のプロパティ
PropertiesServiceには用途に応じた3種類のプロパティがある。
比較表
| 種類 | スコープ | 主な用途 |
|---|---|---|
| Script Properties | スクリプト全体 | APIキー、共通設定 |
| User Properties | ユーザーごと | 個人設定、認証情報 |
| Document Properties | ドキュメントごと | ファイル固有の設定 |
Script Properties(スクリプトプロパティ)
スクリプト全体で共有される設定。誰が実行しても同じ値を取得できる。
// Script Propertiesの操作
const scriptProps = PropertiesService.getScriptProperties();
// 保存
scriptProps.setProperty('API_KEY', 'sk-xxxxxxxxxxxxx');
// 取得
const apiKey = scriptProps.getProperty('API_KEY');
// 削除
scriptProps.deleteProperty('API_KEY');
// 全て取得
const allProps = scriptProps.getProperties();
console.log(allProps); // { API_KEY: 'sk-xxx', OTHER_KEY: 'value' }
// 全て削除
scriptProps.deleteAllProperties();
用途例:
- APIキー(OpenAI、Slack等)
- Webhook URL
- 共通の閾値・設定値
User Properties(ユーザープロパティ)
実行ユーザーごとに異なる値を保存できる。Aさんが保存した値はBさんからは見えない。
// User Propertiesの操作
const userProps = PropertiesService.getUserProperties();
// 保存(このユーザー専用)
userProps.setProperty('THEME', 'dark');
userProps.setProperty('NOTIFICATION', 'true');
// 取得
const theme = userProps.getProperty('THEME');
console.log(theme); // 'dark'
用途例:
- ユーザーの表示設定(テーマ、言語)
- 通知設定のオン/オフ
- 個人の認証トークン
Document Properties(ドキュメントプロパティ)
ドキュメント(スプレッドシート等)ごとに保存される。同じスクリプトでも、紐づくファイルが違えば別の値を持つ。
// Document Propertiesの操作
const docProps = PropertiesService.getDocumentProperties();
// 保存
docProps.setProperty('LAST_PROCESSED_ROW', '150');
docProps.setProperty('SHEET_VERSION', '2.0');
// 取得
const lastRow = docProps.getProperty('LAST_PROCESSED_ROW');
用途例:
- 処理済み行数(バッチ処理の続行用)
- ファイル固有の設定
- バージョン情報
注意: Document Propertiesはスプレッドシート等にバインドされたスクリプトでのみ使用可能。スタンドアロンスクリプトでは使えない。
PropertiesServiceの基本操作
主要メソッド一覧
| メソッド | 説明 |
|---|---|
getProperty(key) |
値を取得(なければnull) |
setProperty(key, value) |
値を保存 |
deleteProperty(key) |
指定キーを削除 |
getProperties() |
全プロパティをオブジェクトで取得 |
setProperties(obj) |
複数プロパティを一括保存 |
deleteAllProperties() |
全プロパティを削除 |
基本操作のコード例
/**
* プロパティの基本操作サンプル
*/
function propertiesBasicExample() {
const props = PropertiesService.getScriptProperties();
// === 単一の値を操作 ===
// 保存
props.setProperty('APP_NAME', 'My GAS App');
props.setProperty('VERSION', '1.0.0');
// 取得
const appName = props.getProperty('APP_NAME');
console.log('アプリ名:', appName);
// 存在しないキーはnullが返る
const notExists = props.getProperty('NOT_EXISTS');
console.log('存在しないキー:', notExists); // null
// === 複数の値を一括操作 ===
// 一括保存
props.setProperties({
'DB_HOST': 'localhost',
'DB_PORT': '5432',
'DB_NAME': 'myapp'
});
// 全て取得
const allProps = props.getProperties();
console.log('全プロパティ:', allProps);
// === 削除 ===
// 単一削除
props.deleteProperty('VERSION');
// 全削除(注意!)
// props.deleteAllProperties();
}
値の型に注意
PropertiesServiceは文字列しか保存できない。数値やオブジェクトを扱う場合は変換が必要になる。
/**
* 数値・オブジェクトの保存と取得
*/
function saveNonStringValues() {
const props = PropertiesService.getScriptProperties();
// 数値を保存(文字列に変換)
const count = 100;
props.setProperty('COUNT', count.toString());
// 数値を取得(文字列から変換)
const savedCount = parseInt(props.getProperty('COUNT'), 10);
console.log('カウント:', savedCount, typeof savedCount); // 100 number
// オブジェクトを保存(JSONに変換)
const config = {
enabled: true,
threshold: 50,
targets: ['A', 'B', 'C']
};
props.setProperty('CONFIG', JSON.stringify(config));
// オブジェクトを取得(JSONからパース)
const savedConfig = JSON.parse(props.getProperty('CONFIG'));
console.log('設定:', savedConfig);
console.log('有効:', savedConfig.enabled); // true
console.log('閾値:', savedConfig.threshold); // 50
}
実践例① APIキーの安全な保存
外部APIのキーを安全に管理する手順を示す。
初期設定(1回だけ実行)
/**
* APIキーの初期設定(1回だけ実行)
*/
function setupAPIKeys() {
const props = PropertiesService.getScriptProperties();
// ここにAPIキーを設定(実行後はコードからAPIキーを削除!)
props.setProperties({
'OPENAI_API_KEY': 'sk-xxxxxxxxxxxxxxxxxxxxxxxx',
'SLACK_WEBHOOK_URL': 'https://hooks.slack.com/services/xxx/xxx/xxx',
'LINE_CHANNEL_TOKEN': 'xxxxxxxxxxxxxxxxxxxxxxxx'
});
console.log('APIキー設定完了');
// 重要: 設定完了後、このコードのAPIキー部分を削除すること!
}
APIキーを使う関数
/**
* OpenAI APIを呼び出す(キーはプロパティから取得)
*/
function callOpenAI(prompt) {
// プロパティからAPIキーを取得
const apiKey = PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY');
if (!apiKey) {
throw new Error('APIキーが設定されていません。setupAPIKeys()を実行してください。');
}
const url = 'https://api.openai.com/v1/chat/completions';
const payload = {
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
max_tokens: 500
};
const options = {
method: 'post',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + apiKey
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
const json = JSON.parse(response.getContentText());
return json.choices[0].message.content;
}
設定済みか確認する関数
/**
* 必要なAPIキーが設定されているか確認
*/
function checkAPIKeysConfigured() {
const props = PropertiesService.getScriptProperties();
const requiredKeys = ['OPENAI_API_KEY', 'SLACK_WEBHOOK_URL'];
const missing = requiredKeys.filter(key => !props.getProperty(key));
if (missing.length > 0) {
console.error('未設定のAPIキー:', missing.join(', '));
return false;
}
console.log('全てのAPIキーが設定済みです');
return true;
}
実践例② ユーザーごとの設定保存
複数ユーザーで使うスクリプトで、個人設定を保存する方法。
/**
* ユーザー設定の管理
*/
const UserSettings = {
// デフォルト設定
defaults: {
theme: 'light',
language: 'ja',
notifications: true,
itemsPerPage: 20
},
// 設定を取得
get: function(key) {
const props = PropertiesService.getUserProperties();
const value = props.getProperty(key);
if (value === null) {
return this.defaults[key];
}
// boolean/numberの復元
if (value === 'true') return true;
if (value === 'false') return false;
if (!isNaN(value)) return Number(value);
return value;
},
// 設定を保存
set: function(key, value) {
const props = PropertiesService.getUserProperties();
props.setProperty(key, String(value));
},
// 全設定を取得
getAll: function() {
const props = PropertiesService.getUserProperties();
const saved = props.getProperties();
// デフォルトとマージ
return { ...this.defaults, ...saved };
},
// 設定をリセット
reset: function() {
PropertiesService.getUserProperties().deleteAllProperties();
}
};
/**
* 使用例
*/
function userSettingsExample() {
// 設定を取得(未設定ならデフォルト値)
console.log('テーマ:', UserSettings.get('theme')); // 'light'
// 設定を保存
UserSettings.set('theme', 'dark');
UserSettings.set('itemsPerPage', 50);
// 再取得
console.log('テーマ:', UserSettings.get('theme')); // 'dark'
console.log('表示件数:', UserSettings.get('itemsPerPage')); // 50
// 全設定を表示
console.log('全設定:', UserSettings.getAll());
}
実践例③ 設定画面の作成
HTMLダイアログで設定画面を作り、プロパティを管理する方法。
GASコード(Code.gs)
/**
* 設定画面を表示
*/
function showSettingsDialog() {
const html = HtmlService.createHtmlOutputFromFile('Settings')
.setWidth(400)
.setHeight(300)
.setTitle('設定');
SpreadsheetApp.getUi().showModalDialog(html, '設定');
}
/**
* 現在の設定を取得(HTML側から呼び出し)
*/
function getSettings() {
const props = PropertiesService.getScriptProperties();
return {
apiKey: props.getProperty('API_KEY') || '',
webhookUrl: props.getProperty('WEBHOOK_URL') || '',
enableNotification: props.getProperty('ENABLE_NOTIFICATION') === 'true'
};
}
/**
* 設定を保存(HTML側から呼び出し)
*/
function saveSettings(settings) {
const props = PropertiesService.getScriptProperties();
props.setProperty('API_KEY', settings.apiKey);
props.setProperty('WEBHOOK_URL', settings.webhookUrl);
props.setProperty('ENABLE_NOTIFICATION', String(settings.enableNotification));
return { success: true, message: '設定を保存しました' };
}
/**
* メニューに追加
*/
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('⚙️ 設定')
.addItem('設定を開く', 'showSettingsDialog')
.addToUi();
}
HTMLファイル(Settings.html)
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="text"], input[type="password"] {
width: 100%; padding: 8px; box-sizing: border-box;
}
.checkbox-group { display: flex; align-items: center; }
.checkbox-group input { margin-right: 8px; }
button {
background: #4285f4; color: white; padding: 10px 20px;
border: none; border-radius: 4px; cursor: pointer;
}
button:hover { background: #3367d6; }
.message { margin-top: 10px; padding: 10px; border-radius: 4px; }
.success { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
</style>
</head>
<body>
<div class="form-group">
<label>APIキー</label>
<input type="password" id="apiKey" placeholder="sk-xxxxx">
</div>
<div class="form-group">
<label>Webhook URL</label>
<input type="text" id="webhookUrl" placeholder="https://...">
</div>
<div class="form-group checkbox-group">
<input type="checkbox" id="enableNotification">
<label for="enableNotification">通知を有効にする</label>
</div>
<button onclick="save()">保存</button>
<div id="message"></div>
<script>
// 画面読み込み時に現在の設定を取得
google.script.run
.withSuccessHandler(function(settings) {
document.getElementById('apiKey').value = settings.apiKey;
document.getElementById('webhookUrl').value = settings.webhookUrl;
document.getElementById('enableNotification').checked = settings.enableNotification;
})
.getSettings();
// 保存
function save() {
const settings = {
apiKey: document.getElementById('apiKey').value,
webhookUrl: document.getElementById('webhookUrl').value,
enableNotification: document.getElementById('enableNotification').checked
};
google.script.run
.withSuccessHandler(function(result) {
showMessage(result.message, 'success');
})
.withFailureHandler(function(error) {
showMessage('エラー: ' + error.message, 'error');
})
.saveSettings(settings);
}
function showMessage(text, type) {
const el = document.getElementById('message');
el.textContent = text;
el.className = 'message ' + type;
}
</script>
</body>
</html>
セキュリティのベストプラクティス
1. APIキーをコードに書かない
// ❌ NG: コードに直接書く
const API_KEY = 'sk-xxxxxxxxxxxxxxxx';
// ✅ OK: プロパティから取得
const API_KEY = PropertiesService.getScriptProperties().getProperty('API_KEY');
2. 初期設定後はコードからキーを削除
// 初期設定関数を実行したら、このコードのAPIキー部分を削除する
function setupAPIKeys() {
props.setProperty('API_KEY', 'sk-xxxx'); // ← 実行後に削除!
}
3. 権限を最小限に
- スクリプトの共有は必要最小限にとどめる
- 編集者権限で共有するとプロパティにアクセスされる可能性がある
4. 定期的なキーローテーション
/**
* APIキーの有効期限チェック
*/
function checkKeyExpiration() {
const props = PropertiesService.getScriptProperties();
const lastRotated = props.getProperty('KEY_LAST_ROTATED');
if (lastRotated) {
const daysSinceRotation = (Date.now() - new Date(lastRotated)) / (1000 * 60 * 60 * 24);
if (daysSinceRotation > 90) {
console.warn('APIキーを90日以上更新していません。ローテーションを検討してください。');
}
}
}
まとめ
ポイント
- 3種類のプロパティ: Script(共通)、User(個人)、Document(ファイル)
- APIキーは必ずプロパティに保存し、コードには直書きしない
- 保存できるのは文字列のみ。数値やオブジェクトはJSON変換が必要
- HTMLダイアログと組み合わせれば管理UIも構築できる
プロパティ選択の目安
| 用途 | 推奨プロパティ |
|---|---|
| APIキー | Script Properties |
| ユーザー設定 | User Properties |
| 処理状態の保存 | Document Properties |
セキュリティチェックリスト
- [ ] APIキーがコードに直書きされていない
- [ ] 初期設定後、コードからキーを削除した
- [ ] スクリプトの共有権限は最小限
- [ ] 定期的にキーをローテーション
PropertiesServiceを使いこなせば、GASのセキュリティと運用の質が一段上がる。
コメント