
Activate Site
- 122 installs
- 572 repo stars
- Updated July 28, 2026
- microsoft/power-platform-skills
>-.
About
>-. > **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. The activate-site skill documents workflows, constraints, and examples from SKILL.md for agent-assisted execution.
- > **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user
- # Activate Power Pages Site
- Provision a new Power Pages website in a Power Platform environment via the Power Platform REST API.
- > **Prerequisite:** This skill expects an existing Power Pages code site created via `/create-site`. Run that skill firs
- **Cloud-aware URL resolution** — Never hardcode API base URLs or site URL domains. Always derive them from the Cloud val
Activate Site by the numbers
- 122 all-time installs (skills.sh)
- Ranked #3,735 of 16,659 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
activate-site capabilities & compatibility
- Capabilities
- > **plugin check**: run `node "${plugin_root}/sc · # activate power pages site · provision a new power pages website in a power p · > **prerequisite:** this skill expects an existi
- Use cases
- documentation
What activate-site says it does
>-
npx skills add https://github.com/microsoft/power-platform-skills --skill activate-siteAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 122 |
|---|---|
| repo stars | ★ 572 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 28, 2026 |
| Repository | microsoft/power-platform-skills ↗ |
How do I apply activate-site using the workflow in its SKILL.md?
>-
Who is it for?
Developers following the activate-site skill for the tasks it documents.
Skip if: Tasks outside the activate-site scope described in SKILL.md.
When should I use this skill?
User mentions activate-site or related triggers from the skill description.
What you get
Working activate-site setup aligned with the documented patterns and constraints.
Files
Plugin check: Run node "${PLUGIN_ROOT}/scripts/check-version.js" — if it outputs a message, show it to the user before proceeding.Activate Power Pages Site
Provision a new Power Pages website in a Power Platform environment via the Power Platform REST API.
Prerequisite: This skill expects an existing Power Pages code site created via /create-site. Run that skill first if the site does not exist yet.Core Principles
- Cloud-aware URL resolution — Never hardcode API base URLs or site URL domains. Always derive them from the Cloud value returned by
pac auth who. - Token handling — Scripts acquire and refresh Azure CLI tokens internally. The agent only needs to verify the user is logged in to Azure CLI.
- Confirm before mutating — Always present the full activation parameters to the user and get explicit approval before POSTing to the websites API.
Initial request: $ARGUMENTS
Workflow
1. Phase 1: Verify Prerequisites — PAC CLI auth + Azure CLI login + activation status check 2. Phase 2: Gather Parameters — Site name, subdomain, website record ID 3. Phase 3: Confirm — Present all parameters to user for approval 4. Phase 4: Activate & Poll — Run activation script (POST + poll provisioning status) 5. Phase 5: Present Summary — Show site URL, suggest next steps
---
Phase 1: Verify Prerequisites
Goal: Ensure PAC CLI is installed and authenticated, and verify the user is logged in to Azure CLI (scripts handle token acquisition internally).
Actions
1.1 Verify PAC CLI
Run pac help to check if the PAC CLI is installed and available on the system PATH.
pac helpIf the command fails (command not found / not recognized):
1. Tell the user: "PAC CLI is not installed. You can install it by running:"
dotnet tool install --global Microsoft.PowerApps.CLI.Tool2. If dotnet is also not available, direct the user to <https://aka.ms/PowerPlatformCLI> for full installation instructions. 3. After installation, verify by running pac help again.
1.2 Check Authentication
Run pac auth who to check current authentication status.
pac auth whoIf authenticated: Extract these values from the output:
- Environment ID — the GUID after
Environment ID: - Organization ID — the GUID after
Organization ID:(this is the Dataverse org ID) - Cloud — the value after
Cloud:(e.g.,Public,UsGov,UsGovHigh,UsGovDod,China)
If not authenticated: Follow the same authentication flow as deploy-site — ask the user for their environment URL and run pac auth create --environment "<URL>".
1.3 Verify Azure CLI Login
Verify the user is logged in to Azure CLI (the activation scripts acquire tokens internally):
az account showIf `az` is not installed or not logged in: Instruct the user to install Azure CLI and run az login --allow-no-subscriptions (this form works whether or not the user has an Azure subscription — the activation flow only needs an AAD token).
1.4 Check If Already Activated
Before gathering parameters, check whether the site is already activated by running the shared activation status script:
node "${PLUGIN_ROOT}/scripts/check-activation-status.js" --projectRoot "<PROJECT_ROOT>"Where <PROJECT_ROOT> is the directory containing powerpages.config.json or .powerpages-site folder. Evaluate the JSON result:
- If `activated` is `true`: Inform the user their site is already activated at
websiteUrl. Suggest next steps (Phase 5.3) and stop — do NOT proceed to Phase 2. - If `activated` is `false`: Proceed to Phase 2.
- If `error` is present: Proceed to Phase 2 (do not block the activation flow).
Output
- PAC CLI installed and authenticated
- Environment ID, Organization ID, and Cloud value extracted
- Azure CLI login confirmed
- Activation status checked (already activated → stop early, not activated → continue)
---
Phase 2: Gather Parameters
Goal: Determine the site name, generate or accept a subdomain, and look up the website record ID needed for the activation API call.
Actions
2.1 Read Site Name
Look for powerpages.config.json in the current directory or one level of subdirectories using Glob:
**/powerpages.config.jsonRead the file and extract the siteName field. If not found, ask the user for the site name using AskUserQuestion.
2.2 Generate Subdomain Suggestion
CRITICAL — This step is MANDATORY. You MUST ask the user about the subdomain before proceeding. Do NOT skip this step or auto-select a subdomain without user input.
Run the subdomain generator script to create a random suggestion:
node "${PLUGIN_ROOT}/skills/activate-site/scripts/generate-subdomain.js"This outputs a string like site-a3f2b1. Resolve the correct site URL domain from the Cloud value obtained in Phase 1.2:
| Cloud | Site URL Domain |
|---|---|
Public | powerappsportals.com |
UsGov | powerappsportals.us |
UsGovHigh | high.powerappsportals.us |
UsGovDod | appsplatform.us |
China | powerappsportals.cn |
<!-- gate: activate-site:2.2.subdomain | category=plan | cancel-leaves=nothing -->
🚦 Gate (plan · activate-site:2.2.subdomain): Confirm or override the generated subdomain. Subdomain is part of the resulting site URL; Cancel exits before any provisioning call.
Present the generated subdomain to the user and ask them to accept or enter their own using AskUserQuestion:
| Question | Header | Options |
|---|---|---|
Your site subdomain will be: `<suggestion>` (full URL: https://<suggestion>.<siteUrlDomain>). Would you like to use this subdomain or enter your own? | Subdomain | Use <suggestion> (Recommended), Enter a custom subdomain |
If custom: The user provides their own subdomain via "Other" free text input. Validate it is lowercase, alphanumeric with hyphens only, and 3-50 characters.
2.3 Get Website Record ID
Run pac pages list to get the website record ID:
pac pages listParse the output to find the website record that matches the site name. Extract the Website Record ID (GUID). If pac pages list returns no results or the command is not available, set websiteRecordId to $null — the API will create a new website record.
Output
- Site name determined (from config file or user input)
- Subdomain chosen (generated or custom)
- Website record ID resolved (GUID or null)
---
Phase 3: Confirm
Goal: Present all activation parameters to the user and get explicit approval before making the API call.
Actions
<!-- gate: activate-site:3.confirm | category=final | cancel-leaves=nothing -->
🚦 Gate (final · activate-site:3.confirm): Last-call before the activation API call. All activation parameters echoed back; Cancel exits cleanly before provisioning. Fires fresh on every skill invocation. Whenplan-almorchestrates multi-stage activation (Staging + Production), it invokesactivate-siteonce per stage — each invocation hits this gate fresh with its ownsiteName,subdomain, and target env. The Staging activation consent does NOT cover Production. Each stage's site URL is a separate go-live decision (different audiences, different timing).
Present all activation parameters to the user using AskUserQuestion:
| Question | Header | Options |
|---|---|---|
Ready to activate your Power Pages site with these settings:\n\n- Site name: <siteName>\n- Subdomain: <subdomain>.powerappsportals.com\n- Environment ID: <environmentId>\n\nProceed with activation? | Activate | Yes, activate the site (Recommended), No, cancel |
If "No": Stop the skill and inform the user they can re-run it later.
If "Yes": Proceed to Phase 4.
Output
- User has explicitly approved the activation parameters
---
Phase 4: Activate & Poll
Goal: POST to the Power Platform websites API to start provisioning, poll until completion, and report the result.
Actions
4.1 Run Activation Script
Run the shared activation script, passing all parameters gathered in Phases 1–2:
node "${PLUGIN_ROOT}/skills/activate-site/scripts/activate-site.js" --siteName "<siteName>" --subdomain "<subdomain>" --organizationId "<organizationId>" --environmentId "<environmentId>" --cloud "<cloud>" --websiteRecordId "<websiteRecordId>"Omit --websiteRecordId if it is null/empty.
The script acquires an Azure CLI token, POSTs to the websites API, extracts the Operation-Location header, and polls every 10 seconds for up to 5 minutes (refreshing the token periodically). It outputs a JSON result to stdout.
Note: This script may run for up to 5 minutes while polling. Use a Bash timeout of at least 360 seconds (6 minutes).
4.2 Handle Results
Evaluate the JSON output:
status value | statusCode / errorCode | Action |
|---|---|---|
| `Succeeded` | — | Provisioning complete. The result includes siteUrl. Proceed to Phase 5. |
| `Failed` | 400 + SubdomainConflict (or error message mentions subdomain) | Subdomain already taken. Loop back to Phase 2 action 2.2 for a new subdomain, then re-run the script. |
| `Failed` | 401 | Token expired. Ask the user to run az login --allow-no-subscriptions and retry. |
| `Failed` | 403 | Insufficient permissions. Inform user they need the "Power Pages site creator" or "System Administrator" role. |
| `Failed` | 409 | Website already exists. Inform user and suggest using /deploy-site instead. |
| `Failed` | 429 or 5xx | Throttling or server error. Wait 5 seconds and re-run the script once. |
| `Failed` | other | Present the error to the user and help troubleshoot. |
| `Running` | — | Provisioning still in progress after 5 minutes. Inform the user it may take up to 15 minutes and suggest checking the Power Platform admin center. |
error field | — | Prerequisite failure (missing args, no token). Present the error and help troubleshoot. |
Output
- Provisioning status resolved (Succeeded with
siteUrl, Failed with error details, or Running with timeout advisory)
---
Phase 5: Present Summary
Goal: Show the user the final site URL and suggest next steps.
Actions
5.1 Show Results
Present the activation summary using the siteUrl from the script output:
Power Pages site activated successfully!
Site Name: <siteName>
Site URL: <siteUrl>
Environment: <environmentName> (<environmentId>)
Status: ProvisionedThe script already resolves the correct cloud-specific site URL domain, so use the siteUrl value directly.
5.1b Write docs/alm/last-activate.json Marker
For consumers like the rendered ALM plan (Manual path's per-target Activate steps), write a structured marker that captures the post-activation state. Ensure the docs/alm/ directory exists first:
node -e "require('fs').mkdirSync('docs/alm',{recursive:true})"
# Determine the stage label this activation was for. The upstream ALM context
# (e.g. the user running this after import-solution) supplies it as "Staging"/
# "Production"; standalone invocations may leave it null — refreshActivateSite
# falls back to env-URL matching against planData.stages[].envUrl.
node -e "require('fs').writeFileSync('docs/alm/last-activate.json', JSON.stringify({
stageName: <stageNameOrNull>,
siteName: '<siteName>',
siteUrl: '<siteUrl>',
websiteRecordId: '<websiteRecordId>',
environmentUrl: '<envUrl>',
environmentId: '<environmentId>',
activatedAt: new Date().toISOString(),
status: 'Activated',
cloud: '<cloud>',
}, null, 2))"When the activation was an already-activated detection (Phase 1.4), still write the marker — the rendered plan should reflect "this stage is live at https://..." regardless of whether activation was performed this run or pre-existing. Set status: 'AlreadyActivated' and use the existing siteUrl from Phase 1.4's check.
5.2 Record Skill Usage
Reference: ${PLUGIN_ROOT}/references/skill-tracking-reference.mdFollow the skill tracking instructions in the reference to record this skill's usage. Use --skillName "ActivateSite".
5.2b Refresh the ALM plan (if one exists)
node "${PLUGIN_ROOT}/scripts/lib/refresh-alm-plan-data.js" \
--projectRoot "." \
--phase activate-site \
--stageName "{stageNameOrEmpty}" \
--render{stageNameOrEmpty} is the Manual-path target stage label (e.g. Staging, Production) the agent was activating — stated by the user, or inferred from the target env. Pass an empty string when unknown; refreshActivateSite falls back to URL-matching docs/alm/last-activate.json's environmentUrl against planData.stages[].envUrl, so standalone invocations still get captured.
The helper reads docs/alm/last-activate.json, writes a per-target entry into planData.activations[stageName] (siteUrl, status, activatedAt), and re-renders docs/alm-plan.html so the matching Activate site in {stageName} checklist step shows an ACTIVATED badge with the live site URL inline. When docs/.alm-plan-data.json is absent (standalone, not part of an ALM plan), the helper returns ok:false as a soft no-op.
Point the user at the next step (user-driven sequencing). The helper's stdout JSON includes nextStep: { name, skill: string | null } | null. When non-null, branch on skill: when skill is non-null, tell the user "Plan updated. Next in your plan: {nextStep.name} → run `{nextStep.skill}` when you're ready."; when skill is null (an internal step such as Finalize, no user command), name the step only — "Plan updated. Next in your plan: {nextStep.name}." — and never print run null. When null or the helper returned ok:false, say nothing about a next step. Never auto-invoke the next skill — the user drives execution.
5.3 Suggest Next Steps
After the summary, suggest:
- Test the site:
/test-site— Verify the site loads correctly and API calls are working - Set up ALM:
/power-pages:plan-alm— Set up ALM to promote this site to staging or production (creates a solution, configures a deployment pipeline or export/import strategy) - Set up the data model:
/setup-datamodel - Add sample data:
/add-sample-data - View the site in the browser at the provisioned URL (note: it may take a few minutes for DNS to propagate)
Output
- Activation summary presented with site URL
- Next steps suggested to the user
---
Important Notes
Progress Tracking
Use TaskCreate at the start to track progress through each phase:
| Task | Description |
|---|---|
| Phase 1 | Verify Prerequisites — PAC CLI auth, Cloud detection, Azure CLI login, activation status check |
| Phase 2 | Gather Parameters — site name, subdomain, website record ID |
| Phase 3 | Confirm — user approval of activation parameters |
| Phase 4 | Activate & Poll — run activation script, handle result |
| Phase 5 | Present Summary — show site URL and next steps |
Mark each task complete with TaskUpdate as you finish each phase.
Key Decision Points
- Phase 1.2: If not authenticated, must authenticate before proceeding — cannot skip.
- Phase 1.4: If site is already activated, stop early — do NOT proceed to Phase 2.
- Phase 2.2: User may accept the generated subdomain or provide a custom one — validate custom input.
- Phase 3: User must explicitly approve activation. If declined, stop the skill entirely.
- Phase 4.2: On 400 (subdomain taken), loop back to Phase 2 action 2.2 and re-run the script — do not abort.
- Phase 4.2: On 409 (site already exists), redirect user to
/deploy-siteinstead. - Phase 4.2: On timeout (still Running after 5 minutes), do not treat as failure — advise user to check admin center.
Begin with Phase 1: Verify Prerequisites
#!/usr/bin/env node
// Activates (provisions) a Power Pages site via the Power Platform websites API.
// Handles the POST request, polls the Operation-Location header for status, and
// refreshes the Azure CLI token during long polling cycles.
//
// Usage:
// node activate-site.js --siteName "My Site" --subdomain "my-site" --organizationId "<guid>" --environmentId "<guid>" --cloud "Public" [--websiteRecordId "<guid>"]
//
// Output (JSON to stdout):
// { "status": "Succeeded", "siteUrl": "https://...", "siteName": "...", "subdomain": "..." }
// { "status": "Failed", "statusCode": 400, "errorCode": "SubdomainConflict", "error": "..." }
// { "status": "Running", "message": "Provisioning still in progress after 5 minutes" }
// { "error": "..." } — when prerequisites are missing or unexpected errors occur
const { getAuthToken, makeRequest, CLOUD_TO_API, CLOUD_TO_SITE_DOMAIN } = require('../../../scripts/lib/validation-helpers');
// --- Helpers ---
function output(obj) {
process.stdout.write(JSON.stringify(obj));
process.exit(0);
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function parseArgs(argv) {
const args = {};
const keys = ['--siteName', '--subdomain', '--organizationId', '--environmentId', '--cloud', '--websiteRecordId'];
for (const key of keys) {
const idx = argv.indexOf(key);
if (idx !== -1 && idx + 1 < argv.length) {
args[key.replace('--', '')] = argv[idx + 1];
}
}
return args;
}
// --- Parse arguments ---
const args = parseArgs(process.argv.slice(2));
if (!args.siteName) output({ error: 'Missing required argument: --siteName' });
if (!args.subdomain) output({ error: 'Missing required argument: --subdomain' });
if (!args.organizationId) output({ error: 'Missing required argument: --organizationId' });
if (!args.environmentId) output({ error: 'Missing required argument: --environmentId' });
const cloud = args.cloud || 'Public';
const ppApiBaseUrl = CLOUD_TO_API[cloud] || CLOUD_TO_API['Public'];
const siteDomain = CLOUD_TO_SITE_DOMAIN[cloud] || CLOUD_TO_SITE_DOMAIN['Public'];
const siteUrl = `https://${args.subdomain}.${siteDomain}`;
// --- Acquire token ---
let token = getAuthToken(ppApiBaseUrl);
if (!token) {
output({ error: 'Azure CLI token not available. Run "az login --allow-no-subscriptions" first.' });
}
// --- Build request body ---
const body = {
name: args.siteName,
subdomain: args.subdomain,
templateName: 'DefaultPortalTemplate',
dataverseOrganizationId: args.organizationId,
selectedBaseLanguage: 1033,
};
if (args.websiteRecordId) {
body.websiteRecordId = args.websiteRecordId;
}
// --- POST to websites API and poll for completion ---
const apiUrl = `${ppApiBaseUrl}/powerpages/environments/${args.environmentId}/websites?api-version=2022-03-01-preview`;
(async () => {
const postResult = await makeRequest({
method: 'POST',
url: apiUrl,
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(body),
includeHeaders: true,
timeout: 30000,
});
if (postResult.error) {
output({ error: `POST request failed: ${postResult.error}` });
}
const statusCode = postResult.statusCode;
// --- Handle non-202 responses ---
if (statusCode !== 202) {
let errorCode = null;
let errorMessage = postResult.body || 'Unknown error';
try {
const parsed = JSON.parse(postResult.body);
errorCode = parsed.error?.code || parsed.code || null;
errorMessage = parsed.error?.message || parsed.message || errorMessage;
} catch {
// Body is not JSON, use raw
}
output({
status: 'Failed',
statusCode,
errorCode,
error: errorMessage,
});
}
// --- 202 Accepted: extract Operation-Location and poll ---
const operationLocation = postResult.headers?.['operation-location'] || null;
if (!operationLocation) {
output({ error: 'POST returned 202 but no Operation-Location header was found' });
}
// Ensure the poll URL includes api-version (some endpoints omit it from Operation-Location)
let pollUrl = operationLocation;
if (pollUrl && !pollUrl.includes('api-version')) {
pollUrl += (pollUrl.includes('?') ? '&' : '?') + 'api-version=2022-03-01-preview';
}
const maxAttempts = 30; // 30 × 10s = 5 minutes
const pollIntervalMs = 10000;
const tokenRefreshEvery = 6; // refresh every ~60s
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
await sleep(pollIntervalMs);
// Refresh token periodically
if (attempt % tokenRefreshEvery === 0) {
const refreshed = getAuthToken(ppApiBaseUrl);
if (refreshed) token = refreshed;
}
let pollStatus;
try {
const pollResult = await makeRequest({
url: pollUrl,
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/json',
},
timeout: 15000,
});
if (pollResult.error || !pollResult.body) continue;
pollStatus = JSON.parse(pollResult.body);
} catch {
// Single poll failure — continue to next attempt
continue;
}
const status = pollStatus.operationStatus || pollStatus.status || pollStatus.Status;
if (status === 'OperationComplete') {
output({
status: 'Succeeded',
siteUrl: pollStatus.websiteUrl || siteUrl,
siteName: args.siteName,
subdomain: args.subdomain,
});
}
if (status === 'OperationFailed') {
const errMsg = pollStatus.error?.message || pollStatus.Error?.message || JSON.stringify(pollStatus);
output({
status: 'Failed',
statusCode: 200,
error: `Provisioning failed: ${errMsg}`,
});
}
// "OperationInProgress" / "OperationNotStarted" — continue polling
}
// Timed out
output({
status: 'Running',
message: 'Provisioning still in progress after 5 minutes. Check the Power Platform admin center for status.',
siteUrl,
siteName: args.siteName,
subdomain: args.subdomain,
});
})();
#!/usr/bin/env node
// Generates a random subdomain suggestion for Power Pages site activation.
// Mirrors Power Pages Studio logic: Math.random().toString(36) for base-36 suffix.
// Outputs format: site-uc8f3 (5 base-36 chars: 0-9 + a-z)
const suffix = Math.random().toString(36).slice(2, 7);
process.stdout.write(`site-${suffix}`);
#!/usr/bin/env node
// Validates Power Pages site activation output.
// Runs as a Stop hook to verify the website was provisioned in the environment.
// Calls the shared check-activation-status.js script to query the Power Platform API
// instead of relying on an intermediate file.
const path = require('path');
const { execSync } = require('child_process');
const { approve, block, runValidation, findPath } = require('../../../scripts/lib/validation-helpers');
runValidation(async (cwd) => {
const configPath = findPath(cwd, 'powerpages.config.json');
if (!configPath) approve(); // Not a Power Pages project, skip
const projectRoot = path.dirname(configPath);
const checkScript = path.resolve(__dirname, '../../../scripts/check-activation-status.js');
let result;
try {
const output = execSync(`node "${checkScript}" --projectRoot "${projectRoot}"`, {
encoding: 'utf8',
timeout: 30000,
});
result = JSON.parse(output);
} catch {
approve(); // Auth/transient failure — don't block
}
if (result.activated === true) {
approve();
}
if (result.activated === false) {
block(
`Power Pages activation validation failed:\n- Site '${result.siteName || 'unknown'}' is not activated. The site may not have been provisioned successfully.`
);
}
// Error or unexpected shape — don't block
approve();
});
Related skills
FAQ
What does activate-site do?
>-
When should I use activate-site?
Invoke when >-.
Is activate-site safe to install?
Review the Security Audits panel on this page before installing in production.