From kaseya-datto-rmm
Manages Datto RMM account and site variables for configuration: scopes, inheritance, CRUD via REST API, script usage, naming conventions.
npx claudepluginhub wyre-technology/msp-claude-plugins --plugin datto-rmmThis skill uses the workspace's default tool permissions.
Variables in Datto RMM store key-value configuration data at account or site level. They're used to customize component scripts, store configuration values, and maintain environment-specific settings. This skill covers variable management, scoping, and usage patterns.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides agent creation for Claude Code plugins with file templates, frontmatter specs (name, description, model), triggering examples, system prompts, and best practices.
Variables in Datto RMM store key-value configuration data at account or site level. They're used to customize component scripts, store configuration values, and maintain environment-specific settings. This skill covers variable management, scoping, and usage patterns.
| Scope | Description | Use Case |
|---|---|---|
| Account | Available to all sites | Global configuration |
| Site | Available to specific site | Client-specific settings |
Account Variables (Global)
│
▼ (inherited by)
Site Variables
│
▼ (used in)
Jobs/Components
Site variables can override account variables with the same name.
All variables are stored as strings but can represent:
interface Variable {
id: number; // Variable ID
name: string; // Variable name
value: string; // Variable value
description?: string; // Optional description
scope: VariableScope; // "account" or "site"
siteUid?: string; // Site UID (for site variables)
createdAt: number; // Creation timestamp
modifiedAt: number; // Last modification
}
type VariableScope = 'account' | 'site';
Recommended Format: SCREAMING_SNAKE_CASE
Examples:
BACKUP_PATHADMIN_EMAILLOG_RETENTION_DAYSANTIVIRUS_SCAN_SCHEDULEReserved Prefixes:
CS_ - Datto internal useDATTO_ - System variablesGET /api/v2/account/variables
Authorization: Bearer {token}
Response:
{
"variables": [
{
"id": 1,
"name": "BACKUP_PATH",
"value": "D:\\Backups",
"description": "Default backup destination",
"scope": "account",
"createdAt": 1680000000000,
"modifiedAt": 1707991200000
},
{
"id": 2,
"name": "ADMIN_EMAIL",
"value": "alerts@msp.com",
"scope": "account"
}
]
}
GET /api/v2/site/{siteUid}/variables
Authorization: Bearer {token}
Response:
{
"variables": [
{
"id": 101,
"name": "BACKUP_PATH",
"value": "E:\\ClientBackups",
"description": "Client-specific backup path",
"scope": "site",
"siteUid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
{
"id": 102,
"name": "CLIENT_CODE",
"value": "ACME",
"scope": "site",
"siteUid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
]
}
POST /api/v2/account/variables
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "NEW_VARIABLE",
"value": "variable_value",
"description": "Description of the variable"
}
POST /api/v2/site/{siteUid}/variables
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "SITE_SPECIFIC_VAR",
"value": "site_value",
"description": "Site-specific configuration"
}
PUT /api/v2/account/variable/{variableId}
Authorization: Bearer {token}
Content-Type: application/json
{
"value": "updated_value",
"description": "Updated description"
}
For site variables:
PUT /api/v2/site/{siteUid}/variable/{variableId}
DELETE /api/v2/account/variable/{variableId}
Authorization: Bearer {token}
For site variables:
DELETE /api/v2/site/{siteUid}/variable/{variableId}
Account variables can be overridden by site variables:
async function getEffectiveVariable(client, siteUid, variableName) {
// Try site variable first
const siteVars = await client.request(`/api/v2/site/${siteUid}/variables`);
const siteVar = siteVars.variables?.find(v => v.name === variableName);
if (siteVar) {
return {
name: variableName,
value: siteVar.value,
scope: 'site',
source: `Site: ${siteUid}`
};
}
// Fall back to account variable
const accountVars = await client.request('/api/v2/account/variables');
const accountVar = accountVars.variables?.find(v => v.name === variableName);
if (accountVar) {
return {
name: variableName,
value: accountVar.value,
scope: 'account',
source: 'Account'
};
}
return {
name: variableName,
value: null,
error: 'Variable not found'
};
}
async function setupSiteVariables(client, siteUid, variables) {
const results = [];
for (const [name, value] of Object.entries(variables)) {
try {
await client.request(`/api/v2/site/${siteUid}/variables`, {
method: 'POST',
body: JSON.stringify({
name,
value,
description: `Auto-created: ${new Date().toISOString()}`
})
});
results.push({ name, success: true });
} catch (error) {
results.push({ name, success: false, error: error.message });
}
await sleep(100);
}
return results;
}
async function auditVariables(client, siteUids) {
const report = {
account: [],
sites: {}
};
// Get account variables
const accountVars = await client.request('/api/v2/account/variables');
report.account = accountVars.variables || [];
// Get site variables
for (const siteUid of siteUids) {
try {
const siteVars = await client.request(`/api/v2/site/${siteUid}/variables`);
report.sites[siteUid] = siteVars.variables || [];
} catch (error) {
report.sites[siteUid] = { error: error.message };
}
await sleep(100);
}
return report;
}
async function findVariableOverrides(client, variableName) {
const accountVars = await client.request('/api/v2/account/variables');
const accountVar = accountVars.variables?.find(v => v.name === variableName);
const sitesResponse = await client.request('/api/v2/sites?max=250');
const sites = sitesResponse.sites || [];
const overrides = [];
for (const site of sites) {
try {
const siteVars = await client.request(`/api/v2/site/${site.uid}/variables`);
const siteVar = siteVars.variables?.find(v => v.name === variableName);
if (siteVar) {
overrides.push({
siteName: site.name,
siteUid: site.uid,
value: siteVar.value,
isOverride: accountVar ? true : false
});
}
} catch (error) {
// Skip sites with errors
}
await sleep(100);
}
return {
variableName,
accountValue: accountVar?.value || null,
overrides,
overrideCount: overrides.length
};
}
async function applyVariableTemplate(client, siteUid, template) {
/*
* Template format:
* {
* "BACKUP_PATH": "D:\\Backups\\{SITE_NAME}",
* "LOG_RETENTION_DAYS": "30",
* "ADMIN_EMAIL": "{inherit}"
* }
*/
const site = await client.request(`/api/v2/site/${siteUid}`);
const results = [];
for (const [name, valueTemplate] of Object.entries(template)) {
// Skip inherited variables
if (valueTemplate === '{inherit}') {
results.push({ name, action: 'inherited' });
continue;
}
// Replace placeholders
const value = valueTemplate
.replace('{SITE_NAME}', site.name)
.replace('{SITE_UID}', siteUid);
try {
await client.request(`/api/v2/site/${siteUid}/variables`, {
method: 'POST',
body: JSON.stringify({ name, value })
});
results.push({ name, action: 'created', value });
} catch (error) {
if (error.message?.includes('already exists')) {
// Update existing
const existing = await findVariableByName(client, siteUid, name);
if (existing) {
await client.request(`/api/v2/site/${siteUid}/variable/${existing.id}`, {
method: 'PUT',
body: JSON.stringify({ value })
});
results.push({ name, action: 'updated', value });
}
} else {
results.push({ name, action: 'error', error: error.message });
}
}
await sleep(100);
}
return results;
}
async function findVariableByName(client, siteUid, name) {
const response = await client.request(`/api/v2/site/${siteUid}/variables`);
return response.variables?.find(v => v.name === name);
}
| Error | Status | Cause | Resolution |
|---|---|---|---|
| Variable exists | 400 | Duplicate name | Use PUT to update |
| Variable not found | 404 | Invalid ID | Verify variable exists |
| Invalid name | 400 | Reserved prefix or invalid chars | Use valid naming |
| Permission denied | 403 | API restrictions | Check permissions |
async function safeSetVariable(client, scope, siteUid, name, value) {
// Validate name
if (name.startsWith('CS_') || name.startsWith('DATTO_')) {
return { success: false, error: 'Reserved prefix' };
}
if (!/^[A-Z][A-Z0-9_]*$/.test(name)) {
return { success: false, error: 'Invalid name format. Use SCREAMING_SNAKE_CASE' };
}
const baseUrl = scope === 'site'
? `/api/v2/site/${siteUid}/variables`
: '/api/v2/account/variables';
try {
// Try to create
await client.request(baseUrl, {
method: 'POST',
body: JSON.stringify({ name, value })
});
return { success: true, action: 'created' };
} catch (error) {
if (error.status === 400) {
// Try to update
const existing = scope === 'site'
? await findVariableByName(client, siteUid, name)
: await findAccountVariableByName(client, name);
if (existing) {
const updateUrl = scope === 'site'
? `/api/v2/site/${siteUid}/variable/${existing.id}`
: `/api/v2/account/variable/${existing.id}`;
await client.request(updateUrl, {
method: 'PUT',
body: JSON.stringify({ value })
});
return { success: true, action: 'updated' };
}
}
return { success: false, error: error.message };
}
}
async function findAccountVariableByName(client, name) {
const response = await client.request('/api/v2/account/variables');
return response.variables?.find(v => v.name === name);
}
BACKUP_*, ALERT_*, LOG_*| Category | Examples | Purpose |
|---|---|---|
| Backup | BACKUP_PATH, BACKUP_RETENTION_DAYS | Backup configuration |
| Logging | LOG_PATH, LOG_LEVEL | Log settings |
| Alerting | ALERT_EMAIL, ALERT_THRESHOLD | Alert configuration |
| Security | AV_SCAN_SCHEDULE, FIREWALL_ENABLED | Security settings |
| Client | CLIENT_CODE, CLIENT_CONTACT | Client identification |
Variables are referenced in component scripts using Datto's syntax:
PowerShell:
$backupPath = $env:BACKUP_PATH
$retentionDays = $env:LOG_RETENTION_DAYS
# Use the variables
Get-ChildItem -Path $backupPath -Recurse |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$retentionDays) } |
Remove-Item
Bash:
BACKUP_PATH="${BACKUP_PATH:-/backup}"
RETENTION_DAYS="${LOG_RETENTION_DAYS:-30}"
find "$BACKUP_PATH" -type f -mtime +$RETENTION_DAYS -delete