
Activate Site
Provision and activate a Power Pages website in a Power Platform environment via the websites API with polling until Succeeded or Failed.
Overview
activate-site is an agent skill for the Build phase that provisions a Power Pages site through the Power Platform websites API with Operation-Location polling and JSON status output.
Install
npx skills add https://github.com/microsoft/power-platform-skills --skill activate-siteWhat is this skill?
- Node activate-site.js POSTs to Power Platform websites API with siteName, subdomain, organizationId, environmentId, and
- Polls Operation-Location until provisioning Succeeded, Failed, or long-running timeout
- Refreshes Azure CLI token during extended polling cycles
- Structured JSON stdout for Succeeded (siteUrl), Failed (errorCode such as SubdomainConflict), Running, or error
- Optional websiteRecordId parameter for idempotent or update-style flows
- Long-poll message when provisioning still running after 5 minutes
Adoption & trust: 78 installs on skills.sh; 349 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You need a live Power Pages site record and URL in an environment but manual portal clicks do not fit an agent-driven or repeatable pipeline.
Who is it for?
Builders scripting Power Pages stand-up who already have organization and environment IDs and Azure-authenticated API access.
Skip if: Publishing front-end code bundles (use deploy-site), defining Dataverse tables (use setup-datamodel), or non-Power-Platform hosting.
When should I use this skill?
User needs to create or activate a Power Pages website in an environment before deploy or datamodel work.
What do I get? / Deliverables
Provisioning completes with a JSON result containing siteUrl on success or actionable error codes on failure, ready for datamodel setup or deploy-site.
- JSON activation result with siteUrl or errorCode
- Terminal provisioning status for agent branching
Recommended Skills
Journey fit
Site activation wires the Power Pages resource in the tenant before front-end code and Dataverse-backed features can attach. Integrations is the right shelf because activation calls Power Platform websites APIs and Azure-authenticated HTTP, not app UI code.
How it compares
This is API provisioning and polling, not a PAC CLI upload—pair it with deploy-site for the full go-live path.
Common Questions / FAQ
Who is activate-site for?
activate-site is for developers and solo builders automating Power Pages creation in a known Power Platform environment via the websites API.
When should I use activate-site?
Use activate-site in Build when you must create or activate a site with a subdomain before connecting Dataverse or deploying a code site; typically before deploy-site in the same skill family.
Is activate-site safe to install?
It performs authenticated POSTs against your tenant—review the Security Audits panel on this page and restrict who can pass organization and environment IDs.
Workflow Chain
Then invoke: deploy site
SKILL.md
READMESKILL.md - Activate Site
#!/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: