
Add Sample Data
- 124 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 add-sample-data 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
- Populate Dataverse tables with sample records via OData API so users can test and demo their Power Pages sites.
- **Respect insertion order**: Always insert parent/referenced tables before child/referencing tables so lookup IDs are av
- **Use TaskCreate/TaskUpdate**: Track all progress throughout all phases -- create the todo list upfront with all phases
- **Fail gracefully**: On insertion failure, log the error and continue with remaining records -- never attempt automated
Add Sample Data by the numbers
- 124 all-time installs (skills.sh)
- Ranked #939 of 2,184 Testing & QA skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
add-sample-data capabilities & compatibility
- Capabilities
- > **plugin check**: run `node "${plugin_root}/sc · populate dataverse tables with sample records vi · **respect insertion order**: always insert paren · **use taskcreate/taskupdate**: track all progres
- Use cases
- documentation
What add-sample-data says it does
>-
npx skills add https://github.com/microsoft/power-platform-skills --skill add-sample-dataAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 124 |
|---|---|
| repo stars | ★ 572 |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 28, 2026 |
| Repository | microsoft/power-platform-skills ↗ |
How do I apply add-sample-data using the workflow in its SKILL.md?
>-
Who is it for?
Developers following the add-sample-data skill for the tasks it documents.
Skip if: Tasks outside the add-sample-data scope described in SKILL.md.
When should I use this skill?
User mentions add-sample-data or related triggers from the skill description.
What you get
Working add-sample-data 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.Add Sample Data
Populate Dataverse tables with sample records via OData API so users can test and demo their Power Pages sites.
Core Principles
- Respect insertion order: Always insert parent/referenced tables before child/referencing tables so lookup IDs are available when needed.
- Use TaskCreate/TaskUpdate: Track all progress throughout all phases -- create the todo list upfront with all phases before starting any work.
- Fail gracefully: On insertion failure, log the error and continue with remaining records -- never attempt automated rollback.
Initial request: $ARGUMENTS
---
Phase 1: Verify Prerequisites
Goal: Confirm PAC CLI auth, acquire an Azure CLI token, and verify API access
Actions:
1. Create todo list with all 6 phases (see Progress Tracking table) 2. Follow the prerequisite steps in ${PLUGIN_ROOT}/references/dataverse-prerequisites.md to verify PAC CLI auth, acquire an Azure CLI token, and confirm API access. Note the environment URL as <envUrl> for subsequent script calls.
Output: Authenticated session with valid token and confirmed API access
---
Phase 2: Discover Tables
Goal: Find the custom tables available in the user's Dataverse environment
Actions:
Path A: Read .datamodel-manifest.json (Preferred)
Check if .datamodel-manifest.json exists in the project root (written by the setup-datamodel skill). If it exists, read it -- it already contains table logical names, display names, and column info.
See ${PLUGIN_ROOT}/references/datamodel-manifest-schema.md for the full manifest schema.
Path B: Query OData API (Fallback)
If no manifest exists, discover custom tables via OData:
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET "EntityDefinitions?\$select=LogicalName,DisplayName,EntitySetName&\$filter=IsCustomEntity eq true"For each discovered table, fetch its custom columns:
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET "EntityDefinitions(LogicalName='<table>')/Attributes?\$select=LogicalName,DisplayName,AttributeType,RequiredLevel&\$filter=IsCustomAttribute eq true"2.1 Present Available Tables
Show the user the list of discovered tables with their columns so they can choose which to populate.
Output: List of discovered tables with their columns presented to the user
---
Phase 3: Select Tables & Configure
Goal: Gather user preferences on which tables to populate and how many records to create
Actions:
3.1 Select Tables
<!-- gate: add-sample-data:3.1.tables | category=plan | cancel-leaves=nothing -->
🚦 Gate (plan · add-sample-data:3.1.tables): Multi-select over discovered tables — decides which tables get populated with sample data.
>
Trigger: Phase 2 discovery completed.
Why we ask: Auto-selecting all tables can pollute production-shape tables (e.g. contact) with throwaway records.Cancel leaves: Nothing — no record inserts yet.
Use AskUserQuestion to ask which tables they want to populate (use multiSelect: true). List all discovered tables as options.
3.2 Select Record Count
<!-- gate: add-sample-data:3.2.count | category=plan | cancel-leaves=nothing -->
🚦 Gate (plan · add-sample-data:3.2.count): Pick records-per-table (5 / 10 / 25 / Custom). Custom branch is a follow-up free-text prompt under the same gate.
>
Trigger: Tables selected in 3.1.
Why we ask: Inserting hundreds of records can blow past Dataverse storage / API throttling budgets.
Cancel leaves: Nothing — no record inserts yet.
Use AskUserQuestion to ask how many sample records per table:
| Option | Description |
|---|---|
| 5 records | Quick test -- just enough to verify the setup |
| 10 records | Light demo data for basic testing |
| 25 records | Fuller dataset for realistic demos |
| Custom | Let the user specify a number |
3.3 Determine Insertion Order
Analyze relationships between selected tables. Parent/referenced tables must be inserted first so their IDs are available for child/referencing table lookups.
Build the insertion order:
1. Tables with no lookup dependencies (parent tables) -- insert first 2. Tables that reference already-inserted tables -- insert next 3. Continue until all tables are ordered
Output: Confirmed table selection, record count, and insertion order
---
Phase 4: Generate & Review Sample Data
Goal: Generate contextually appropriate sample records and get user approval before inserting
Actions:
4.1 Generate Contextual Sample Data
For each selected table, generate sample records with contextually appropriate values based on column names and types:
- String columns: Generate realistic values matching the column name (e.g., "Email" ->
jane.doe@example.com, "Phone" ->(555) 123-4567, "Name" -> realistic names) - Memo columns: Generate short descriptive text relevant to the column name
- Integer/Decimal/Currency columns: Generate reasonable numeric values
- DateTime columns: Generate dates within a sensible range (past year to next month)
- Boolean columns: Mix of
trueandfalsevalues - Picklist/Choice columns: Query valid options first (see references/odata-record-patterns.md), then use actual option values
- Lookup columns: Reference records from parent tables that will be/were already inserted
4.2 Present Sample Data Preview
For each table, show a markdown table previewing the sample records directly in the conversation:
### Project (cr123_project) -- 5 records
| Name | Description | Status | Start Date |
|------|-------------|--------|------------|
| Website Redesign | Modernize the corporate website | 100000000 (Active) | 2025-03-15 |
| Mobile App | Build iOS and Android app | 100000001 (Planning) | 2025-04-01 |
| ... | ... | ... | ... |Show relationship handling: which lookup fields reference which parent table records.
Output: Sample data plan ready for insertion. Proceed directly to Phase 5.
---
Phase 5: Insert Sample Data
Goal: Execute OData POST calls to create all approved sample records with correct relationship handling
Actions:
Refer to references/odata-record-patterns.md for full patterns.
5.1 Get Entity Set Names
For each table, get the entity set name (needed for the API URL):
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET "EntityDefinitions(LogicalName='<table>')?\$select=EntitySetName"5.2 Get Picklist Options
For any picklist/choice columns, query valid option values before insertion:
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET "EntityDefinitions(LogicalName='<table>')/Attributes(LogicalName='<column>')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?\$expand=OptionSet"Use the actual Value integers from the option set in your sample data.
5.3 Insert Parent Tables First
Insert records into parent/referenced tables first to capture their IDs:
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> POST "<EntitySetName>" --body '{"cr123_name":"Sample Record","cr123_description":"A sample record for testing"}' --include-headersThe --include-headers flag includes the OData-EntityId response header, which contains the created record ID. Parse the GUID from the response to use in child table lookups.
Store parent record IDs for use in child table lookups.
5.4 Insert Child Tables with Lookups
For child/referencing tables, use @odata.bind syntax to set lookup fields:
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> POST "<ChildEntitySetName>" --body '{"cr123_name":"Child Record","cr123_ParentId@odata.bind":"/<ParentEntitySetName>(<parent_guid>)"}' --include-headers5.5 Track Progress
Track each insertion attempt:
- Record table name, record number, success/failure
- On failure, log the error message but continue with remaining records
- Do NOT attempt automated rollback on failure
5.6 Refresh Token Periodically
The dataverse-request.js script handles 401 token refresh internally. For long-running operations (many records), periodically re-run verify-dataverse-access.js to confirm the session is still valid:
node "${PLUGIN_ROOT}/scripts/verify-dataverse-access.js" <envUrl>Output: All approved records inserted with parent-child relationships established
---
Phase 6: Verify & Summarize
Goal: Confirm record counts and present a final summary to the user
Actions:
6.1 Verify Record Counts
For each table that was populated, query the record count:
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET "<EntitySetName>?\$count=true&\$top=0"The @odata.count field in the response gives the total record count.
6.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 "AddSampleData".
6.3 Present Summary
Present a summary table:
| Table | Records Requested | Records Created | Failures |
|---|---|---|---|
cr123_project (Project) | 10 | 10 | 0 |
cr123_task (Task) | 10 | 9 | 1 |
Include:
- Total records created across all tables
- Any failures with error details
- Lookup relationships that were established
6.4 Suggest Next Steps
After the summary, suggest:
- Review the data in the Power Pages maker portal or model-driven app
- If the site is not yet built:
/create-site - If the site is ready to deploy:
/deploy-site
Output: Verified record counts and summary presented to the user
---
Important Notes
Throughout All Phases
- Use TaskCreate/TaskUpdate to track progress at every phase
- Ask for user confirmation at key decision points (see list below)
- Respect insertion order -- always insert parent tables before child tables
- Fail gracefully -- log errors and continue, never rollback automatically
- Refresh tokens every 20 records to avoid expiration
Key Decision Points (Wait for User)
1. After Phase 2: Confirm which tables to populate 2. After Phase 3: Confirm record count and insertion order 3. After Phase 6: Review summary and decide next steps
Progress Tracking
Before starting Phase 1, create a task list with all phases using TaskCreate:
| Task subject | activeForm | Description |
|---|---|---|
| Verify prerequisites | Verifying prerequisites | Confirm PAC CLI auth, acquire Azure CLI token, verify API access |
| Discover tables | Discovering tables | Read .datamodel-manifest.json or query OData API for custom tables |
| Select tables and configure | Configuring tables | User picks tables, record count, and determine insertion order |
| Generate and review sample data | Generating sample data | Generate contextual sample records, present preview, get user approval |
| Insert sample data | Inserting records | Execute OData POST calls with relationship handling and token refresh |
| Verify and summarize | Verifying results | Confirm record counts, present summary, suggest next steps |
Mark each task in_progress when starting it and completed when done via TaskUpdate. This gives the user visibility into progress and keeps the workflow deterministic.
---
Begin with Phase 1: Verify Prerequisites
OData API Patterns for Record Creation
Reference document for the add-sample-data skill. Contains patterns for inserting records, setting lookup bindings, handling different column types, and querying record counts via the Dataverse OData Web API (v9.2).
Authentication, error handling, and retry patterns are in the shared reference: ${PLUGIN_ROOT}/references/odata-common.md. Read that file first for headers, token refresh, HTTP status codes, and retry logic.---
Get Entity Set Name
Entity set names are required for all record operations. They differ from logical names (e.g., cr123_project → cr123_projects).
Endpoint: GET {envUrl}/api/data/v9.2/EntityDefinitions(LogicalName='<table>')?$select=EntitySetName
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET "EntityDefinitions(LogicalName='cr123_project')?\$select=EntitySetName"The response JSON data.EntitySetName contains the entity set name (e.g., "cr123_projects").
---
Insert a Record
Endpoint: POST {envUrl}/api/data/v9.2/<EntitySetName>
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> POST "cr123_projects" --body '{"cr123_name":"Website Redesign","cr123_description":"Modernize the corporate website with a fresh design","cr123_startdate":"2025-06-15T10:30:00Z","cr123_budget":15000.00,"cr123_isactive":true,"cr123_status":100000000}'Capturing the Created Record ID
The record ID is returned in the OData-EntityId response header. Use --include-headers to capture it:
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> POST "cr123_projects" --body '{"cr123_name":"Website Redesign"}' --include-headersThe response JSON includes a headers object with the OData-EntityId value. Parse the GUID from it to use in subsequent lookups.
---
Column Type Values
String (SingleLine.Text)
Set as a plain string:
{ "cr123_name": "Sample Value" }Memo (MultiLine.Text)
Set as a plain string (supports longer text):
{ "cr123_description": "This is a longer description that can span multiple lines and paragraphs." }Integer (WholeNumber)
Set as an integer:
{ "cr123_quantity": 42 }Decimal
Set as a decimal number:
{ "cr123_rating": 4.75 }Currency (Money)
Set as a numeric value:
{ "cr123_price": 99.99 }DateTime
Set as ISO 8601 format string:
{ "cr123_startdate": "2025-06-15T10:30:00Z" }For date-only fields:
{ "cr123_birthdate": "2025-06-15" }Boolean
Set as true or false:
{ "cr123_isactive": true }Choice / Picklist
Set as the integer option value (NOT the label text):
{ "cr123_status": 100000000 }Choice option values typically start at 100000000 and increment by 1.
Lookup (Relationship Binding)
Use the @odata.bind annotation to reference a related record:
{ "cr123_ProjectId@odata.bind": "/cr123_projects(00000000-0000-0000-0000-000000000001)" }The format is: "<lookup_logical_name>@odata.bind": "/<ReferencedEntitySetName>(<guid>)"
---
Getting Picklist Options
Before inserting records with picklist/choice columns, query the valid option values:
Endpoint: GET {envUrl}/api/data/v9.2/EntityDefinitions(LogicalName='<table>')/Attributes(LogicalName='<column>')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?$expand=OptionSet
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET "EntityDefinitions(LogicalName='cr123_project')/Attributes(LogicalName='cr123_status')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?\$expand=OptionSet"The response data.OptionSet.Options array contains objects with Value (e.g., 100000000) and Label.LocalizedLabels[0].Label (e.g., "Active").
Use these actual Value integers in your sample data — never guess option values.
---
Lookup Binding Examples
Single Lookup
A task referencing a project:
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> POST "cr123_tasks" --body '{"cr123_name":"Design mockups","cr123_duedate":"2025-07-01T00:00:00Z","cr123_ProjectId@odata.bind":"/cr123_projects(<projectGuid>)"}' --include-headersMultiple Lookups
A record referencing multiple parent tables:
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> POST "<EntitySetName>" --body '{"cr123_name":"Project Update Meeting","cr123_ProjectId@odata.bind":"/cr123_projects(<projectGuid>)","cr123_ContactId@odata.bind":"/contacts(<contactGuid>)"}' --include-headers---
Querying Record Count
Verify how many records exist in a table after insertion:
Endpoint: GET {envUrl}/api/data/v9.2/<EntitySetName>?$count=true&$top=0
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET "cr123_projects?\$count=true&\$top=0"The response data["@odata.count"] contains the total record count.
The $top=0 ensures no actual records are returned — only the count.
---
Batch Operations (Optional)
For inserting many records efficiently, use OData batch requests to send multiple operations in a single HTTP call.
Endpoint: POST {envUrl}/api/data/v9.2/$batch
Headers:
Authentication is handled automatically by dataverse-request.js. To send a batch request, use POST to the $batch endpoint. The script handles auth headers and token refresh internally:
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> POST "\$batch" --body '<batch body>'Body format:
--batch_<batchId>
Content-Type: multipart/mixed; boundary=changeset_<changesetId>
--changeset_<changesetId>
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1
POST /api/data/v9.2/<EntitySetName> HTTP/1.1
Content-Type: application/json
{"cr123_name": "Record 1"}
--changeset_<changesetId>
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2
POST /api/data/v9.2/<EntitySetName> HTTP/1.1
Content-Type: application/json
{"cr123_name": "Record 2"}
--changeset_<changesetId>--
--batch_<batchId>--Note: Batch requests share a single transaction per changeset — if one operation fails, all operations in that changeset are rolled back. Keep changesets small (5-10 operations) to limit blast radius of failures.
---
Error Handling
See ${PLUGIN_ROOT}/references/odata-common.md for HTTP status codes, error response format, Dataverse error codes, and retry patterns.
Related skills
FAQ
What does add-sample-data do?
>-
When should I use add-sample-data?
Invoke when >-.
Is add-sample-data safe to install?
Review the Security Audits panel on this page before installing in production.