Files
twenty/packages/twenty-apps/examples/document-generator/src/objects/document.object.ts
T
martmull 07a921f8ca Add Document Generator SDK app + step-by-step tutorial (#22522)
## What & why

This adds a **guided tutorial** that teaches the Twenty SDK by building
one real, useful app end to end — plus the finished app itself, ready
for the marketplace.

The app, **Document Generator**, turns reusable templates into
personalized documents using CRM data: write a template once with
`{{placeholders}}`, then generate a filled-in document for any Person or
Company from the command menu, an AI agent, or a workflow.

## Two parts

**1. The app — `packages/twenty-apps/public/document-generator`**

Each capability maps to one tutorial chapter:
- **Data:** `documentTemplate` + `document` objects, fields, and a
bidirectional relation
- **Logic:** a single `generate-document` handler exposed as an **AI
tool**, a **workflow action**, and an **HTTP POST route**; plus a public
**HTML view route**
- **UI:** two views + sidebar navigation, a **command-menu item** (on
Person selection) that opens a **React front component**
- **AI:** an agent + skill; a default application role; marketplace
metadata + logo
- **Tests:** unit tests for the template renderer + an install
integration test

**2. The tutorial —
`packages/twenty-docs/.../apps/tutorials/document-generator/`**

A six-chapter series under **Developers › Apps › Tutorial** (Overview →
Data model → Generating documents → HTTP routes → Building the UI → AI
agent → Publishing). Minimal prose, paste-ready code, inline links to
the matching reference pages, and real screenshots. Registers a new
"Tutorial" nav group and regenerates `docs.json` + the navigation
template.

## Verification

Validated against a running Twenty instance (`twenty-app-dev` on
`:2020`):
- `twenty dev --once` installs cleanly (28 metadata objects created)
- Generated a real document from a Person — placeholders resolved (name,
job title, `company.name`, email), zero missing tokens
- Command menu → front component → generate flow works in the UI
- Public HTML view route renders the document
- App gates green: `yarn lint` (0/0), `yarn typecheck`, `yarn test:unit`
(7/7)

All screenshots in the tutorial are captured from this run.

## Notes
- Left out per-app CI workflows (`.github/workflows`) to keep scope
tight — happy to add them if wanted.

https://claude.ai/code/session_012FoC76zPbPmpgtN7MXMPwy

---
_Generated by [Claude
Code](https://claude.ai/code/session_012FoC76zPbPmpgtN7MXMPwy)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22522?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-07 11:13:34 +02:00

78 lines
2.4 KiB
TypeScript

import { defineObject, FieldType } from 'twenty-sdk/define';
import {
DOCUMENT_CONTENT_FIELD_UNIVERSAL_IDENTIFIER,
DOCUMENT_FILE_FIELD_UNIVERSAL_IDENTIFIER,
DOCUMENT_NAME_FIELD_UNIVERSAL_IDENTIFIER,
DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
DOCUMENT_STATUS_DRAFT,
DOCUMENT_STATUS_FIELD_UNIVERSAL_IDENTIFIER,
DOCUMENT_STATUS_GENERATED,
DOCUMENT_STATUS_OPTION_DRAFT_UNIVERSAL_IDENTIFIER,
DOCUMENT_STATUS_OPTION_GENERATED_UNIVERSAL_IDENTIFIER,
} from 'src/constants/universal-identifiers';
export default defineObject({
universalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
nameSingular: 'document',
namePlural: 'documents',
labelSingular: 'Document',
labelPlural: 'Documents',
description: 'A generated document produced from a template and a CRM record.',
icon: 'IconFile',
labelIdentifierFieldMetadataUniversalIdentifier:
DOCUMENT_NAME_FIELD_UNIVERSAL_IDENTIFIER,
fields: [
{
universalIdentifier: DOCUMENT_NAME_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.TEXT,
name: 'name',
label: 'Name',
description: 'Document name.',
icon: 'IconAbc',
},
{
universalIdentifier: DOCUMENT_CONTENT_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.TEXT,
name: 'content',
label: 'Content',
description: 'The rendered document text, with all placeholders filled.',
icon: 'IconFileText',
},
{
universalIdentifier: DOCUMENT_STATUS_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.SELECT,
name: 'status',
label: 'Status',
description: 'Where this document is in its lifecycle.',
icon: 'IconProgress',
defaultValue: `'${DOCUMENT_STATUS_DRAFT}'`,
options: [
{
id: DOCUMENT_STATUS_OPTION_DRAFT_UNIVERSAL_IDENTIFIER,
value: DOCUMENT_STATUS_DRAFT,
label: 'Draft',
color: 'gray',
position: 0,
},
{
id: DOCUMENT_STATUS_OPTION_GENERATED_UNIVERSAL_IDENTIFIER,
value: DOCUMENT_STATUS_GENERATED,
label: 'Generated',
color: 'green',
position: 1,
},
],
},
{
universalIdentifier: DOCUMENT_FILE_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.FILES,
name: 'file',
label: 'File',
description: 'The generated document as a downloadable PDF.',
icon: 'IconFileTypePdf',
universalSettings: { maxNumberOfValues: 1 },
},
],
});