Nothing else in this lab works if this module is wrong, and none of it is difficult. That combination is exactly why it is worth doing slowly.
You are going to make three decisions that are expensive to reverse later: where the components live, what a ticket is, and how an update knows which ticket it belongs to. Every module after this one configures something on top of those three answers.
Outcome. A dedicated solution containing a Support Ticket table, a Ticket Update table, and a lookup relationship joining them.
Time. 45–65 minutes, and the longest module in the lab. Each of the three checkpoints below is a safe place to stop and come back.
Starting point. A Power Platform environment with Dataverse, and a role that can create solutions and tables: Environment Maker, System Customizer, or System Administrator. Use a development or training environment, never production. New to the lab? Start at the overview.
Why the solution comes first
Before you build anything, decide where it lives.
A solution is a container for application components and their dependencies: tables, columns, relationships, forms, views, business rules, process flows, the app itself. It is the unit that moves between environments. Build outside one and your work still functions, but it has no transport boundary, and assembling it later means hunting for pieces you no longer remember creating.
The default solution is genuinely useful for discovery and administration. It is a poor day-to-day development boundary, because every unrelated customization in the environment accumulates there too.
- Open the Power Apps maker portal and confirm the correct environment is selected. Check this every time you return to the lab; it is the single most common cause of “my changes vanished.”
- Select Solutions, then New solution.
- Enter:
| Property | Value |
|---|---|
| Display name | Support Ticket Learning Lab |
| Name | SupportTicketLearningLab |
| Publisher | An existing training publisher |
| Version | 1.0.0.0 |
- Select Create, then open the new solution.
The publisher is not a formality. It owns the customization prefix that
gets stamped onto every schema name you create in this lab, so a column
you name Ticket Age in Days becomes something like new_TicketAgeinDays
underneath. That prefix becomes part of the contract any integration
depends on, which is why production teams choose a publisher deliberately
rather than accepting whatever is default.
Checkpoint. The solution is open, its publisher and version are visible, and it is empty. Empty is correct; everything from here is created inside it. Safe to stop here.
The Support Ticket table
A table gives a business concept a governed schema: stable identity, security, relationships, views, reusable metadata. Use typed columns to constrain meaning, and free text only where narrative flexibility is the actual requirement.
- From inside the solution, select New, then Table, then Table again.
- Enter:
| Property | Value |
|---|---|
| Display name | Support Ticket |
| Plural name | Support Tickets |
| Primary column | Ticket Title |
| Ownership | User or team |
- Enable Activities where the option appears.
- Save.
Enabling Activities is doing one specific thing to your future: it adds the platform’s activity machinery to this table, which is why a Timeline component becomes available when you build the form in module 2. That component is not the one you want there, and module 2 explains why at the point where it would mislead you. It is enabled here because a realistic support-ticket table would have it, and because meeting the trap is more instructive than avoiding it.
The primary column identifies the row in lookups and throughout model-driven experiences. It is the ticket’s short title here, not its ticket number, because a human-readable name makes every lookup and navigation breadcrumb legible.
Now the columns. Open the table’s Columns area and create each of these:
| Display name | Data type | Notes |
|---|---|---|
Ticket Number | Autonumber | Format TKT-{SEQNUM:5}, producing TKT-00001 |
Description | Multiple lines of text | Optional |
Ticket Status | Choice (local) | Values: New, In Progress, Resolved. Default New |
Priority | Choice (local) | Values: Low, Normal, High, Critical. Default Normal |
Resolution Notes | Multiple lines of text | Leave optional |
Resolved On | Date and time | Optional |
Two of those choices deserve their reasoning stated.
Resolution Notes stays optional at the schema level on purpose. The requirement is that it becomes mandatory when a ticket resolves, not always. Making it permanently required here would block every valid New and In Progress ticket. Module 3 makes it conditional, which is the correct place for a conditional rule.
Ticket Status is your own choice column, not the platform’s. Every
Dataverse table already ships with system state columns, usually surfaced
as Status and Status Reason and stored as statecode and statuscode.
They govern whether a row is active or inactive, and they carry platform
behaviour you do not want to redesign in a first lab. Your Ticket Status
is a separate business column that happens to describe a similar idea.
Keeping them separate is deliberate; conflating them is a genuine source
of confusion in real projects.
Checkpoint. The table lists Ticket Title, Ticket Number, Description, Ticket Status, Priority, Resolution Notes, Resolved On, and the system-generated Created On. Ticket Status and Priority show their choice values. Resolution Notes is not required. Safe to stop here.
The Ticket Update table and the lookup
One ticket accumulates many updates. Each update belongs to exactly one ticket. That sentence determines the entire design.
Create the child table:
- Solution, New, Table, Table.
- Enter:
| Property | Value |
|---|---|
| Display name | Ticket Update |
| Plural name | Ticket Updates |
| Primary column | Update Title |
| Ownership | User or team |
- Save, then add one column:
Update Details, Multiple lines of text, Business required.
Now the relationship. This is the step everything else depends on:
- In the Ticket Update table, select Columns, then New column.
- Enter:
| Property | Value |
|---|---|
| Display name | Support Ticket |
| Data type | Lookup |
| Related table | Support Ticket |
| Required | Business required |
- Save.
Read where that lookup lives, because the placement is the design. The lookup sits on Ticket Update, the child. That is what allows many update rows to point at one ticket:
Support Ticket 1 ─────────< N Ticket Updates
Put the lookup on the parent instead and a ticket could reference exactly one update, which reverses the cardinality you actually need. Making it Business required is what stops an update from existing without a parent, which would be a row nobody can find and no ticket-centric report can see.
Checkpoint. Ticket Update exists with a required Update Details and a business-required Support Ticket lookup. Dataverse now exposes a one-to-many relationship from Support Ticket to Ticket Update, and the next module uses it twice: once to show updates on a form, and once to count them.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
| New solution is unavailable | Confirm the environment has Dataverse and your role includes customization privileges. |
| The expected publisher is missing | Use an approved existing publisher, or ask an environment administrator to create one. |
| Autonumber options do not appear | Set the data type to Autonumber first; the format field appears afterwards. |
| A choice value does not show up | Save the choice definition and the column, then refresh the designer. |
| The related table is not listed for the lookup | Save and publish the Support Ticket table, then reopen the lookup designer. |
| New columns do not appear on any form | Expected. Columns and form placement are separate operations; forms come in module 2. |
Knowledge Check
- What problem does a solution solve that simply creating tables does not?
- Why are Ticket Status and Priority choice columns rather than text?
- Why is Ticket Number not the primary column?
- On which table does the Support Ticket lookup physically live, and what would break if it lived on the other one?
- What is the difference between making Resolution Notes permanently required and conditionally required?
Architecture Review
Why a separate child table rather than one long multiline field on the ticket: separate rows carry their own timestamps, ownership, security, and reporting grain. You can count them, filter them, and later add an update type or a customer-visible flag. Text in a single field can do none of that.
Production note. Real models also decide cascade behaviour on the relationship, delete restrictions, auditing, alternate keys, field-level security, retention, and whether choices should be global for reuse across applications. Decide those before integrations start depending on schema names, because schema names are the contract.
Stretch. Add an Assigned Support Team lookup and a global Support Tier choice. Explain why each is a relationship or a shared choice rather than free text.
Review scenario. A developer proposes storing Priority as text because integrations might send any value. What do you say, and where does the normalization belong instead?
Stopping point
Safe to stop here. You have a transport boundary, two tables, and the relationship that joins them. Module 2 turns all of it into something a person can actually use.