| name | dataverse-scaffold-tables |
| description | Provision Dataverse tables, columns, choices (option sets) and 1:N relationships via the Web API, reusing the Power Apps Code Apps sign-in. Use when asked to "scaffold Dataverse tables", "create a Dataverse table/column/choice/relationship", or build a Dataverse schema from a spec. |
| metadata | {"author":"chuckytesla","version":"2.0.0","argument-hint":"<schema.json|schema.yaml> [--dry-run] [--no-solution] (signs in first via npx power-apps login [--account <email>])"} |
Scaffold Dataverse tables
Generic engine: you feed it a schema file (JSON or YAML) and it creates that schema in Dataverse.
It creates tables, columns, local choices and 1:N relationships by POSTing to the Web API
EntityDefinitions / RelationshipDefinitions endpoints. The skill contains no schema of its own —
the model is always the file you pass. Idempotent — existing tables and relationships are detected
and skipped, so re-running is safe.
Authentication (this skill signs you in)
This is the first skill in the flow that needs Dataverse auth, so it owns the sign-in. It runs
npx power-apps login — the same sign-in Power Apps Code Apps use
(sign-in docs).
That stores MSAL tokens in a DPAPI-encrypted cache; the shared helper
../lib/dataverse-client.mjs then rebuilds the same MSAL public client
and acquires a Dataverse token silently for <orgUrl>/.default for every Web API call below — no
pac, no Azure CLI, no new app registration. The account needs System Customizer / System
Administrator rights to write metadata.
The earlier scaffold-code-app skill deliberately does not sign in; the login lives here.
Prerequisites
DATAVERSE_URL set in .env (or the environment), e.g.
DATAVERSE_URL=https://orgXXXXXXXX.crm.dynamics.com — the environment's Environment URL.
Region (prod by default) and environmentId are read from power.config.json (written by
scaffold-code-app).
Step 1 — Sign in (opens your browser — warn first)
⚠️ Tell the user before running this: the command opens their browser to sign in, and they
need to be ready to complete the login there. The terminal prints
Opening your browser to sign in. Complete the sign-in there to continue... and waits until login
finishes.
npx power-apps login [--account <login email>]
Use the account that owns the environment (pass --account <email> if known, otherwise the CLI
prompts). Wait for the command to complete — the user finishes the browser flow — before scaffolding.
If you're already signed in from a recent session this is a no-op; re-run it if a later call reports an
interaction/consent error.
Step 2 — Scaffold the schema (how to run)
node .agents/skills/dataverse-scaffold-tables/scaffold.mjs path/to/schema.yaml --dry-run
node .agents/skills/dataverse-scaffold-tables/scaffold.mjs path/to/schema.yaml
node .agents/skills/dataverse-scaffold-tables/scaffold.mjs path/to/schema.yaml --no-solution
Always do a --dry-run first, then run live. The schema file is .json or .yaml — see the
contract below.
Model contract
The input is an object with tables, an optional solution, and optional relationships. YAML
equivalent shown; JSON works identically.
lcid: 1033
solution:
uniqueName: myorg_MySolution
displayName: My Solution
version: 1.0.0.0
publisher: { uniqueName: myorgpub, displayName: My Publisher, prefix: myorg, optionValuePrefix: 10000 }
tables:
- schemaName: myorg_Widget
displayName: Widget
displayCollectionName: Widgets
ownership: OrganizationOwned
primaryName: { schemaName: myorg_Name, displayName: Name, maxLength: 200, required: ApplicationRequired }
columns:
- { schemaName: myorg_Code, displayName: Code, type: String, maxLength: 100, required: ApplicationRequired }
- schemaName: myorg_Status
displayName: Status
type: Picklist
required: ApplicationRequired
optionSet:
name: myorg_status
options:
- { value: 1, label: Draft }
- { value: 2, label: Active }
- { schemaName: myorg_StartTime, displayName: Start time, type: DateTime, format: DateAndTime, behavior: UserLocal }
- { schemaName: myorg_Notes, displayName: Notes, type: Memo, maxLength: 2000 }
relationships:
- schemaName: myorg_widget_myorg_part_WidgetId
type: OneToMany
referenced: myorg_widget
referencing: myorg_part
lookup: { schemaName: myorg_WidgetId, displayName: Widget, required: ApplicationRequired }
cascade: { delete: Cascade, assign: NoCascade, reparent: NoCascade }
Column type values: String, Memo, DateTime, Picklist, Integer, Boolean.
required: None | ApplicationRequired | Recommended. Choices are local option sets created
inline on the picklist column, with explicit integer values (pinned, not auto-assigned). Lookups
are created by relationships, not as columns. Every schemaName prefix must match the solution
publisher's prefix (Dataverse enforces this when writing into a solution).
Authoring a schema file from a Markdown spec
If your source of truth is a narrative spec (e.g. a specs/*.md with table/column/choice definitions),
translate it once into a schema file in the contract above (schema names, types, max lengths, required
levels, option values, cascade behavior) and feed that file to the skill. Then --dry-run and review
before going live.
Notes & limits
- Metadata writes via the Web API are immediate (no separate publish step).
- Re-running skips anything that already exists; it does not alter or add columns to an existing table.
- First Dataverse call for a freshly-signed-in account may require a one-time consent — re-run
npx power-apps login if you see an interaction-required error.
- After scaffolding, add the tables as Code App data sources (the
dataverse-add-data-source skill —
npx power-apps add-data-source -a dataverse -t <logical-name>) so the SDK generates the typed
services your app codes against.