Skip to content
Development
Skill

/add-pdf-report

Internal implementation skill invoked by /add-native for app-generated PDF report workflows using expo-print and, when present, expo-sharing.

From plugin
power-platform-skills
86896 skills19 agents4 MCP
Install
$ npx -y skills add microsoft/power-platform-skills --skill add-pdf-report --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/add-pdf-report

Context preview

The summary Claude sees to decide when to auto-load this skill.

Internal implementation skill invoked by /add-native for app-generated PDF report workflows using expo-print and, when present, expo-sharing.

SKILL.md

add-pdf-report.SKILL.md
name: add-pdf-report
description: Internal implementation skill invoked by /add-native for app-generated PDF report workflows using expo-print and, when present, expo-sharing.
user-invocable: false
disable-model-invocation: true
allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion
model: sonnet

**Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - read first.

Add PDF Report

**Internal helper.** Users should invoke `/add-native pdf-report`, `/add-native generate-pdf`, or `/add-native pdf-export`; `/add-native` routes here after resolving the capability.

Generate or verify a local PDF report wrapper for app-owned PDFs created from records, evidence, certificates, receipts, or summaries. This helper uses `expo-print` to create a local PDF file URI. It may use `expo-sharing` only when that package is already present. It never installs packages or imports the native PDF viewer directly.

Capability boundaries

| User need | Correct path | |---|---| | Generate/export/print a report from app data | This helper: `expo-print` -> local PDF URI | | Share the generated local PDF from the device | Add share method only if `expo-sharing` is already in `package.json` | | Retain the generated PDF in Dataverse | Create/update parent row first, then upload to a Dataverse File column with generated services | | Open an existing HTTPS or local file PDF in the Power Apps native viewer | `/add-native pdf-viewer`, only if `@microsoft/power-apps-native-pdf-viewer` 0.2.9+ is already present | | Pick/import/upload a user-selected PDF | `/add-native document-picker` or host `<FilePicker>` for Dataverse File columns |

Local generated PDFs are usually `file://` URIs and can be passed to `openHttpsPdf(...)` with `@microsoft/power-apps-native-pdf-viewer` 0.2.9+.

Steps

1. Verify app

test -f app.config.js && test -f power.config.json && test -f package.json && test -d src

If this fails, tell the user to run `/create-mobile-app` first and STOP.

2. Verify packages are already present

`expo-print` is required. `expo-sharing` is optional unless the plan specifically needs sharing behavior.

node -e "const p=require('./package.json'); const deps={...p.dependencies,...p.devDependencies}; const required='expo-print'; if (!deps[required]) { console.error('MISSING: expo-print is not in package.json. The template/app must already ship it for /add-native pdf-report. This skill will not install it or edit native config. Capability not added.'); process.exit(1); } console.log('OK: expo-print package present'); console.log(deps['expo-sharing'] ? 'OK: expo-sharing package present' : 'OPTIONAL_MISSING: expo-sharing is not in package.json; generated PDFs can be created/viewed/uploaded, but sharing helpers must not be generated.');"

If `expo-print` is missing, STOP. Do not run `npm install`, `npx expo install`, `pod install`, or edit `app.config.js`. Do not add `pdf-report` to the plan or generated wrappers for this app.

If `expo-sharing` is missing:

  • Continue for generate-only, native-viewer preview, or Dataverse-upload flows.
  • Do not import `expo-sharing`.
  • Do not generate `sharePdfReport(...)`.
  • If the user's requirement specifically includes sharing, STOP and say sharing is not supported by this template.

3. Write or verify `src/native/pdfReport.ts`

Create `src/native/pdfReport.ts` if it does not exist. If it already exists, inspect it and patch only if it throws instead of returning a result, imports missing packages, or routes local URIs to the native PDF viewer.

The wrapper MUST:

  • Import `expo-print` only after Step 2 confirms it is present.
  • Import `expo-sharing` only when Step 2 confirms it is present.
  • Return discriminated unions and never throw.
  • Treat generated local PDFs as local files for view/share/upload flows.
  • Never import `@microsoft/power-apps-native-pdf-viewer` directly from this wrapper.
  • Keep HTML generation deterministic and app-owned; do not fetch remote HTML inside the wrapper.

Base wrapper when `expo-sharing` is present:

// src/native/pdfReport.ts
import * as Print from 'expo-print';
import * as Sharing from 'expo-sharing';

export type PdfReportResult =
  | { ok: true; uri: string; numberOfPages?: number; base64?: string }
  | { ok: false; reason: 'EMPTY_HTML' | 'PRINT_FAILED'; message?: string };

export type PdfShareResult =
  | { ok: true }
  | { ok: false; reason: 'INVALID_URI' | 'SHARING_UNAVAILABLE' | 'SHARE_FAILED'; message?: string };

export function escapePdfHtml(value: string): string {
  return value
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

export function wrapPdfDocument(input: { title: string; bodyHtml: string; styles?: string }): string {
  const title = escapePdfHtml(input.title.trim() || 'Report');
  return `<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>${title}</title>
    <style>
      body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 32px; color: #111827; }
      h1, h2, h3 { margin: 0 0 12px; }
      table { width: 100%; border-collapse: collapse; }
      th, td { border-bottom: 1px solid #e5e7eb; padding: 8px; text-align: left; }
      ${input.styles ?? ''}
    </style>
  </head>
  <body>${input.bodyHtml}</body>
</html>`;
}

export async function createPdfReport(
  html: string,
  options?: { includeBase64?: boolean },
): Promise<PdfReportResult> {
  if (!html.trim()) {
    return { ok: false, reason: 'EMPTY_HTML', message: 'PDF report HTML is empty.' };
  }

  try {
    const result = await Print.printToFileAsync({
      html,
      base64: options?.includeBase64 ?? false,
    });

    return {
      ok: true,
      uri: result.uri,
      numberOfPages: result.numberOfPages,
      base64: result.base64,
Read more
Ships withpower-platform-skills

Official agent skills/plugins for Power Platform development by Microsoft.

Get the whole plugin

Other skills on power-platform-skills.