Skip to main content
skill CX Codex

platform-custom-report-type-generate — Claude Code skill: Custom

Guides the agent through authoring and validating Salesforce Custom Report Type metadata: base object, joins, exposed fields. Handy when a .reportType-meta.xml deploy fails.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add forcedotcom/sf-skills --skill platform-custom-report-type-generate -a claude-code
Open on GitHub

What platform-custom-report-type-generate does

The skill gives the agent a rulebook for .reportType-meta.xml files. One CRT is one file, and the file basename becomes the report type's developer name. It spells out the required elements: <fullName>, <label>, <baseObject>, <deployed>, plus <join> and <sections> with their columns.

A separate block covers the critical rules. If <fullName> is present, it has to match the file name exactly — same characters, same casing, same underscores. Each join hinges on <outerJoin>: false keeps only records that have children, true keeps them all. A join chain holds at most four objects, and an inner join after an outer one is not allowed. Fields are referenced by API name, never by label.

The skill targets authoring, not reporting. It stays away from running, editing or filtering existing reports, and from folders, dashboards and list views. API version 51.0 or higher is required, otherwise some elements will fail validation at deploy time.

What it helps with

  • build a CRT across custom objects and their relationships
  • pick inner or outer join for a chain of objects
  • check that <fullName> matches the file name
  • debug a .reportType-meta.xml deployment error
  • curate the field set shown in report builder

Example prompts for the agent

  • Create a Custom Report Type for Account and Project__c, fields from both, outer join
  • Check my AccountsWithProjects.reportType-meta.xml — why does the deploy fail?
  • Add a Contact join via Contacts to this CRT, no inner join after an outer one

How to install platform-custom-report-type-generate

1Skills CLI (Vercel)

npx skills add forcedotcom/sf-skills --skill platform-custom-report-type-generate -a claude-code

2Manually: put the files in place

~/.claude/skills/platform-custom-report-type-generate/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/platform-custom-report-type-generate/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about platform-custom-report-type-generate

How do I install platform-custom-report-type-generate in Claude?

Run npx skills add forcedotcom/sf-skills --skill platform-custom-report-type-generate -a claude-code. The skill file lands at ~/.claude/skills/platform-custom-report-type-generate/SKILL.md.

What does platform-custom-report-type-generate do?

It helps the agent write and validate Salesforce Custom Report Type metadata: base object, joins, and field sections. It also walks through deploy errors for those files.

Does the skill work on existing reports?

No. It covers authoring and validation of metadata only. Running, editing or filtering existing reports, plus folders, dashboards and list views, sit outside its scope.

Source text

SKILL.md · Apache-2.0
---
name: platform-custom-report-type-generate
description: "Use to create, generate, or validate Salesforce Custom Report Type metadata. Trigger on custom report types, CRTs, cross-object reports, report builder data sources, exposing fields for reporting across related objects, inner vs outer joins in reports, report type categories, or deployment errors for .reportType-meta.xml files. Do NOT trigger for running, editing, or filtering existing reports; creating report folders, dashboards, or list views; or non-authoring reporting questions."
metadata:
  version: "1.0"
  domains: ["Platform"]
  minApiVersion: "51.0"
---

## Specification

# Salesforce Custom Report Type Metadata Knowledge

## Overview
Custom Report Types (CRTs) define the **data framework** for Salesforce reports. They specify a primary object, up to 3 related objects, the relationship (join) between them, and which fields are available in the report builder.

## Purpose
- Enable reporting across custom objects and custom relationships not covered by standard report types
- Curate a focused set of fields for report builders (including fields reached via lookup)
- Control inner/outer join behavior to include or exclude primary records without related records

## Configuration

**File extension:** `.reportType-meta.xml`. The file basename is the report type's developer name (e.g. `AccountsWithProjects.reportType-meta.xml`). Each CRT is a single file, not nested under an object folder.

### Key Elements

Top-level `<ReportType>` children:

| Element | Required | Notes |
|---------|----------|-------|
| `<fullName>` | Yes | API identifier; must match the file name. Letters, numbers, underscores; must begin with a letter; no spaces; no trailing underscore; no consecutive underscores |
| `<label>` | Yes | Human-friendly name shown in the report type picker |
| `<description>` | Recommended | State the business "why" — who uses this and what they learn |
| `<baseObject>` | Yes | API name of the primary object (e.g. `Account`, `Project__c`). Cannot be changed after initial creation. All objects, including custom and external, are supported (external objects from API 38.0+) |
| `<category>` | Recommended | Report builder category — see `references/category-values.md` |
| `<deployed>` | Yes | `true` to expose to users; `false` while building/iterating |
| `<join>` | Conditional | Adds a related object and its join behavior. Nest further `<join>` blocks for deeper relationships |
| `<sections>` | Recommended | Groups of columns available to the report type. Though not strictly required, a report without columns isn't useful |

`<sections>` (group of columns) sub-elements:

| Element | Required | Notes |
|---------|----------|-------|
| `<masterLabel>` | Yes | Section heading shown in the report builder |
| `<columns>` | Conditional | One per field exposed in the section |

`<columns>` (single field) sub-elements:

| Element | Required | Notes |
|---------|----------|-------|
| `<field>` | Yes | Field API name (or dotted lookup-traversal path) |
| `<table>` | Yes | The object the field belongs to — base object name or dotted relationship path |
| `<checkedByDefault>` | Yes | `true` if the column is selected by default in the report builder |
| `<displayNameOverride>` | No | Custom column label shown in the report builder, overriding the field's default label |

## Critical Rules (Read First)

### Rule 1: If `<fullName>` Is Present, It Must Match the File Name
In source format, `fullName` is inherited from `Metadata` and derived from the file name, so the `<fullName>` element is technically optional. The repo convention is to include it. **If you include `<fullName>`, its value must equal the file name (everything before `.reportType-meta.xml`) exactly — same characters, same casing, same underscores.**

**Wrong** — file name and `<fullName>` differ:
- File: `account_projects.reportType-meta.xml`
- `<fullName>AccountProjects</fullName>`
  (Mismatch: file uses `account_projects`, fullName uses `AccountProjects`)

**Right** — file name and `<fullName>` are identical:
- File: `AccountProjects.reportType-meta.xml`
- `<fullName>AccountProjects</fullName>`

### Rule 2: Join Semantics — `outerJoin` Controls Inclusion

Each `<join>` block has an `<outerJoin>` element that determines which primary records appear in the report:

| `<outerJoin>` value | Behavior | Report Builder Label |
|---------------------|----------|----------------------|
| `false` | Inner join — only primary records that HAVE at least one related record | "Each 'A' record must have at least one related 'B' record" |
| `true` | Outer join — all primary records, with or without related records | "'A' records may or may not have related 'B' records" |

**Default when unspecified:** Use `true` (outer join) when the user wants to see all primary records regardless of children. Use `false` when the report only makes sense if children exist.

### Rule 3: Each Object Needs Its Own `<sections>` Block

Every object in the CRT (primary + each joined object) must have a corresponding `<sections>` block that lists the fields exposed for reporting. Without a section for an object, none of its fields appear in the report builder.

- `<masterLabel>` on each section is the section heading in the report builder
- `<columns>` entries list the fields — each with a `<field>` (API name) and `<table>` (object API name)
- For fields reached via lookup, use the relationship path in `<field>` (e.g. `Owner.Name` with `<table>` set to the owning object)

### Rule 4: Field API Names, Not Labels

Use exact API names for fields: standard fields use their defined names (`Name`, `CreatedDate`, `OwnerId`), custom fields use `Field__c`. Custom objects must include `__c`.

**Wrong:**
- `<field>Account Name</field>`

**Right:**
- `<field>Name</field>` with `<table>Account</table>`

### Rule 5: Relationship Path for Joined Objects

When adding a `<join>`, the `<relationship>` element must use the **child relationship name** as defined on the lookup/master-detail field pointing from the child object to the parent. For custom relationships, this typically ends in `__r`.

**Wrong:**
- `<relationship>Project</relationship>` (for a custom child relationship)

**Right:**
- `<relationship>Projects__r</relationship>` (child relationship name)
- `<relationship>Contacts</relationship>` (standard, non-custom child relationship)

### Rule 6: Maximum 4 Objects Total in a Join Chain

A single CRT can join a maximum of **four objects total** (the base object + up to 3 additional objects via nested `<join>` blocks).

### Rule 7: No Inner Join After an Outer Join

Once the join chain contains an outer join (`<outerJoin>true</outerJoin>`), every subsequent nested join must also be an outer join. An inner join that follows an outer join earlier in the sequence is not allowed.

**Wrong:**
```xml
<join>
    <outerJoin>true</outerJoin>        <!-- outer join first -->
    <relationship>Contacts</relationship>
    <join>
        <outerJoin>false</outerJoin>   <!-- WRONG: inner join after outer -->
        <relationship>Assets</relationship>
    </join>
</join>
```

**Right:**
```xml
<join>
    <outerJoin>true</outerJoin>
    <relationship>Contacts</relationship>
    <join>
        <outerJoin>true</outerJoin>    <!-- outer stays outer -->
        <relationship>Assets</relationship>
    </join>
</join>
```

### Rule 8: `<table>` for Joined Objects Uses Dotted Path

In `<sections>`, the `<table>` element identifies which object in the join chain each column belongs to. For the base object, use the object name directly (e.g. `Account`). For joined objects, use the **dotted relationship path** from the base object.

| Object in chain | `<table>` value |
|-----------------|-----------------|
| Base (Account) | `Account` |
| First join (Account → Contacts) | `Account.Contacts` |
| Nested join (Account → Contacts → Assets) | `Account.Contacts.Assets` |

### Rule 9: Field Paths Can Traverse Lookups

`<field>` values may reference fields reached via lookup relationships using dot notation — for example `Owner.Email` (owner User's email) or `ReportsTo.CreatedBy.Contact.Owner.MobilePhone`. The `<table>` must still be the object that owns the starting field.

### Rule 10: Historical Trending Fields Use `_hst` Suffix

For a field with `trackTrending=true`, the API name in `<field>` and `<table>` uses the `_hst` suffix:

```xml
<columns>
    <checkedByDefault>false</checkedByDefault>
    <field>Field2__c_hst</field>
    <table>CustomTrendedObject__c.CustomTrendedObject__c_hst</table>
</columns>
```

### Rule 11: Primary Object Cannot Be Changed After Deployment

Once deployed, the `<baseObject>` of a CRT is locked. To change the primary object, create a new CRT and retire the old one.

### Rule 12: `autogenerated` Is Reserved for Historical Trending

The `<autogenerated>` element (API 29.0+) marks CRTs that Salesforce created automatically when historical trending was enabled on an object. Do not set this manually on hand-authored CRTs.

## Generation Workflow

### Step 1: Gather Requirements
- Primary object API name (e.g. `Account`, `Project__c`)
- Related objects and the relationship between each (which has the lookup/master-detail to which)
- For each relationship: inner join (children required) or outer join (children optional)?
- Which fields to expose per object — aim for task-relevant, not the full field list
- Audience and category — where should this appear in the report builder picker?
- Whether this ships as `deployed=true` now or stays `deployed=false` during iteration

### Step 2: Examine Existing Examples
- Look in the project for in-project CRT patterns
- If existing report types have been retrieved from an org, compare against those structures

### Step 3: Write the Specification
Document before authoring:
- `fullName` and `label`
- `baseObject`
- Category and `deployed` state
- Join chain: for each related object — relationship name, outer vs inner join
- Section layout: one section per object, ordered list of fields
- Acceptance criteria: which records should appear when the report runs, which fields are available in the builder

### Step 4: Author the Metadata File

Start from the closest example in `examples/` and adapt it to the user's scenario:

- Primary object only (no joins) → `examples/AccountsWithIndustry.reportType-meta.xml`
- Outer join (primary records included even without children) → `examples/AccountsWithProjects.reportType-meta.xml`
- Nested inner join (every level requires children) → `examples/AccountProjectsWithTasks.reportType-meta.xml`

Name the file `<DeveloperName>.reportType-meta.xml`.

### Step 5: Validate
- Well-formed XML with correct namespace (`xmlns="http://soap.sforce.com/2006/04/metadata"`)
- File name (without `.reportType-meta.xml`) matches `<fullName>` when `<fullName>` is included
- `<baseObject>` is a valid API name and the object is deployed
- Every `<relationship>` uses the correct child relationship name (`__r` suffix for custom)
- Each object referenced in `<sections>` is part of the CRT (primary or joined)
- All `<field>` references exist on the parent `<table>` and use API names (not labels)
- `<category>` is a valid Salesforce category value
- `<deployed>` is `true` if users need to access the CRT immediately

## Reference File Index

| File | When to read |
|------|--------------|
| `examples/AccountsWithIndustry.reportType-meta.xml` | Step 2 / Step 4 — primary-object-only template |
| `examples/AccountsWithProjects.reportType-meta.xml` | Step 2 / Step 4 — outer-join template (primary included even without children) |
| `examples/AccountProjectsWithTasks.reportType-meta.xml` | Step 2 / Step 4 — nested inner-join template (every level requires children) |
| `references/category-values.md` | Step 3 — to choose a valid `<category>` value from the `ReportTypeCategory` enum |
| `references/errors-and-troubleshooting.md` | When fields don't appear in the report builder or join requirements conflict |

## Verification Checklist

### Universal Checks
- [ ] File extension is `.reportType-meta.xml`
- [ ] File basename satisfies the developer-name rules (begins with a letter, only letters/numbers/underscores, no spaces, no trailing underscore, no consecutive underscores)
- [ ] If `<fullName>` is included, it matches the file basename exactly (same characters, casing, and underscores)
- [ ] `<label>` is human-readable and under 40 characters
- [ ] `<description>` explains the business purpose
- [ ] `<baseObject>` uses a valid API name and that object is deployed
- [ ] `<category>` is a valid `ReportTypeCategory` enum value
- [ ] `<deployed>` is set appropriately (`true` for user access, `false` for in-progress iteration)
- [ ] `<autogenerated>` is NOT set manually (reserved for historical-trending CRTs)

### Join Checks
- [ ] Each `<join>` uses the correct child **relationship name** (not the lookup field API name)
- [ ] Custom relationships use `__r` suffix
- [ ] `<outerJoin>` is set intentionally: `true` = optional children, `false` = required children
- [ ] No inner join (`<outerJoin>false</outerJoin>`) appears after an outer join earlier in the sequence
- [ ] Total object count (base + joins, including nested) is 4 or fewer

### Section Checks
- [ ] Every object in the CRT has a corresponding `<sections>` block
- [ ] `<masterLabel>` on each section is descriptive
- [ ] Every `<columns>` has both `<field>` (API name) and `<table>` (object API name or dotted path)
- [ ] `<checkedByDefault>` is set for each column
- [ ] `<table>` for base object is the object API name (e.g. `Account`)
- [ ] `<table>` for joined objects uses the dotted relationship path (e.g. `Account.Projects__r`, `Account.Projects__r.Tasks__r`)
- [ ] Field references use API names (not labels); custom fields use `__c`
- [ ] Lookup traversal fields use dot notation (e.g. `Owner.Email`) with `<table>` set to the object owning the starting field
- [ ] Historical trending fields use `_hst` suffix in both `<field>` and `<table>` when applicable
- [ ] No duplicate fields within a section

© forcedotcom · Apache-2.0 · forcedotcom/sf-skills

Similar skills

All skills for Data and databases →

From the same set167

mobile-platform-native-capabilities-integrate Guides an agent through building Salesforce LWCs that use native mobile features such as scanning, location, NFC, and payments. Use it for lightning/mobileCapabilities work. CX Codex +1 4,700installs commerce-b2b-open-code-components-replace Replaces out-of-the-box B2B Commerce components with open source equivalents in site metadata content.json. Or just reports the mappings when you don't want files touched. CX Codex +1 4,600installs commerce-b2b-open-code-components-integrate Pulls Salesforce B2B Commerce open source components from GitHub into a store's site metadata so they show up in the Experience Builder palette. CX Codex +1 4,600installs platform-policy-rule-generate Writes PolicyRuleDefinition and PolicyRuleDefinitionSet metadata XML for Salesforce Data Cloud: schema, category tree and pre-deploy checks. CX Codex +1 4,100installs platform-sharing-owd-configure Reads and changes Organization-Wide Defaults on Salesforce objects: shows current access levels and edits sharingModel in the object metadata. CX Codex +1 3,700installs platform-encryption-configure Skill for generating Salesforce Shield Platform Encryption metadata and evaluating key management architecture models. CX Codex +1 3,600installs platform-dataspace-access-configure Configures and inspects Salesforce Data Cloud DataSpace access for permission sets. Use it when a permission set needs a DataSpace scope or object grants. CX Codex +1 3,700installs dx-pkg-post-install-configure Automates Salesforce managed package setup after installation from its documentation, including permissions, FLS, page layouts and tab access. CX Codex +1 3,500installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor