mirror of
https://github.com/documenso/documenso.git
synced 2026-06-22 20:32:07 +10:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e596ce7f5 | |||
| b41b026e95 | |||
| f4e6aca4cf |
@@ -1,161 +0,0 @@
|
||||
---
|
||||
date: 2026-01-28
|
||||
title: Pdf Placeholder Field Positioning
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This feature enables automatic field placement in PDFs using placeholder text, eliminating the need for manual coordinate-based positioning. It supports two complementary workflows:
|
||||
|
||||
1. **Automatic detection on upload** - PDFs containing structured placeholders like `{{signature, r1}}` have fields created automatically when uploaded
|
||||
2. **API placeholder positioning** - Developers can reference any text in a PDF to position fields instead of calculating coordinates
|
||||
|
||||
## Goals
|
||||
|
||||
- Allow users to prepare documents in Word/Google Docs with placeholders that become signature fields
|
||||
- Reduce friction for document preparation workflows
|
||||
- Provide API developers with a simpler alternative to coordinate-based field positioning
|
||||
- Support documents with repeated placeholders (e.g., initials on every page)
|
||||
|
||||
## Placeholder Format (Automatic Detection)
|
||||
|
||||
```
|
||||
{{FIELD_TYPE, RECIPIENT, option1=value1, option2=value2}}
|
||||
```
|
||||
|
||||
### Components
|
||||
|
||||
- **FIELD_TYPE** (required): One of `signature`, `initials`, `name`, `email`, `date`, `text`, `number`, `radio`, `checkbox`, `dropdown`
|
||||
- **RECIPIENT** (required): `r1`, `r2`, `r3`, etc. - identifies which recipient the field belongs to
|
||||
- **OPTIONS** (optional): Key-value pairs like `required=true`, `fontSize=14`, `readOnly=true`
|
||||
|
||||
### Examples
|
||||
|
||||
- `{{signature, r1}}` - Signature field for first recipient
|
||||
- `{{text, r1, required=true, label=Company Name}}` - Required text field with label
|
||||
- `{{number, r2, minValue=0, maxValue=100}}` - Number field with validation
|
||||
|
||||
### Behavior
|
||||
|
||||
- Placeholders without recipient identifiers (e.g., `{{signature}}`) are skipped during automatic detection - reserved for API use
|
||||
- Invalid field types are silently skipped
|
||||
- Placeholder text is covered with white rectangles after field creation
|
||||
|
||||
## API Placeholder Positioning
|
||||
|
||||
The `/api/v2/envelope/field/create-many` endpoint accepts `placeholder` as an alternative to coordinates:
|
||||
|
||||
```json
|
||||
{
|
||||
"recipientId": 123,
|
||||
"type": "SIGNATURE",
|
||||
"placeholder": "{{signature}}"
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ------------- | ------- | -------------------------------------------- |
|
||||
| `placeholder` | string | Text to search for in the PDF |
|
||||
| `width` | number | Optional override (percentage) |
|
||||
| `height` | number | Optional override (percentage) |
|
||||
| `matchAll` | boolean | When true, creates fields at ALL occurrences |
|
||||
|
||||
### matchAll Behavior
|
||||
|
||||
- Default (`false`): Only first occurrence gets a field
|
||||
- `true`: Creates a field at every occurrence of the placeholder text
|
||||
|
||||
This is useful for documents requiring initials on every page.
|
||||
|
||||
## Implementation Components
|
||||
|
||||
### Core Functions
|
||||
|
||||
- `extractPlaceholdersFromPDF()` - Scans PDF for `{{...}}` patterns with recipient identifiers
|
||||
- `removePlaceholdersFromPDF()` - Covers placeholder text with white rectangles
|
||||
- `whiteoutRegions()` - Low-level helper for drawing white boxes on PDF pages
|
||||
- `parseFieldTypeFromPlaceholder()` - Converts placeholder field type to FieldType enum
|
||||
- `parseFieldMetaFromPlaceholder()` - Parses options into fieldMeta format
|
||||
|
||||
### Integration Points
|
||||
|
||||
1. **Upload flow** (`create-envelope.ts`, `create-envelope-items.ts`)
|
||||
- Extract placeholders at upload time (before saving to storage)
|
||||
- Pass placeholders in-memory to envelope creation
|
||||
- Create placeholder recipients if none provided
|
||||
- Create fields within the same transaction
|
||||
|
||||
2. **API field creation** (`create-envelope-fields.ts`)
|
||||
- Accept `placeholder` as alternative to coordinates
|
||||
- Search PDF for placeholder text
|
||||
- Resolve position from bounding box
|
||||
- Support `matchAll` for multiple occurrences
|
||||
|
||||
### Field Meta Parsing
|
||||
|
||||
The following properties are explicitly parsed:
|
||||
|
||||
- `required`, `readOnly` → boolean
|
||||
- `fontSize`, `minValue`, `maxValue`, `characterLimit` → number
|
||||
- Other properties pass through as strings
|
||||
|
||||
Note: Signature fields do not support fieldMeta options.
|
||||
|
||||
## Testing
|
||||
|
||||
### E2E Tests
|
||||
|
||||
**UI Tests** (`e2e/auto-placing-fields/`):
|
||||
|
||||
- Single recipient placeholder detection
|
||||
- Multiple recipient placeholder detection
|
||||
- Field configuration from placeholder options
|
||||
- Skipping placeholders without recipient identifiers
|
||||
- Skipping invalid field types
|
||||
|
||||
**API Tests** (`e2e/api/v2/placeholder-fields-api.spec.ts`):
|
||||
|
||||
- Placeholder-based field positioning
|
||||
- Width/height overrides
|
||||
- Error on placeholder not found
|
||||
- Mixed coordinate and placeholder positioning
|
||||
- First occurrence only (default)
|
||||
- All occurrences with `matchAll: true`
|
||||
|
||||
## Documentation
|
||||
|
||||
### User Documentation
|
||||
|
||||
`/users/documents/pdf-placeholders` - Explains:
|
||||
|
||||
- Placeholder format and syntax
|
||||
- Supported field types
|
||||
- Recipient identifiers
|
||||
- Available options per field type
|
||||
- Troubleshooting
|
||||
|
||||
### Developer Documentation
|
||||
|
||||
`/developers/public-api/reference` - Documents:
|
||||
|
||||
- Coordinate-based positioning (existing)
|
||||
- Placeholder-based positioning (new)
|
||||
- matchAll parameter
|
||||
- Mixing both methods
|
||||
|
||||
## Edge Cases Handled
|
||||
|
||||
1. **No placeholders found** - Original PDF returned unchanged
|
||||
2. **Placeholder not found (API)** - Returns error with placeholder text
|
||||
3. **Multiple occurrences** - First only by default, all with `matchAll: true`
|
||||
4. **No recipient identifier** - Skipped during auto-detection, works for API
|
||||
5. **Invalid field type** - Skipped during auto-detection
|
||||
6. **Signature field with options** - Options ignored (signature doesn't support fieldMeta)
|
||||
|
||||
## Future Considerations
|
||||
|
||||
- Support for placeholder text styles (bold, underline) to indicate field properties
|
||||
- Template-level placeholder mapping for reusable configurations
|
||||
- Placeholder validation in document editor before sending
|
||||
@@ -1,76 +0,0 @@
|
||||
---
|
||||
date: 2026-01-26
|
||||
title: Validate Signer Fields On Distribute
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Validate that signers have at least one signature field before allowing document/envelope distribution via API, matching the existing UI behavior.
|
||||
|
||||
## Background
|
||||
|
||||
The API originally allowed distributing documents/envelopes without validating that signers had signature fields assigned. This was intentional - we thought API users might have specific flows where this flexibility was needed.
|
||||
|
||||
However, after running it this way for a while, we've observed that more often than not, API users inadvertently send documents without fields assigned. This causes confusion for their recipients (who receive a document with nothing to sign) and breaks their own systems expecting a completed signing flow.
|
||||
|
||||
## Problem
|
||||
|
||||
The API allowed distributing documents/envelopes even when signers had no signature fields assigned. This was inconsistent with the UI which validates this condition before allowing distribution.
|
||||
|
||||
## Solution
|
||||
|
||||
### 1. Create centralized validation helper
|
||||
|
||||
**File**: `packages/lib/utils/recipients.ts`
|
||||
|
||||
- Added `RECIPIENT_ROLES_THAT_REQUIRE_FIELDS` constant (currently only `SIGNER`)
|
||||
- Added `getRecipientsWithMissingFields()` function that returns recipients missing required fields
|
||||
- Uses existing `isSignatureFieldType` guard from `packages/prisma/guards/is-signature-field.ts`
|
||||
|
||||
### 2. Add server-side validation
|
||||
|
||||
**File**: `packages/lib/server-only/document/send-document.ts`
|
||||
|
||||
- Added validation check that throws `AppError` with `INVALID_REQUEST` code when signers are missing signature fields
|
||||
- This blocks both v1 and v2 API distribution endpoints since they both use `sendDocument()`
|
||||
|
||||
### 3. Fix v1 API error handling
|
||||
|
||||
**File**: `packages/api/v1/implementation.ts`
|
||||
|
||||
- Changed `sendDocument` endpoint to use `AppError.toRestAPIError(err)` instead of always returning 500
|
||||
- Now returns 400 for validation errors
|
||||
|
||||
### 4. Update UI to use shared helper
|
||||
|
||||
**Files**:
|
||||
|
||||
- `apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx`
|
||||
- `packages/ui/primitives/document-flow/add-fields.tsx`
|
||||
|
||||
### 5. Consolidate `hasSignatureField` checks
|
||||
|
||||
Updated to use `isSignatureFieldType` guard (checks both `SIGNATURE` and `FREE_SIGNATURE`):
|
||||
|
||||
- `apps/remix/app/components/general/document-signing/document-signing-form.tsx`
|
||||
- `apps/remix/app/components/general/envelope-signing/envelope-signer-form.tsx`
|
||||
- `apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx`
|
||||
- `apps/remix/app/components/embed/embed-direct-template-client-page.tsx`
|
||||
- `apps/remix/app/components/embed/embed-document-signing-page-v1.tsx`
|
||||
|
||||
### 6. Add E2E tests
|
||||
|
||||
**Files**:
|
||||
|
||||
- `packages/app-tests/e2e/api/v1/document-sending.spec.ts` - 5 new tests
|
||||
- `packages/app-tests/e2e/api/v2/distribute-validation.spec.ts` - 8 new tests
|
||||
|
||||
## Test Coverage
|
||||
|
||||
- Distribution fails when signer has no fields
|
||||
- Distribution fails when signer has only non-signature fields
|
||||
- Distribution succeeds with SIGNATURE field
|
||||
- Distribution succeeds with FREE_SIGNATURE field (v1 only via Prisma)
|
||||
- Distribution succeeds when VIEWER/CC/APPROVER have no fields
|
||||
- Distribution fails when one of multiple signers is missing signature field
|
||||
- Distribution succeeds when all signers have signature fields
|
||||
@@ -1,168 +0,0 @@
|
||||
---
|
||||
date: 2026-02-11
|
||||
title: Cert Page Width Mismatch
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
Certificate and audit log pages are generated with hardcoded A4 dimensions (`PDF_SIZE_A4_72PPI`: 595×842) regardless of the actual document page sizes. When the source document uses a different page size (e.g., Letter, Legal, or custom dimensions), the certificate/audit log pages end up with a different width than the document pages. This causes problems with courts that expect uniform page dimensions throughout a PDF.
|
||||
|
||||
**Both width and height must match** the last page of the document so the entire PDF prints uniformly.
|
||||
|
||||
**Root cause**: In `seal-document.handler.ts` (lines 186-187), the certificate payload always uses:
|
||||
|
||||
```ts
|
||||
pageWidth: PDF_SIZE_A4_72PPI.width, // 595
|
||||
pageHeight: PDF_SIZE_A4_72PPI.height, // 842
|
||||
```
|
||||
|
||||
These hardcoded values flow into `generateCertificatePdf`, `generateAuditLogPdf`, `renderCertificate`, and `renderAuditLogs` — all of which use `pageWidth`/`pageHeight` to set Konva stage dimensions and layout content.
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Role |
|
||||
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| `packages/lib/jobs/definitions/internal/seal-document.handler.ts` | Orchestrates sealing; passes page dimensions to cert/audit generators |
|
||||
| `packages/lib/constants/pdf.ts` | Defines `PDF_SIZE_A4_72PPI` (595×842) |
|
||||
| `packages/lib/server-only/pdf/generate-certificate-pdf.ts` | Generates certificate PDF; accepts `pageWidth`/`pageHeight` |
|
||||
| `packages/lib/server-only/pdf/generate-audit-log-pdf.ts` | Generates audit log PDF; accepts `pageWidth`/`pageHeight` |
|
||||
| `packages/lib/server-only/pdf/render-certificate.ts` | Renders certificate pages via Konva; uses `pageWidth`/`pageHeight` for stage + layout |
|
||||
| `packages/lib/server-only/pdf/render-audit-logs.ts` | Renders audit log pages via Konva; uses `pageWidth`/`pageHeight` for stage + layout |
|
||||
| `packages/lib/server-only/pdf/get-page-size.ts` | Existing utility — extend with `@libpdf/core` version |
|
||||
| `packages/trpc/server/document-router/download-document-certificate.ts` | Standalone certificate download (also hardcodes A4) |
|
||||
| `packages/trpc/server/document-router/download-document-audit-logs.ts` | Standalone audit log download (also hardcodes A4) |
|
||||
|
||||
## Architecture
|
||||
|
||||
### Current Flow
|
||||
|
||||
1. **One cert PDF + one audit log PDF** generated per envelope with hardcoded A4 dims
|
||||
2. Both appended to **every** envelope item (document) via `decorateAndSignPdf` → `pdfDoc.copyPagesFrom()`
|
||||
3. The audit log is envelope-level (all recipients, all events across all docs) — one per envelope, not per document
|
||||
|
||||
### Multi-Document Envelopes
|
||||
|
||||
- V1 envelopes: single document only
|
||||
- V2 envelopes: support multiple documents (envelope items)
|
||||
- Each envelope item gets both cert + audit log pages appended to it
|
||||
- If documents have different page sizes → need size-matched cert/audit for each
|
||||
|
||||
### Reading Page Dimensions (`@libpdf/core` only)
|
||||
|
||||
Use `@libpdf/core`'s `PDF` class — NOT `@cantoo/pdf-lib`:
|
||||
|
||||
```ts
|
||||
const pdfDoc = await PDF.load(pdfData);
|
||||
const lastPage = pdfDoc.getPage(pdfDoc.getPageCount() - 1);
|
||||
const { width, height } = lastPage; // e.g. 612, 792 for Letter
|
||||
```
|
||||
|
||||
Already used this way in `seal-document.handler.ts` lines 403-410 for V2 field insertion.
|
||||
"Last page" = last page of the original document, before cert/audit pages are appended.
|
||||
|
||||
### Content Layout Adaptation
|
||||
|
||||
Both renderers already handle variable dimensions gracefully:
|
||||
|
||||
- **Width**: `render-certificate.ts:713` / `render-audit-logs.ts:588` — `Math.min(pageWidth - minimumMargin * 2, contentMaxWidth)` with `contentMaxWidth = 768`. Wider pages get more margin, narrower pages tighter margins.
|
||||
- **Height**: Both renderers paginate content into pages using `groupRowsIntoPages()` which respects `pageHeight` via `maxTableHeight = pageHeight - pageTopMargin - pageBottomMargin`. Shorter pages just mean more pages; taller pages fit more rows per page.
|
||||
|
||||
### Playwright PDF Path — Out of Scope
|
||||
|
||||
The `NEXT_PRIVATE_USE_PLAYWRIGHT_PDF` toggle enables a deprecated Playwright-based PDF generation path (`get-certificate-pdf.ts`, `get-audit-logs-pdf.ts`) that also hardcodes `format: 'A4'` in `page.pdf()`. This path is **not being updated** as part of this fix:
|
||||
|
||||
- Both files are marked `@deprecated`
|
||||
- The Konva-based path is the default and recommended path
|
||||
- The Playwright path is behind a feature flag and will be removed
|
||||
|
||||
No changes needed. Add a code comment noting the A4 limitation if the Playwright path is ever re-enabled.
|
||||
|
||||
## Plan
|
||||
|
||||
### 1. Extend `get-page-size.ts` with `@libpdf/core` utility
|
||||
|
||||
Add a `getLastPageDimensions` function to the existing `packages/lib/server-only/pdf/get-page-size.ts` file. This consolidates page-size logic in one place (the file already has the legacy `@cantoo/pdf-lib` version).
|
||||
|
||||
```ts
|
||||
export const getLastPageDimensions = (pdfDoc: PDF): { width: number; height: number } => {
|
||||
const lastPage = pdfDoc.getPage(pdfDoc.getPageCount() - 1);
|
||||
const width = Math.round(lastPage.width);
|
||||
const height = Math.round(lastPage.height);
|
||||
|
||||
if (width < MIN_CERT_PAGE_WIDTH || height < MIN_CERT_PAGE_HEIGHT) {
|
||||
return { width: PDF_SIZE_A4_72PPI.width, height: PDF_SIZE_A4_72PPI.height };
|
||||
}
|
||||
|
||||
return { width, height };
|
||||
};
|
||||
```
|
||||
|
||||
**Dimension rounding**: `Math.round()` both width and height. PDF points at 72ppi are typically whole numbers; rounding avoids spurious float-precision mismatches (e.g., 612.0 vs 612.00001) that would cause unnecessary duplicate cert/audit PDF generation.
|
||||
|
||||
**Minimum page dimensions**: Enforce a minimum threshold (e.g., 300pt for both width and height). If either dimension falls below the minimum, fall back to A4 (595×842). The certificate and audit log renderers have headers, table rows, margins, and QR codes that require a minimum viable area.
|
||||
|
||||
### 2. Read last page dimensions from each envelope item's PDF
|
||||
|
||||
In `seal-document.handler.ts`, before generating cert/audit PDFs:
|
||||
|
||||
- For each `envelopeItem`, load the PDF and read the **last page's width and height** using `getLastPageDimensions`
|
||||
- Use `PDF.load()` then pass the loaded doc to the utility
|
||||
|
||||
**Resealing consideration**: When `isResealing` is true, envelope items are remapped to use `initialData` (lines 152-158) before this point. Page-size extraction must operate on the same data source that `decorateAndSignPdf` will use. Since the `envelopeItems` array is already remapped by the time we read dimensions, reading from `envelopeItem.documentData` will naturally give the correct (initial) data. No special handling needed beyond ensuring the dimension read happens **after** the resealing remap.
|
||||
|
||||
### 3. Generate cert/audit PDFs per unique page size
|
||||
|
||||
Current flow generates one cert + one audit log doc per envelope. Change to:
|
||||
|
||||
1. Collect `{ width, height }` of the last page for each envelope item
|
||||
2. Deduplicate by `"${width}x${height}"` key (using the already-rounded integers)
|
||||
3. For each unique size, generate cert PDF and audit log PDF with those dimensions
|
||||
4. Store in a `Map<string, { certificateDoc, auditLogDoc }>` keyed by `"${width}x${height}"`
|
||||
|
||||
For the common single-document case, this is one generation — same perf as today.
|
||||
|
||||
### 4. Thread the correct docs into `decorateAndSignPdf`
|
||||
|
||||
In the envelope item loop, look up the item's last-page dimensions in the map and pass the matching cert/audit docs. Signature of `decorateAndSignPdf` doesn't change — it still receives a single `certificateDoc` and `auditLogDoc`, just the right ones per item.
|
||||
|
||||
### 5. Update standalone download routes
|
||||
|
||||
`download-document-certificate.ts` and `download-document-audit-logs.ts` also hardcode A4:
|
||||
|
||||
- Both routes have `documentId` which maps to a specific envelope item
|
||||
- Fetch **that specific document's** PDF data, load it, read last page width + height via `getLastPageDimensions`
|
||||
- Pass `{ pageWidth, pageHeight }` to the generator
|
||||
- This ensures the standalone download matches the dimensions the user would see in the sealed PDF for that document
|
||||
|
||||
### 6. Edge cases
|
||||
|
||||
| Scenario | Behavior |
|
||||
| --------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| Mixed page sizes within one PDF | Use last page's dimensions (per spec) |
|
||||
| Page dimensions below minimum threshold | Fall back to A4 (595×842) |
|
||||
| Landscape pages | width/height just swap roles; renderers adapt via `Math.min()` capping. No special handling |
|
||||
| Fallback if page dims unreadable | Default to A4 (595×842) |
|
||||
| Resealing | Dimensions read after `initialData` remap — correct source automatically |
|
||||
| Playwright PDF path enabled | Remains A4 — out of scope, deprecated |
|
||||
| Single-doc envelope (most common) | One generation, same perf as today |
|
||||
| Multi-doc envelope, same page sizes | Dedup key matches → one generation |
|
||||
| Multi-doc envelope, different sizes | One generation per unique size |
|
||||
|
||||
### 7. Tests
|
||||
|
||||
- Add assertion-based E2E test (no visual regression / reference images needed)
|
||||
- Seal a Letter-size (612×792) PDF through the full flow
|
||||
- Load the sealed output and assert all pages (document + cert + audit) have matching width/height
|
||||
- Can be added to `envelope-alignment.spec.ts` or as a new focused test
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. **Extend `get-page-size.ts`** — add `getLastPageDimensions(pdfDoc: PDF): { width: number; height: number }` using `@libpdf/core`, with `Math.round()` and minimum dimension enforcement
|
||||
2. **In `seal-document.handler.ts`**:
|
||||
a. After the resealing remap (line ~159), load each envelope item's PDF via `PDF.load()` and collect last page `{ width, height }` using `getLastPageDimensions`
|
||||
b. Deduplicate by `"${width}x${height}"` key
|
||||
c. Generate cert/audit PDFs per unique size (parallel via `Promise.all`)
|
||||
d. In envelope item loop, look up matching cert/audit doc by size key
|
||||
3. **Fix `download-document-certificate.ts`** — load the specific document's PDF, read last page dims via `getLastPageDimensions`, pass to generator
|
||||
4. **Fix `download-document-audit-logs.ts`** — same as above, using the specific `documentId`'s PDF
|
||||
5. **Add E2E test** — assertion-based test with a Letter-size document verifying all page dimensions match after sealing
|
||||
@@ -1,186 +0,0 @@
|
||||
---
|
||||
date: 2026-01-14
|
||||
title: Simplewebauthn V13 Upgrade
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Upgrade SimpleWebAuthn packages from v9.x to v13.x to address the deprecation of `@simplewebauthn/types` and take advantage of new features and improvements.
|
||||
|
||||
## Current State
|
||||
|
||||
The codebase currently uses:
|
||||
- `@simplewebauthn/browser@9.x`
|
||||
- `@simplewebauthn/server@9.x`
|
||||
- `@simplewebauthn/types@9.x`
|
||||
|
||||
## Breaking Changes Summary (v9 → v13)
|
||||
|
||||
### v10.0.0 Breaking Changes
|
||||
1. **Minimum Node version raised to Node v20**
|
||||
2. **`generateRegistrationOptions()` now expects `Base64URLString` for `excludeCredentials` IDs** (no more `type: 'public-key'` needed)
|
||||
3. **`generateAuthenticationOptions()` now expects `Base64URLString` for `allowCredentials` IDs**
|
||||
4. **`credentialID` returned from verification methods is now `Base64URLString`** instead of `Uint8Array`
|
||||
5. **`AuthenticatorDevice.credentialID` is now `Base64URLString`**
|
||||
6. **`rpID` is now required when calling `generateAuthenticationOptions()`**
|
||||
7. **`generateRegistrationOptions()` will generate random user IDs** if not provided
|
||||
8. **`user.id` is treated as base64url string in `startRegistration()`**
|
||||
9. **`userHandle` is treated as base64url string in `startAuthentication()`**
|
||||
|
||||
### v11.0.0 Breaking Changes
|
||||
1. **Positional arguments in `startRegistration()` and `startAuthentication()` replaced by object**
|
||||
- Before: `startRegistration(options)`
|
||||
- After: `startRegistration({ optionsJSON: options })`
|
||||
- Before: `startAuthentication(options)`
|
||||
- After: `startAuthentication({ optionsJSON: options })`
|
||||
2. **`AuthenticatorDevice` type renamed to `WebAuthnCredential`**
|
||||
- `credentialID` → `credential.id`
|
||||
- `credentialPublicKey` → `credential.publicKey`
|
||||
3. **`verifyRegistrationResponse()` returns `registrationInfo.credential` instead of individual properties**
|
||||
- `credentialID` → `credential.id`
|
||||
- `credentialPublicKey` → `credential.publicKey`
|
||||
- `counter` → `credential.counter`
|
||||
- `transports` are now in `credential.transports`
|
||||
4. **`verifyAuthenticationResponse()` uses `credential` argument instead of `authenticator`**
|
||||
|
||||
### v13.0.0 Breaking Changes
|
||||
1. **`@simplewebauthn/types` package is retired**
|
||||
- Types are now exported from `@simplewebauthn/browser` and `@simplewebauthn/server`
|
||||
- Import types from `@simplewebauthn/server` instead
|
||||
|
||||
## Files to Update
|
||||
|
||||
### Package Changes
|
||||
1. Remove `@simplewebauthn/types` dependency
|
||||
2. Update `@simplewebauthn/browser` to `^13.2.2`
|
||||
3. Update `@simplewebauthn/server` to `^13.2.2`
|
||||
|
||||
### Server-side Files
|
||||
|
||||
#### 1. `packages/lib/server-only/auth/create-passkey-registration-options.ts`
|
||||
- Change import from `@simplewebauthn/types` to `@simplewebauthn/server`
|
||||
- Remove `type: 'public-key'` from `excludeCredentials` items
|
||||
- Update `userID` to use `isoUint8Array.fromUTF8String()` for proper encoding
|
||||
|
||||
#### 2. `packages/lib/server-only/auth/create-passkey-authentication-options.ts`
|
||||
- Change import from `@simplewebauthn/types` to `@simplewebauthn/server`
|
||||
- Remove `type: 'public-key'` from `allowCredentials` items
|
||||
|
||||
#### 3. `packages/lib/server-only/auth/create-passkey-signin-options.ts`
|
||||
- No changes needed (already using correct options)
|
||||
|
||||
#### 4. `packages/lib/server-only/auth/create-passkey.ts`
|
||||
- Change import from `@simplewebauthn/types` to `@simplewebauthn/server`
|
||||
- Update to use new `registrationInfo.credential` structure:
|
||||
- `credentialID` → `credential.id`
|
||||
- `credentialPublicKey` → `credential.publicKey`
|
||||
- `counter` → `credential.counter`
|
||||
- Note: `credential.id` is now a `Base64URLString`, so `Buffer.from(credentialID)` needs updating
|
||||
|
||||
#### 5. `packages/lib/server-only/document/is-recipient-authorized.ts`
|
||||
- Update `verifyAuthenticationResponse()` to use `credential` instead of `authenticator`:
|
||||
- Change `authenticator: { credentialID, credentialPublicKey, counter }` to `credential: { id, publicKey, counter }`
|
||||
- Since `credential.id` is now base64url string, convert stored `credentialId` buffer to base64url
|
||||
|
||||
#### 6. `packages/auth/server/routes/passkey.ts`
|
||||
- Update `verifyAuthenticationResponse()` to use `credential` instead of `authenticator`
|
||||
- Same changes as `is-recipient-authorized.ts`
|
||||
|
||||
#### 7. `packages/trpc/server/auth-router/create-passkey.ts`
|
||||
- Change import from `@simplewebauthn/types` to `@simplewebauthn/server`
|
||||
|
||||
### Browser-side Files
|
||||
|
||||
#### 8. `apps/remix/app/components/dialogs/passkey-create-dialog.tsx`
|
||||
- Update `startRegistration()` call:
|
||||
- Before: `startRegistration(passkeyRegistrationOptions)`
|
||||
- After: `startRegistration({ optionsJSON: passkeyRegistrationOptions })`
|
||||
|
||||
#### 9. `apps/remix/app/components/forms/signin.tsx`
|
||||
- Update `startAuthentication()` call:
|
||||
- Before: `startAuthentication(options)`
|
||||
- After: `startAuthentication({ optionsJSON: options })`
|
||||
|
||||
#### 10. `apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx`
|
||||
- Update `startAuthentication()` call:
|
||||
- Before: `startAuthentication(options)`
|
||||
- After: `startAuthentication({ optionsJSON: options })`
|
||||
|
||||
### Database/Schema Considerations
|
||||
|
||||
The database stores `credentialId` as `Bytes`. The new API returns `credential.id` as `Base64URLString`. We need to:
|
||||
1. When **storing** a new passkey: Convert from `Base64URLString` to `Buffer`
|
||||
2. When **passing to verification**: Convert from `Buffer` to `Base64URLString`
|
||||
|
||||
Use `isoBase64URL` helper from `@simplewebauthn/server/helpers` for these conversions.
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Update package.json dependencies
|
||||
```bash
|
||||
npm uninstall @simplewebauthn/types
|
||||
npm install @simplewebauthn/browser@^13.2.2 @simplewebauthn/server@^13.2.2
|
||||
```
|
||||
|
||||
### Step 2: Update type imports
|
||||
Replace all `@simplewebauthn/types` imports with `@simplewebauthn/server`
|
||||
|
||||
### Step 3: Update browser-side API calls
|
||||
- `startRegistration(options)` → `startRegistration({ optionsJSON: options })`
|
||||
- `startAuthentication(options)` → `startAuthentication({ optionsJSON: options })`
|
||||
|
||||
### Step 4: Update server-side registration
|
||||
- Update `excludeCredentials` format (remove `type: 'public-key'`)
|
||||
- Update `userID` encoding if needed
|
||||
- Update `verifyRegistrationResponse()` result handling for new `credential` structure
|
||||
|
||||
### Step 5: Update server-side authentication
|
||||
- Update `allowCredentials` format (remove `type: 'public-key'`)
|
||||
- Update `verifyAuthenticationResponse()` to use `credential` instead of `authenticator`
|
||||
- Handle `Base64URLString` for `credential.id`
|
||||
|
||||
### Step 6: Update credential storage/retrieval
|
||||
- When storing: Convert `Base64URLString` to `Buffer`
|
||||
- When reading: Convert `Buffer` to `Base64URLString`
|
||||
|
||||
### Step 7: Test passkey flows
|
||||
1. Test passkey creation
|
||||
2. Test passkey sign-in
|
||||
3. Test passkey authentication for document signing
|
||||
4. Test passkey deletion
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Converting stored Buffer to Base64URLString for verification
|
||||
```typescript
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
|
||||
// When reading from database (Buffer) and passing to verification
|
||||
const credential = {
|
||||
id: isoBase64URL.fromBuffer(passkey.credentialId),
|
||||
publicKey: new Uint8Array(passkey.credentialPublicKey),
|
||||
counter: Number(passkey.counter),
|
||||
transports: passkey.transports,
|
||||
};
|
||||
```
|
||||
|
||||
### Converting Base64URLString to Buffer for storage
|
||||
```typescript
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
|
||||
// When storing from registration response
|
||||
const credentialIdBuffer = Buffer.from(
|
||||
isoBase64URL.toBuffer(registrationInfo.credential.id)
|
||||
);
|
||||
```
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
1. **Database compatibility**: The `credentialId` is stored as `Bytes` in the database. The new API uses `Base64URLString`. We need proper conversion functions.
|
||||
- **Mitigation**: Use `isoBase64URL.fromBuffer()` and `isoBase64URL.toBuffer()` for conversions
|
||||
|
||||
2. **Existing passkeys**: Existing passkeys should continue to work as long as conversion is done correctly.
|
||||
- **Mitigation**: Test with existing passkeys after upgrade
|
||||
|
||||
3. **Browser compatibility**: v10+ requires newer browser APIs.
|
||||
- **Mitigation**: `browserSupportsWebAuthn()` already handles this check
|
||||
@@ -1,75 +0,0 @@
|
||||
You are an expert in TypeScript, Node.js, Remix, React, Shadcn UI and Tailwind.
|
||||
|
||||
Code Style and Structure:
|
||||
|
||||
- Write concise, technical TypeScript code with accurate examples
|
||||
- Use functional and declarative programming patterns; avoid classes
|
||||
- Prefer iteration and modularization over code duplication
|
||||
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
|
||||
- Structure files: exported component, subcomponents, helpers, static content, types
|
||||
|
||||
Naming Conventions:
|
||||
|
||||
- Use lowercase with dashes for directories (e.g., components/auth-wizard)
|
||||
- Favor named exports for components
|
||||
|
||||
TypeScript Usage:
|
||||
|
||||
- Use TypeScript for all code; prefer types over interfaces
|
||||
- Use functional components with TypeScript interfaces
|
||||
|
||||
Syntax and Formatting:
|
||||
|
||||
- Create functions using `const fn = () => {}`
|
||||
- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
|
||||
- Use declarative JSX
|
||||
- Never use 'use client'
|
||||
- Never use 1 line if statements
|
||||
|
||||
Error Handling and Validation:
|
||||
|
||||
- Prioritize error handling: handle errors and edge cases early
|
||||
- Use early returns and guard clauses
|
||||
- Implement proper error logging and user-friendly messages
|
||||
- Use Zod for form validation
|
||||
- Model expected errors as return values in Server Actions
|
||||
- Use error boundaries for unexpected errors
|
||||
|
||||
UI and Styling:
|
||||
|
||||
- Use Shadcn UI, Radix, and Tailwind Aria for components and styling
|
||||
- Implement responsive design with Tailwind CSS; use a mobile-first approach
|
||||
- When using Lucide icons, prefer the longhand names, for example HomeIcon instead of Home
|
||||
|
||||
React forms
|
||||
|
||||
- Use zod for form validation react-hook-form for forms
|
||||
- Look at TeamCreateDialog.tsx as an example of form usage
|
||||
- Use <Form> <FormItem> elements, and also wrap the contents of form in a fieldset which should have the :disabled attribute when the form is loading
|
||||
|
||||
TRPC Specifics
|
||||
|
||||
- Every route should be in it's own file, example routers/teams/create-team.ts
|
||||
- Every route should have a types file associated with it, example routers/teams/create-team.types.ts. These files should have the OpenAPI meta, and request/response zod schemas
|
||||
- The request/response schemas should be named like Z[RouteName]RequestSchema and Z[RouteName]ResponseSchema
|
||||
- Use create-team.ts and create-team.types.ts as an example when creating new routes.
|
||||
- When creating the OpenAPI meta, only use GET and POST requests, do not use any other REST methods
|
||||
- Deconstruct the input argument on it's one line of code.
|
||||
|
||||
Toast usage
|
||||
|
||||
- Use the t`string` macro from @lingui/react/macro to display toast messages
|
||||
|
||||
Remix/ReactRouter Usage
|
||||
|
||||
- Use (params: Route.Params) to get the params from the route
|
||||
- Use (loaderData: Route.LoaderData) to get the loader data from the route
|
||||
- When using loaderdata, deconstruct the data you need from the loader data inside the function body
|
||||
- Do not use json() to return data, directly return the data
|
||||
|
||||
Translations
|
||||
|
||||
- Use <Trans>string</Trans> to display translations in jsx code, this should be imported from @lingui/react/macro
|
||||
- Use the t`string` macro from @lingui/react/macro to display translations in typescript code
|
||||
- t should be imported as const { t } = useLingui() where useLingui is imported from @lingui/react/macro
|
||||
- String in constants should be using the t`string` macro
|
||||
@@ -25,7 +25,7 @@
|
||||
"GitHub.copilot",
|
||||
"GitHub.vscode-pull-request-github",
|
||||
"Prisma.prisma",
|
||||
"VisualStudioExptTeam.vscodeintellicode"
|
||||
"VisualStudioExptTeam.vscodeintellicode",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Start the database and mailserver
|
||||
docker compose -f ./docker/compose-without-app.yml up -d
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Copy the env file
|
||||
cp .env.example .env
|
||||
|
||||
# Run the dev setup
|
||||
npm run dx
|
||||
# Run the migrations
|
||||
npm run prisma:migrate-dev
|
||||
|
||||
+21
-116
@@ -1,96 +1,38 @@
|
||||
# The license key to enable enterprise features for self hosters
|
||||
NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY=
|
||||
|
||||
# [[AUTH]]
|
||||
NEXTAUTH_URL="http://localhost:3000"
|
||||
NEXTAUTH_SECRET="secret"
|
||||
|
||||
# [[CRYPTO]]
|
||||
# Application Key for symmetric encryption and decryption
|
||||
# REQUIRED: This should be a random string of at least 32 characters
|
||||
NEXT_PRIVATE_ENCRYPTION_KEY="CAFEBABE"
|
||||
# REQUIRED: This should be a random string of at least 32 characters
|
||||
NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY="DEADBEEF"
|
||||
|
||||
# [[AUTH OPTIONAL]]
|
||||
# Find documentation on setting up Google OAuth here:
|
||||
# https://docs.documenso.com/developers/self-hosting/setting-up-oauth-providers#google-oauth-gmail
|
||||
NEXT_PRIVATE_GOOGLE_CLIENT_ID=""
|
||||
NEXT_PRIVATE_GOOGLE_CLIENT_SECRET=""
|
||||
# Find documentation on setting up Microsoft OAuth here:
|
||||
# https://docs.documenso.com/developers/self-hosting/setting-up-oauth-providers#microsoft-oauth-azure-ad
|
||||
NEXT_PRIVATE_MICROSOFT_CLIENT_ID=""
|
||||
NEXT_PRIVATE_MICROSOFT_CLIENT_SECRET=""
|
||||
|
||||
NEXT_PRIVATE_OIDC_WELL_KNOWN=""
|
||||
NEXT_PRIVATE_OIDC_CLIENT_ID=""
|
||||
NEXT_PRIVATE_OIDC_CLIENT_SECRET=""
|
||||
NEXT_PRIVATE_OIDC_PROVIDER_LABEL="OIDC"
|
||||
NEXT_PRIVATE_OIDC_SKIP_VERIFY=""
|
||||
# Specifies the prompt to use for OIDC signin, explicitly setting
|
||||
# an empty string will omit the prompt parameter.
|
||||
# See: https://www.cerberauth.com/blog/openid-connect-oauth2-prompts/
|
||||
NEXT_PRIVATE_OIDC_PROMPT="login"
|
||||
|
||||
# [[URLS]]
|
||||
NEXT_PUBLIC_WEBAPP_URL="http://localhost:3000"
|
||||
# URL used by the web app to request itself (e.g. local background jobs)
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL="http://localhost:3000"
|
||||
|
||||
# [[SERVER]]
|
||||
# OPTIONAL: The port the server will listen on. Defaults to 3000.
|
||||
PORT=3000
|
||||
NEXT_PUBLIC_MARKETING_URL="http://localhost:3001"
|
||||
|
||||
# [[DATABASE]]
|
||||
NEXT_PRIVATE_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documenso"
|
||||
# Defines the URL to use for the database when running migrations and other commands that won't work with a connection pool.
|
||||
NEXT_PRIVATE_DIRECT_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documenso"
|
||||
|
||||
# [[SIGNING]]
|
||||
# The transport to use for document signing. Available options: local (default) | gcloud-hsm
|
||||
NEXT_PRIVATE_SIGNING_TRANSPORT="local"
|
||||
# OPTIONAL: The passphrase to use for the local file-based signing transport.
|
||||
NEXT_PRIVATE_SIGNING_PASSPHRASE=
|
||||
# OPTIONAL: The local file path to the .p12 file to use for the local signing transport.
|
||||
NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=
|
||||
# OPTIONAL: The base64-encoded contents of the .p12 file to use for the local signing transport.
|
||||
NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS=
|
||||
# OPTIONAL: The path to the Google Cloud HSM key to use for the gcloud-hsm signing transport.
|
||||
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH=
|
||||
# OPTIONAL: The path to the Google Cloud HSM public certificate file to use for the gcloud-hsm signing transport.
|
||||
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH=
|
||||
# OPTIONAL: The base64-encoded contents of the Google Cloud HSM public certificate file to use for the gcloud-hsm signing transport.
|
||||
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS=
|
||||
# OPTIONAL: The path to the Google Cloud Credentials file to use for the gcloud-hsm signing transport.
|
||||
NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS=
|
||||
# OPTIONAL: The path to the certificate chain file for the gcloud-hsm signing transport.
|
||||
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_FILE_PATH=
|
||||
# OPTIONAL: The base64-encoded contents of the certificate chain for the gcloud-hsm signing transport.
|
||||
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_CONTENTS=
|
||||
# OPTIONAL: The Google Secret Manager path to retrieve the certificate for the gcloud-hsm signing transport.
|
||||
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_SECRET_MANAGER_CERT_PATH=
|
||||
# OPTIONAL: Comma-separated list of timestamp authority URLs for PDF signing (enables LTV and archival timestamps).
|
||||
NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY=
|
||||
# OPTIONAL: Contact info to embed in PDF signatures. Defaults to the webapp URL.
|
||||
NEXT_PUBLIC_SIGNING_CONTACT_INFO=
|
||||
# OPTIONAL: Set to "true" to use the legacy adbe.pkcs7.detached subfilter instead of ETSI.CAdES.detached.
|
||||
NEXT_PRIVATE_USE_LEGACY_SIGNING_SUBFILTER=
|
||||
# [[E2E Tests]]
|
||||
E2E_TEST_AUTHENTICATE_USERNAME="Test User"
|
||||
E2E_TEST_AUTHENTICATE_USER_EMAIL="testuser@mail.com"
|
||||
E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_password"
|
||||
|
||||
# [[STORAGE]]
|
||||
# OPTIONAL: Defines the storage transport to use. Available options: database (default) | s3
|
||||
NEXT_PUBLIC_UPLOAD_TRANSPORT="database"
|
||||
# OPTIONAL: Defines the endpoint to use for the S3 storage transport. Relevant when using third-party S3-compatible providers.
|
||||
NEXT_PRIVATE_UPLOAD_ENDPOINT="http://127.0.0.1:9002"
|
||||
# OPTIONAL: Defines the force path style to use for the S3 storage transport. Relevant when using third-party S3-compatible providers.
|
||||
# This will change it from using virtual hosts <bucket>.domain.com/<path> to fully qualified paths domain.com/<bucket>/<path>
|
||||
NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE="false"
|
||||
NEXT_PRIVATE_UPLOAD_ENDPOINT=
|
||||
# OPTIONAL: Defines the region to use for the S3 storage transport. Defaults to us-east-1.
|
||||
NEXT_PRIVATE_UPLOAD_REGION="unknown"
|
||||
NEXT_PRIVATE_UPLOAD_REGION=
|
||||
# REQUIRED: Defines the bucket to use for the S3 storage transport.
|
||||
NEXT_PRIVATE_UPLOAD_BUCKET="documenso"
|
||||
NEXT_PRIVATE_UPLOAD_BUCKET=
|
||||
# OPTIONAL: Defines the access key ID to use for the S3 storage transport.
|
||||
NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID="documenso"
|
||||
NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID=
|
||||
# OPTIONAL: Defines the secret access key to use for the S3 storage transport.
|
||||
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY="password"
|
||||
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY=
|
||||
|
||||
# [[SMTP]]
|
||||
# OPTIONAL: Defines the transport to use for sending emails. Available options: smtp-auth (default) | smtp-api | mailchannels
|
||||
@@ -109,14 +51,10 @@ NEXT_PRIVATE_SMTP_APIKEY_USER=
|
||||
NEXT_PRIVATE_SMTP_APIKEY=
|
||||
# OPTIONAL: Defines whether to force the use of TLS.
|
||||
NEXT_PRIVATE_SMTP_SECURE=
|
||||
# OPTIONAL: if this is true and NEXT_PRIVATE_SMTP_SECURE is false then TLS is not used even if the server supports STARTTLS extension
|
||||
NEXT_PRIVATE_SMTP_UNSAFE_IGNORE_TLS=
|
||||
# REQUIRED: Defines the sender name to use for the from address.
|
||||
NEXT_PRIVATE_SMTP_FROM_NAME="Documenso"
|
||||
NEXT_PRIVATE_SMTP_FROM_NAME="No Reply @ Documenso"
|
||||
# REQUIRED: Defines the email address to use as the from address.
|
||||
NEXT_PRIVATE_SMTP_FROM_ADDRESS="noreply@documenso.com"
|
||||
# OPTIONAL: Defines the service for nodemailer
|
||||
NEXT_PRIVATE_SMTP_SERVICE=
|
||||
# OPTIONAL: The API key to use for Resend.com
|
||||
NEXT_PRIVATE_RESEND_API_KEY=
|
||||
# OPTIONAL: The API key to use for MailChannels.
|
||||
@@ -129,56 +67,23 @@ NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN=
|
||||
NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR=
|
||||
# OPTIONAL: The private key to use for DKIM signing.
|
||||
NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY=
|
||||
# OPTIONAL: Displays the maximum document upload limit to the user in MBs
|
||||
NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT=5
|
||||
|
||||
# [[EE ONLY]]
|
||||
# OPTIONAL: The AWS SES API KEY to verify email domains with.
|
||||
NEXT_PRIVATE_SES_ACCESS_KEY_ID=
|
||||
NEXT_PRIVATE_SES_SECRET_ACCESS_KEY=
|
||||
NEXT_PRIVATE_SES_REGION=
|
||||
|
||||
# [[STRIPE]]
|
||||
NEXT_PRIVATE_STRIPE_API_KEY=
|
||||
NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET=
|
||||
|
||||
# [[BACKGROUND JOBS]]
|
||||
NEXT_PRIVATE_JOBS_PROVIDER="local"
|
||||
NEXT_PRIVATE_INNGEST_EVENT_KEY=
|
||||
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID=
|
||||
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID=
|
||||
NEXT_PUBLIC_STRIPE_FREE_PLAN_ID=
|
||||
|
||||
# [[FEATURES]]
|
||||
# OPTIONAL: Leave blank to disable PostHog and feature flags.
|
||||
NEXT_PUBLIC_POSTHOG_KEY=""
|
||||
# OPTIONAL: Defines the host to use for PostHog.
|
||||
NEXT_PUBLIC_POSTHOG_HOST="https://eu.posthog.com"
|
||||
# OPTIONAL: Leave blank to disable billing.
|
||||
NEXT_PUBLIC_FEATURE_BILLING_ENABLED=
|
||||
# OPTIONAL: Leave blank to allow users to signup through /signup page.
|
||||
NEXT_PUBLIC_DISABLE_SIGNUP=
|
||||
# OPTIONAL: Set to true to use internal webapp url in browserless requests.
|
||||
NEXT_PUBLIC_USE_INTERNAL_URL_BROWSERLESS=false
|
||||
|
||||
# [[TELEMETRY]]
|
||||
# OPTIONAL: Set to "true" to disable anonymous telemetry for self-hosted instances.
|
||||
# Telemetry helps us understand how Documenso is being used and improve the product.
|
||||
# We only collect: app version, installation ID, and node ID. No personal data is collected.
|
||||
DOCUMENSO_DISABLE_TELEMETRY=
|
||||
|
||||
# [[AI]]
|
||||
# OPTIONAL: Google Cloud Project ID for Vertex AI.
|
||||
GOOGLE_VERTEX_PROJECT_ID=""
|
||||
# OPTIONAL: Google Cloud region for Vertex AI. Defaults to "global".
|
||||
GOOGLE_VERTEX_LOCATION="global"
|
||||
# OPTIONAL: API key for Google Vertex AI (Gemini). Get your key from:
|
||||
# https://console.cloud.google.com/vertex-ai/studio/settings/api-keys
|
||||
GOOGLE_VERTEX_API_KEY=""
|
||||
|
||||
# [[E2E Tests]]
|
||||
E2E_TEST_AUTHENTICATE_USERNAME="Test User"
|
||||
E2E_TEST_AUTHENTICATE_USER_EMAIL="testuser@mail.com"
|
||||
E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_Password123"
|
||||
|
||||
# [[LOGGER]]
|
||||
# OPTIONAL: The file to save the logger output to. Will disable stdout if provided.
|
||||
NEXT_PRIVATE_LOGGER_FILE_PATH=
|
||||
|
||||
# [[PLAIN SUPPORT]]
|
||||
NEXT_PRIVATE_PLAIN_API_KEY=
|
||||
# This is only required for the marketing site
|
||||
# [[REDIS]]
|
||||
NEXT_PRIVATE_REDIS_URL=
|
||||
NEXT_PRIVATE_REDIS_TOKEN=
|
||||
|
||||
@@ -4,13 +4,10 @@ module.exports = {
|
||||
extends: ['@documenso/eslint-config'],
|
||||
rules: {
|
||||
'@next/next/no-img-element': 'off',
|
||||
'no-unreachable': 'error',
|
||||
'react-hooks/exhaustive-deps': 'off',
|
||||
},
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: ['apps/*/'],
|
||||
},
|
||||
},
|
||||
ignorePatterns: ['lingui.config.ts', 'packages/lib/translations/**/*.js'],
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
name: 'Bug Report'
|
||||
labels: ['bug']
|
||||
name: "Bug Report"
|
||||
labels: ["bug"]
|
||||
description: Create a bug report to help us improve
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thank you for reporting an issue.
|
||||
value:
|
||||
Thank you for reporting an issue.
|
||||
Please fill in as much of the form below as you're able to.
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
name: 'Feature Request'
|
||||
name: "Feature Request"
|
||||
description: Suggest a new idea or enhancement for this project
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Please provide a clear and concise title for your feature request
|
||||
value: Please provide a clear and concise title for your feature request
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Feature Description
|
||||
@@ -32,5 +32,4 @@ body:
|
||||
- label: I have provided a detailed description of the requested feature.
|
||||
- label: I have explained the use case or scenario for this feature.
|
||||
- label: I have included any relevant technical details or design suggestions.
|
||||
- label: I understand that this is a suggestion and that there is no guarantee of implementation.
|
||||
- label: I want to work on creating a PR for this issue if approved
|
||||
- label: I understand that this is a suggestion and that there is no guarantee of implementation.
|
||||
@@ -1,39 +1,35 @@
|
||||
name: 'General Improvement Request'
|
||||
description: 'Suggest a minor enhancement or improvement for this project'
|
||||
title: '[Title for your improvement suggestion]'
|
||||
name: "General Improvement"
|
||||
description: Suggest a minor enhancement or improvement for this project
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Please provide a clear and concise title for your improvement suggestion
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 'Describe the improvement you are suggesting in detail'
|
||||
description: 'Explain why this improvement would be beneficial. Share any context, pain points, or reasons for suggesting this change.'
|
||||
validations:
|
||||
required: true
|
||||
label: Improvement Description
|
||||
description: Describe the improvement you are suggesting in detail. Explain what specific aspect of the project it addresses or enhances.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 'Additional Information & Alternatives (optional)'
|
||||
description: 'Are there any additional context or information that might be relevant to the improvement suggestion.'
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: assignee
|
||||
label: Rationale
|
||||
description: Explain why this improvement would be beneficial. Share any context, pain points, or reasons for suggesting this change.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 'Do you want to work on this improvement?'
|
||||
multiple: false
|
||||
options:
|
||||
- 'No'
|
||||
- 'Yes'
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
label: Proposed Solution
|
||||
description: If you have a suggestion for how this improvement could be implemented, describe it here. Include any technical details, design suggestions, or other relevant information.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Alternatives (optional)
|
||||
description: Are there any alternative approaches to achieve the same improvement? Describe other ways to address the issue or enhance the project.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any additional context or information that might be relevant to the improvement suggestion.
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 'Please check the boxes that apply to this improvement suggestion.'
|
||||
label: Please check the boxes that apply to this improvement suggestion.
|
||||
options:
|
||||
- label: 'I have searched the existing issues and improvement suggestions to avoid duplication.'
|
||||
- label: 'I have provided a clear description of the improvement being suggested.'
|
||||
- label: 'I have explained the rationale behind this improvement.'
|
||||
- label: 'I have included any relevant technical details or design suggestions.'
|
||||
- label: 'I understand that this is a suggestion and that there is no guarantee of implementation.'
|
||||
validations:
|
||||
required: true
|
||||
- label: I have searched the existing issues and improvement suggestions to avoid duplication.
|
||||
- label: I have provided a clear description of the improvement being suggested.
|
||||
- label: I have explained the rationale behind this improvement.
|
||||
- label: I have included any relevant technical details or design suggestions.
|
||||
- label: I understand that this is a suggestion and that there is no guarantee of implementation.
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
name: Pull Request
|
||||
about: Submit changes to the project for review and inclusion
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!--- Describe the changes introduced by this pull request. -->
|
||||
@@ -1,39 +0,0 @@
|
||||
name: 'Setup node and cache node_modules'
|
||||
inputs:
|
||||
node_version:
|
||||
required: false
|
||||
default: v22.x
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Set up Node ${{ inputs.node_version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ inputs.node_version }}
|
||||
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: npm-
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v3
|
||||
id: cache-node-modules
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
packages/*/node_modules
|
||||
apps/*/node_modules
|
||||
key: modules-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
npm ci --no-audit
|
||||
npm run prisma:generate
|
||||
env:
|
||||
HUSKY: '0'
|
||||
@@ -1,19 +0,0 @@
|
||||
name: Install playwright binaries
|
||||
description: 'Install playwright, cache and restore if necessary'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Cache playwright
|
||||
id: cache-playwright
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
${{ github.workspace }}/node_modules/playwright
|
||||
key: playwright-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: playwright-
|
||||
|
||||
- name: Install playwright
|
||||
if: steps.cache-playwright.outputs.cache-hit != 'true'
|
||||
run: npx playwright install --with-deps
|
||||
shell: bash
|
||||
+20
-10
@@ -4,19 +4,29 @@ updates:
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
target-branch: 'main'
|
||||
interval: "weekly"
|
||||
target-branch: "main"
|
||||
labels:
|
||||
- 'ci dependencies'
|
||||
- 'ci'
|
||||
- "ci dependencies"
|
||||
- "ci"
|
||||
open-pull-requests-limit: 0
|
||||
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/apps/web'
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/apps/marketing"
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
target-branch: 'main'
|
||||
interval: "weekly"
|
||||
target-branch: "main"
|
||||
labels:
|
||||
- 'npm dependencies'
|
||||
- 'frontend'
|
||||
- "npm dependencies"
|
||||
- "frontend"
|
||||
open-pull-requests-limit: 0
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/apps/web"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
target-branch: "main"
|
||||
labels:
|
||||
- "npm dependencies"
|
||||
- "frontend"
|
||||
open-pull-requests-limit: 0
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
'apps: web':
|
||||
- apps/web/**
|
||||
|
||||
'version bump 👀':
|
||||
- '**/package.json'
|
||||
- '**/package-lock.json'
|
||||
|
||||
'🚨 migrations 🚨':
|
||||
- packages/prisma/migrations/**/migration.sql
|
||||
|
||||
'🚨 e2e changes 🚨':
|
||||
- packages/app-tests/e2e/**
|
||||
|
||||
'🚨 .env changes 🚨':
|
||||
- .env.example
|
||||
|
||||
'pkg: ee changes':
|
||||
- packages/ee/**
|
||||
+18
-47
@@ -1,68 +1,39 @@
|
||||
name: 'Continuous Integration'
|
||||
name: "Continuous Integration"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
push:
|
||||
branches: ['main']
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: ['main']
|
||||
branches: [ "main" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
HUSKY: 0
|
||||
|
||||
jobs:
|
||||
build_app:
|
||||
name: Build App
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- uses: ./.github/actions/node-install
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Copy env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Build app
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
build_docker:
|
||||
name: Build Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
push: false
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
tags: documenso-${{ github.sha }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
||||
|
||||
- # Temp fix
|
||||
# https://github.com/docker/build-push-action/issues/252
|
||||
# https://github.com/moby/buildkit/issues/1896
|
||||
name: Move cache
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
name: 'CodeQL'
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: ['main']
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: ['main']
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
@@ -19,24 +19,30 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
language: ['javascript']
|
||||
language: [ 'javascript' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Copy env
|
||||
run: cp .env.example .env
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: npm
|
||||
|
||||
- uses: ./.github/actions/node-install
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build app
|
||||
run: npm run build
|
||||
- name: Copy env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Build Documenso
|
||||
run: npm run build
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
name: Deploy to Production
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Push to release branch
|
||||
run: |
|
||||
git checkout release || git checkout -b release
|
||||
git merge --ff-only main
|
||||
git push origin release
|
||||
@@ -1,53 +1,51 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: ['main']
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
branches: [ "main" ]
|
||||
jobs:
|
||||
e2e_tests:
|
||||
name: 'E2E Tests'
|
||||
timeout-minutes: 60
|
||||
runs-on: warp-ubuntu-2204-x64-8x
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Copy env
|
||||
run: cp .env.example .env
|
||||
|
||||
- uses: ./.github/actions/node-install
|
||||
|
||||
- name: Start Services
|
||||
run: npm run dx:up
|
||||
|
||||
- uses: ./.github/actions/playwright-install
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
- name: Generate Prisma Client
|
||||
run: npm run prisma:generate -w @documenso/prisma
|
||||
- name: Create the database
|
||||
run: npm run prisma:migrate-dev
|
||||
|
||||
- name: Seed the database
|
||||
run: npm run prisma:seed
|
||||
|
||||
- name: Install playwright browsers
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: npm run ci
|
||||
env:
|
||||
# Needed since we use next start which will set the NODE_ENV to production
|
||||
NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH: './example/cert.p12'
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: test-results
|
||||
path: 'packages/app-tests/**/test-results/*'
|
||||
retention-days: 7
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
env:
|
||||
NEXT_PRIVATE_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/documenso
|
||||
NEXT_PRIVATE_DIRECT_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/documenso
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
name: 'Welcome New Contributors'
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: ['opened']
|
||||
issues:
|
||||
types: ['opened']
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
welcome-message:
|
||||
name: Welcome Contributors
|
||||
if: github.event.action == 'opened'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pr-message: |
|
||||
Thank you for creating your first Pull Request and for being a part of the open signing revolution! 💚🚀
|
||||
<br /> Feel free to hop into our community in [Discord](https://documen.so/discord)
|
||||
issue-message: |
|
||||
Thank you for opening your first issue and for being a part of the open signing revolution!
|
||||
<br /> One of our team members will review it and get back to you as soon as it possible 💚
|
||||
<br /> Meanwhile, please feel free to hop into our community in [Discord](https://documen.so/discord)
|
||||
@@ -1,63 +0,0 @@
|
||||
name: 'Issue Assignee Check'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: ['assigned']
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
countIssues:
|
||||
if: ${{ github.event.issue.assignee }} && github.event.action == 'assigned' && github.event.sender.type == 'User'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: npm
|
||||
|
||||
- name: Install Octokit
|
||||
run: npm install @octokit/rest@18
|
||||
|
||||
- name: Check Assigned User's Issue Count
|
||||
id: parse-comment
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { Octokit } = require("@octokit/rest");
|
||||
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
||||
|
||||
const username = context.payload.issue.assignee.login;
|
||||
console.log(`Username Extracted: ${username}`);
|
||||
|
||||
const { data: issues } = await octokit.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
assignee: username,
|
||||
state: 'open'
|
||||
});
|
||||
|
||||
const issueCount = issues.length;
|
||||
console.log(`Issue Count For ${username}: ${issueCount}`);
|
||||
|
||||
if (issueCount > 3) {
|
||||
let issueCountMessage = `### 🚨 Documenso Police 🚨`;
|
||||
issueCountMessage += `\n@${username} has ${issueCount} open issues assigned already. Consider whether this issue should be assigned to them or left open for another contributor.`;
|
||||
|
||||
await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: issueCountMessage,
|
||||
headers: {
|
||||
'Authorization': `token ${{ secrets.GITHUB_TOKEN }}`,
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
name: Auto Label Assigned Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [assigned]
|
||||
|
||||
jobs:
|
||||
label-when-assigned:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Label issue
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const issue = context.issue;
|
||||
// To run only on issues and not on PR
|
||||
if (github.context.payload.issue.pull_request === undefined) {
|
||||
const labelResponse = await github.rest.issues.addLabels({
|
||||
owner: issue.owner,
|
||||
repo: issue.repo,
|
||||
issue_number: issue.number,
|
||||
labels: ['status: assigned']
|
||||
});
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
name: 'Label Issues'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: ['opened', 'reopened']
|
||||
|
||||
jobs:
|
||||
label_issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ["status: triage"]
|
||||
})
|
||||
@@ -1,20 +0,0 @@
|
||||
name: 'PR Labeler'
|
||||
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v4
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
sync-labels: ''
|
||||
@@ -1,64 +0,0 @@
|
||||
name: 'PR Review Reminder'
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: ['opened', 'ready_for_review']
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
checkPRs:
|
||||
if: ${{ github.event.pull_request.user.login }} && github.event.action == ('opened' || 'ready_for_review')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: npm
|
||||
|
||||
- name: Install Octokit
|
||||
run: npm install @octokit/rest@18
|
||||
|
||||
- name: Check user's PRs awaiting review
|
||||
id: parse-prs
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { Octokit } = require("@octokit/rest");
|
||||
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
||||
|
||||
const username = context.payload.pull_request.user.login;
|
||||
console.log(`Username Extracted: ${username}`);
|
||||
|
||||
const { data: pullRequests } = await octokit.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
sort: 'created',
|
||||
direction: 'asc',
|
||||
});
|
||||
|
||||
const userPullRequests = pullRequests.filter(pr => pr.user.login === username && (pr.state === 'open' || pr.state === 'ready_for_review'));
|
||||
const prCount = userPullRequests.length;
|
||||
console.log(`PR Count for ${username}: ${prCount}`);
|
||||
|
||||
if (prCount > 3) {
|
||||
let prReminderMessage = `🚨 @${username} has ${prCount} pull requests awaiting review. Please consider reviewing them when possible. 🚨`;
|
||||
|
||||
await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
body: prReminderMessage,
|
||||
headers: {
|
||||
'Authorization': `token ${{ secrets.GITHUB_TOKEN }}`,
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
name: Publish Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['release']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git tag to build and publish (e.g., v1.0.0)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build_and_publish_platform_containers:
|
||||
name: Build and publish platform containers
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- warp-ubuntu-latest-x64-4x
|
||||
- warp-ubuntu-latest-arm64-4x
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag || github.ref }}
|
||||
fetch-tags: true
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Build the docker image
|
||||
env:
|
||||
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
|
||||
NEXT_PRIVATE_TELEMETRY_KEY: ${{ secrets.NEXT_PRIVATE_TELEMETRY_KEY }}
|
||||
NEXT_PRIVATE_TELEMETRY_HOST: ${{ secrets.NEXT_PRIVATE_TELEMETRY_HOST }}
|
||||
APP_VERSION: ${{ inputs.tag || '' }}
|
||||
run: |
|
||||
if [ -z "$APP_VERSION" ]; then
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
fi
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
|
||||
docker build \
|
||||
-f ./docker/Dockerfile \
|
||||
--progress=plain \
|
||||
--build-arg NEXT_PRIVATE_TELEMETRY_KEY="${NEXT_PRIVATE_TELEMETRY_KEY:-}" \
|
||||
--build-arg NEXT_PRIVATE_TELEMETRY_HOST="${NEXT_PRIVATE_TELEMETRY_HOST:-}" \
|
||||
-t "documenso/documenso-$BUILD_PLATFORM:latest" \
|
||||
-t "documenso/documenso-$BUILD_PLATFORM:$GIT_SHA" \
|
||||
-t "documenso/documenso-$BUILD_PLATFORM:$APP_VERSION" \
|
||||
-t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:latest" \
|
||||
-t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$GIT_SHA" \
|
||||
-t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$APP_VERSION" \
|
||||
.
|
||||
|
||||
- name: Push the docker image to DockerHub
|
||||
run: docker push --all-tags "documenso/documenso-$BUILD_PLATFORM"
|
||||
env:
|
||||
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
|
||||
|
||||
- name: Push the docker image to GitHub Container Registry
|
||||
run: docker push --all-tags "ghcr.io/documenso/documenso-$BUILD_PLATFORM"
|
||||
env:
|
||||
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
|
||||
|
||||
create_and_publish_manifest:
|
||||
name: Create and publish manifest
|
||||
runs-on: ubuntu-latest
|
||||
needs: build_and_publish_platform_containers
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag || github.ref }}
|
||||
fetch-tags: true
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Create and push DockerHub manifest
|
||||
env:
|
||||
APP_VERSION: ${{ inputs.tag || '' }}
|
||||
run: |
|
||||
if [ -z "$APP_VERSION" ]; then
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
fi
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
|
||||
# Check if the version is stable (no rc or beta in the version)
|
||||
if [[ "$APP_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
docker manifest create \
|
||||
documenso/documenso:latest \
|
||||
--amend documenso/documenso-amd64:latest \
|
||||
--amend documenso/documenso-arm64:latest
|
||||
|
||||
docker manifest push documenso/documenso:latest
|
||||
fi
|
||||
|
||||
if [[ "$APP_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
|
||||
docker manifest create \
|
||||
documenso/documenso:rc \
|
||||
--amend documenso/documenso-amd64:rc \
|
||||
--amend documenso/documenso-arm64:rc
|
||||
|
||||
docker manifest push documenso/documenso:rc
|
||||
fi
|
||||
|
||||
docker manifest create \
|
||||
documenso/documenso:$GIT_SHA \
|
||||
--amend documenso/documenso-amd64:$GIT_SHA \
|
||||
--amend documenso/documenso-arm64:$GIT_SHA
|
||||
|
||||
docker manifest create \
|
||||
documenso/documenso:$APP_VERSION \
|
||||
--amend documenso/documenso-amd64:$APP_VERSION \
|
||||
--amend documenso/documenso-arm64:$APP_VERSION
|
||||
|
||||
docker manifest push documenso/documenso:$GIT_SHA
|
||||
docker manifest push documenso/documenso:$APP_VERSION
|
||||
|
||||
- name: Create and push Github Container Registry manifest
|
||||
env:
|
||||
APP_VERSION: ${{ inputs.tag || '' }}
|
||||
run: |
|
||||
if [ -z "$APP_VERSION" ]; then
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
fi
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
|
||||
# Check if the version is stable (no rc or beta in the version)
|
||||
if [[ "$APP_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
docker manifest create \
|
||||
ghcr.io/documenso/documenso:latest \
|
||||
--amend ghcr.io/documenso/documenso-amd64:latest \
|
||||
--amend ghcr.io/documenso/documenso-arm64:latest
|
||||
|
||||
docker manifest push ghcr.io/documenso/documenso:latest
|
||||
fi
|
||||
|
||||
if [[ "$APP_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
|
||||
docker manifest create \
|
||||
ghcr.io/documenso/documenso:rc \
|
||||
--amend ghcr.io/documenso/documenso-amd64:rc \
|
||||
--amend ghcr.io/documenso/documenso-arm64:rc
|
||||
|
||||
docker manifest push ghcr.io/documenso/documenso:rc
|
||||
fi
|
||||
|
||||
docker manifest create \
|
||||
ghcr.io/documenso/documenso:$GIT_SHA \
|
||||
--amend ghcr.io/documenso/documenso-amd64:$GIT_SHA \
|
||||
--amend ghcr.io/documenso/documenso-arm64:$GIT_SHA
|
||||
|
||||
docker manifest create \
|
||||
ghcr.io/documenso/documenso:$APP_VERSION \
|
||||
--amend ghcr.io/documenso/documenso-amd64:$APP_VERSION \
|
||||
--amend ghcr.io/documenso/documenso-arm64:$APP_VERSION
|
||||
|
||||
docker manifest push ghcr.io/documenso/documenso:$GIT_SHA
|
||||
docker manifest push ghcr.io/documenso/documenso:$APP_VERSION
|
||||
@@ -1,4 +1,4 @@
|
||||
name: 'Validate PR Name'
|
||||
name: "Validate PR Name"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
@@ -9,54 +9,13 @@ on:
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
validate-pr:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR creator's previous activity
|
||||
id: check_activity
|
||||
run: |
|
||||
CREATOR=$(curl -s "https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" | jq -r '.user.login')
|
||||
ACTIVITY=$(curl -s "https://api.github.com/search/commits?q=author:${CREATOR}+repo:${{ github.repository }}" | jq -r '.total_count')
|
||||
if [ "$ACTIVITY" -eq 0 ]; then
|
||||
echo "::set-output name=is_new::true"
|
||||
else
|
||||
echo "::set-output name=is_new::false"
|
||||
fi
|
||||
|
||||
- name: Count PRs created by user
|
||||
id: count_prs
|
||||
run: |
|
||||
CREATOR=$(curl -s "https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" | jq -r '.user.login')
|
||||
PR_COUNT=$(curl -s "https://api.github.com/search/issues?q=type:pr+is:open+author:${CREATOR}+repo:${{ github.repository }}" | jq -r '.total_count')
|
||||
echo "::set-output name=pr_count::$PR_COUNT"
|
||||
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
id: lint_pr_title
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: marocchino/sticky-pull-request-comment@v2
|
||||
if: always() && (steps.lint_pr_title.outputs.error_message != null)
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
message: |
|
||||
Hey There! and thank you for opening this pull request! 📝👋🏼
|
||||
|
||||
We require pull request titles to follow the [Conventional Commits Spec](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
|
||||
|
||||
Details:
|
||||
|
||||
```
|
||||
${{ steps.lint_pr_title.outputs.error_message }}
|
||||
```
|
||||
|
||||
- if: ${{ steps.lint_pr_title.outputs.error_message == null && steps.check_activity.outputs.is_new == 'false' && steps.count_prs.outputs.pr_count < 2}}
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
message: |
|
||||
Thank you for following the naming conventions for pull request titles! 💚🚀
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
name: 'Mark Stale Issues and PRs'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 */8 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-pr-stale: 90
|
||||
days-before-issue-stale: 90
|
||||
days-before-issue-close: 180
|
||||
stale-pr-message: 'This PR has not seen activitiy for a while. It will be closed in 30 days unless further activity is detected.'
|
||||
close-pr-message: 'This PR has been closed because of inactivity.'
|
||||
exempt-pr-labels: 'WIP,on-hold,needs review'
|
||||
exempt-issue-labels: 'WIP,on-hold,needs review,roadmap,assigned,needs triage'
|
||||
@@ -1,51 +0,0 @@
|
||||
# This is similar to the "Pull Translations" workflow, but without the conditional check to allow us to
|
||||
# forcefully pull down translations from Crowdin and create a PR regardless if all the translations are fulfilled.
|
||||
#
|
||||
# Intended to be used when we manually update translations in Crowdin UI and want to pull those down when
|
||||
# they already exist.
|
||||
|
||||
name: 'Force pull translations'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
pull_translations:
|
||||
name: Force pull translations
|
||||
runs-on: ubuntu-latest
|
||||
environment: Translations
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: ./.github/actions/node-install
|
||||
|
||||
- name: Pull translations from Crowdin
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
export_only_approved: false
|
||||
localization_branch_name: chore/translations
|
||||
commit_message: 'chore: add translations'
|
||||
pull_request_title: 'chore: add translations'
|
||||
|
||||
env:
|
||||
# A classic GitHub Personal Access Token with the 'repo' scope selected (the user should have write access to the repository).
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
||||
|
||||
# A numeric ID, found at https://crowdin.com/project/<projectName>/tools/api
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
|
||||
# Visit https://crowdin.com/settings#api-key to create this token
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
@@ -1,55 +0,0 @@
|
||||
# Pull down translations from Crowdin every two hours or when triggered manually.
|
||||
|
||||
name: 'Pull translations'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 */2 * * *' # Every two hours.
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
pull_translations:
|
||||
name: Pull translations
|
||||
runs-on: ubuntu-latest
|
||||
environment: Translations
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: ./.github/actions/node-install
|
||||
|
||||
- name: Compile translations
|
||||
id: compile_translations
|
||||
run: npm run translate:compile -- -- --strict
|
||||
continue-on-error: true
|
||||
|
||||
- name: Pull translations from Crowdin
|
||||
if: steps.compile_translations.outcome == 'failure'
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
export_only_approved: false
|
||||
localization_branch_name: chore/translations
|
||||
commit_message: 'chore: add translations'
|
||||
pull_request_title: 'chore: add translations'
|
||||
|
||||
env:
|
||||
# A classic GitHub Personal Access Token with the 'repo' scope selected (the user should have write access to the repository).
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
||||
|
||||
# A numeric ID, found at https://crowdin.com/project/<projectName>/tools/api
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
|
||||
# Visit https://crowdin.com/settings#api-key to create this token
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
@@ -1,97 +0,0 @@
|
||||
name: 'Extract and upload translations'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
push:
|
||||
branches: ['main']
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
extract_translations:
|
||||
name: Extract and upload translations
|
||||
runs-on: ubuntu-latest
|
||||
environment: Translations
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: ./.github/actions/node-install
|
||||
|
||||
- name: Extract translations
|
||||
run: npm run translate:extract
|
||||
|
||||
- name: Commit changes and push to reserved branch
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
BRANCH="chore/extract-translations"
|
||||
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@documenso.com'
|
||||
|
||||
git fetch origin
|
||||
|
||||
# Create branch locally (always reset to main)
|
||||
git checkout -B "$BRANCH" origin/main
|
||||
|
||||
# Stage translation output
|
||||
git add packages/lib/translations
|
||||
|
||||
# If no changes, exit early
|
||||
if git diff --staged --quiet; then
|
||||
echo "No translation changes found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Commit fresh snapshot
|
||||
git commit -m "chore: extract translations"
|
||||
|
||||
# Force push reserved branch
|
||||
git push origin "$BRANCH" --force
|
||||
|
||||
# Does a PR already exist?
|
||||
EXISTING_PR=$(gh pr list \
|
||||
--state open \
|
||||
--head "$BRANCH" \
|
||||
--json number \
|
||||
--jq '.[0].number // empty')
|
||||
|
||||
if [ -z "$EXISTING_PR" ]; then
|
||||
echo "No existing PR — creating new one."
|
||||
gh pr create \
|
||||
--title "chore: extract translations" \
|
||||
--body "Automated translation extraction" \
|
||||
--base main \
|
||||
--head "$BRANCH"
|
||||
else
|
||||
echo "PR #$EXISTING_PR already exists — not creating a new one."
|
||||
fi
|
||||
|
||||
- name: Compile translations
|
||||
id: compile_translations
|
||||
run: npm run translate:compile -- -- --strict
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload missing translations
|
||||
if: ${{ steps.compile_translations.outcome == 'failure' }}
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
download_translations: false
|
||||
localization_branch_name: chore/translations
|
||||
env:
|
||||
# A numeric ID, found at https://crowdin.com/project/<projectName>/tools/api
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
|
||||
# Visit https://crowdin.com/settings#api-key to create this token
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
-19
@@ -1,7 +1,5 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
packages/prisma/generated/types.ts
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
@@ -50,20 +48,3 @@ yarn-error.log*
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# logs
|
||||
logs.json
|
||||
|
||||
# claude
|
||||
.claude
|
||||
CLAUDE.md
|
||||
|
||||
# agents
|
||||
.specs
|
||||
|
||||
# scripts
|
||||
scripts/output*
|
||||
|
||||
# license
|
||||
.documenso-license.json
|
||||
.documenso-license-backup.json
|
||||
|
||||
+18
-2
@@ -4,7 +4,9 @@ tasks:
|
||||
npm run dx:up &&
|
||||
cp .env.example .env &&
|
||||
set -a; source .env &&
|
||||
export NEXTAUTH_URL="$(gp url 3000)" &&
|
||||
export NEXT_PUBLIC_WEBAPP_URL="$(gp url 3000)" &&
|
||||
export NEXT_PUBLIC_MARKETING_URL="$(gp url 3001)"
|
||||
command: npm run d
|
||||
|
||||
ports:
|
||||
@@ -23,10 +25,20 @@ ports:
|
||||
- port: 2500
|
||||
visibility: private
|
||||
onOpen: ignore
|
||||
- port: 54320
|
||||
visibility: private
|
||||
- port: 54320
|
||||
visibility: private
|
||||
onOpen: ignore
|
||||
|
||||
|
||||
github:
|
||||
prebuilds:
|
||||
master: true
|
||||
pullRequests: true
|
||||
pullRequestsFromForks: true
|
||||
addCheck: true
|
||||
addComment: true
|
||||
addBadge: true
|
||||
|
||||
vscode:
|
||||
extensions:
|
||||
- aaron-bond.better-comments
|
||||
@@ -35,5 +47,9 @@ vscode:
|
||||
- esbenp.prettier-vscode
|
||||
- mikestead.dotenv
|
||||
- unifiedjs.vscode-mdx
|
||||
- GitHub.copilot-chat
|
||||
- GitHub.copilot-labs
|
||||
- GitHub.copilot
|
||||
- GitHub.vscode-pull-request-github
|
||||
- Prisma.prisma
|
||||
- VisualStudioExptTeam.vscodeintellicode
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run commitlint -- $1
|
||||
|
||||
+2
-7
@@ -1,9 +1,4 @@
|
||||
SCRIPT_DIR="$(readlink -f "$(dirname "$0")")"
|
||||
MONOREPO_ROOT="$(readlink -f "$SCRIPT_DIR/../")"
|
||||
|
||||
echo "Copying .well-known/ contents"
|
||||
node "$MONOREPO_ROOT/scripts/copy-wellknown.cjs"
|
||||
|
||||
git add "$MONOREPO_ROOT/apps/remix/public/"
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
legacy-peer-deps = true
|
||||
prefer-dedupe = true
|
||||
auto-install-peers = true
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
---
|
||||
description: Add and commit changes using conventional commits
|
||||
allowed-tools: Bash, Read, Glob, Grep
|
||||
---
|
||||
|
||||
Create a git commit for the current changes using the Conventional Commits standard.
|
||||
|
||||
## Process
|
||||
|
||||
1. **Analyze the changes** by running:
|
||||
- `git status` to see all modified/untracked files
|
||||
- `git diff` to see unstaged changes
|
||||
- `git diff --staged` to see already-staged changes
|
||||
- `git log --oneline -5` to see recent commit style
|
||||
|
||||
2. **Stage appropriate files**:
|
||||
- Stage all related changes with `git add`
|
||||
- Do NOT stage files that appear to contain secrets (.env, credentials, API keys, tokens)
|
||||
- If you detect potential secrets, warn the user and skip those files
|
||||
|
||||
3. **Determine the commit type** based on the changes:
|
||||
- `feat`: New feature or capability
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation only
|
||||
- `style`: Formatting, whitespace (not CSS)
|
||||
- `refactor`: Code restructuring without behavior change
|
||||
- `perf`: Performance improvement
|
||||
- `test`: Adding or updating tests
|
||||
- `build`: Build system or dependencies
|
||||
- `ci`: CI/CD configuration
|
||||
- `chore`: Maintenance tasks, tooling, config
|
||||
|
||||
NOTE: Do not use a scope for commits
|
||||
|
||||
4. **Write the commit message**:
|
||||
- **Subject line**: `<type>: <description>`
|
||||
- Use imperative mood ("add" not "added")
|
||||
- Lowercase, no period at end
|
||||
- Max 50 characters if possible, 72 hard limit
|
||||
- **Body** (if needed): Explain _why_, not _what_
|
||||
- Wrap at 72 characters
|
||||
- Separate from subject with blank line
|
||||
|
||||
## Commit Format
|
||||
|
||||
```
|
||||
<type>[scope]: <subject>
|
||||
|
||||
[optional body explaining WHY this change was made]
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Simple change:
|
||||
|
||||
```
|
||||
fix: handle empty input in parser without throwing
|
||||
```
|
||||
|
||||
With body:
|
||||
|
||||
```
|
||||
feat: add streaming response support
|
||||
|
||||
Large responses were causing memory issues in production.
|
||||
Streaming allows processing chunks incrementally.
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- NEVER commit files that may contain secrets
|
||||
- NEVER use `git commit --amend` unless the user explicitly requests it
|
||||
- NEVER use `--no-verify` to skip hooks
|
||||
- If the pre-commit hook fails, fix the issues and create a NEW commit
|
||||
- If there are no changes to commit, inform the user and stop
|
||||
- Use a HEREDOC to pass the commit message to ensure proper formatting
|
||||
|
||||
## Execute
|
||||
|
||||
Run the git commands to analyze, stage, and commit the changes now.
|
||||
@@ -1,112 +0,0 @@
|
||||
---
|
||||
description: Continue implementing a spec from a previous session
|
||||
argument-hint: <spec-file-path>
|
||||
---
|
||||
|
||||
You are continuing implementation of a specification that was started in a previous session. Work autonomously until the feature is complete and tests pass.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Read the spec** at `$ARGUMENTS`
|
||||
2. **Read CODE_STYLE.md** for formatting conventions
|
||||
3. **Assess current state**:
|
||||
- Check git status for uncommitted changes
|
||||
- Run tests to see what's passing/failing (if E2E tests exist)
|
||||
- Review any existing implementation
|
||||
4. **Determine what remains** by comparing the spec to the current state
|
||||
5. **Plan remaining work** using TodoWrite
|
||||
6. **Continue implementing** until complete
|
||||
|
||||
## Assessing Current State
|
||||
|
||||
Run these commands to understand where the previous session left off:
|
||||
|
||||
```bash
|
||||
git status # See uncommitted changes
|
||||
git log --oneline -10 # See recent commits
|
||||
npm run typecheck -w @documenso/remix # Check for type errors
|
||||
npm run lint:fix # Check for linting issues
|
||||
```
|
||||
|
||||
Review the code that's already been written to understand:
|
||||
|
||||
- What's already implemented
|
||||
- What's partially done
|
||||
- What's not started yet
|
||||
|
||||
## Implementation Guidelines
|
||||
|
||||
### During Implementation
|
||||
|
||||
- Follow CODE_STYLE.md strictly (2-space indent, double quotes, braces always, etc.)
|
||||
- Follow workspace rules for TypeScript, React, TRPC patterns, and Remix conventions
|
||||
- Mark todos complete as you finish each task
|
||||
- Commit logical chunks of work
|
||||
|
||||
### Code Quality
|
||||
|
||||
- No stubbed implementations
|
||||
- Handle edge cases and error conditions
|
||||
- Include descriptive error messages with context
|
||||
- Use async/await for all I/O operations
|
||||
- Use AppError class when throwing errors
|
||||
- Use Zod for validation and react-hook-form for forms
|
||||
|
||||
### Testing
|
||||
|
||||
**Important**: E2E tests are time-consuming. Only write tests for non-trivial functionality.
|
||||
|
||||
- Write E2E tests in `packages/app-tests/e2e/` using Playwright
|
||||
- Test critical user flows and edge cases
|
||||
- Follow existing E2E test patterns in the codebase
|
||||
- Use descriptive test names that explain what is being tested
|
||||
- Skip tests for trivial changes (simple UI tweaks, minor refactors, etc.)
|
||||
|
||||
## Autonomous Workflow
|
||||
|
||||
Work continuously through these steps:
|
||||
|
||||
1. **Implement** - Write the code for the current task
|
||||
2. **Typecheck** - Run `npm run typecheck -w @documenso/remix` to verify types
|
||||
3. **Lint** - Run `npm run lint:fix` to fix linting issues
|
||||
4. **Test** - If non-trivial, run E2E tests: `npm run test:dev -w @documenso/app-tests`
|
||||
5. **Fix** - If tests fail, fix and re-run
|
||||
6. **Repeat** - Move to next task
|
||||
|
||||
## Stopping Conditions
|
||||
|
||||
**Stop and report success when:**
|
||||
|
||||
- All spec requirements are implemented
|
||||
- Typecheck passes
|
||||
- Lint passes
|
||||
- E2E tests pass (if written for non-trivial functionality)
|
||||
|
||||
**Stop and ask for help when:**
|
||||
|
||||
- The spec is ambiguous and you need clarification
|
||||
- You encounter a blocking issue you cannot resolve
|
||||
- You need to make a decision that significantly deviates from the spec
|
||||
- External dependencies are missing
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Type checking
|
||||
npm run typecheck -w @documenso/remix
|
||||
|
||||
# Linting
|
||||
npm run lint:fix
|
||||
|
||||
# E2E Tests (only for non-trivial work)
|
||||
npm run test:dev -w @documenso/app-tests # Run E2E tests in dev mode
|
||||
npm run test-ui:dev -w @documenso/app-tests # Run E2E tests with UI
|
||||
npm run test:e2e # Run full E2E test suite
|
||||
|
||||
# Development
|
||||
npm run dev # Start dev server
|
||||
```
|
||||
|
||||
## Begin
|
||||
|
||||
Read the spec file and CODE_STYLE.md, assess the current implementation state, then continue where the previous session left off. Use TodoWrite to track your progress throughout.
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
description: Create a new justification file in .agents/justifications/
|
||||
argument-hint: <justification-slug> [content]
|
||||
---
|
||||
|
||||
You are creating a new justification file in the `.agents/justifications/` directory.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Determine the slug** - Use `$ARGUMENTS` as the file slug (kebab-case recommended)
|
||||
2. **Gather content** - Collect or generate the justification content
|
||||
3. **Create the file** - Use the create-justification script to generate the file
|
||||
|
||||
## Usage
|
||||
|
||||
The script will automatically:
|
||||
- Generate a unique three-word ID (e.g., `swift-emerald-river`)
|
||||
- Create frontmatter with current date and formatted title
|
||||
- Save the file as `{id}-{slug}.md` in `.agents/justifications/`
|
||||
|
||||
## Creating the File
|
||||
|
||||
### Option 1: Direct Content
|
||||
|
||||
If you have the content ready, run:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-justification.ts "$ARGUMENTS" "Your justification content here"
|
||||
```
|
||||
|
||||
### Option 2: Multi-line Content (Heredoc)
|
||||
|
||||
For multi-line content, use heredoc:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-justification.ts "$ARGUMENTS" << HEREDOC
|
||||
Your multi-line
|
||||
justification content
|
||||
goes here
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
### Option 3: Pipe Content
|
||||
|
||||
You can also pipe content:
|
||||
|
||||
```bash
|
||||
echo "Your content" | npx tsx scripts/create-justification.ts "$ARGUMENTS"
|
||||
```
|
||||
|
||||
## File Format
|
||||
|
||||
The created file will have:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-13
|
||||
title: Justification Title
|
||||
---
|
||||
|
||||
Your content here
|
||||
```
|
||||
|
||||
The title is automatically formatted from the slug (e.g., `architecture-decision` → `Architecture Decision`).
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Use descriptive slugs in kebab-case (e.g., `tech-stack-choice`, `api-design-rationale`)
|
||||
- Include clear reasoning and context for the decision
|
||||
- The unique ID ensures no filename conflicts
|
||||
- Files are automatically dated for organization
|
||||
|
||||
## Begin
|
||||
|
||||
Create a justification file using the slug from `$ARGUMENTS` and appropriate content documenting the reasoning or justification.
|
||||
@@ -1,76 +0,0 @@
|
||||
---
|
||||
description: Create a new plan file in .agents/plans/
|
||||
argument-hint: <plan-slug> [content]
|
||||
---
|
||||
|
||||
You are creating a new plan file in the `.agents/plans/` directory.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Determine the slug** - Use `$ARGUMENTS` as the file slug (kebab-case recommended)
|
||||
2. **Gather content** - Collect or generate the plan content
|
||||
3. **Create the file** - Use the create-plan script to generate the file
|
||||
|
||||
## Usage
|
||||
|
||||
The script will automatically:
|
||||
|
||||
- Generate a unique three-word ID (e.g., `happy-blue-moon`)
|
||||
- Create frontmatter with current date and formatted title
|
||||
- Save the file as `{id}-{slug}.md` in `.agents/plans/`
|
||||
|
||||
## Creating the File
|
||||
|
||||
### Option 1: Direct Content
|
||||
|
||||
If you have the content ready, run:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-plan.ts "$ARGUMENTS" "Your plan content here"
|
||||
```
|
||||
|
||||
### Option 2: Multi-line Content (Heredoc)
|
||||
|
||||
For multi-line content, use heredoc:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-plan.ts "$ARGUMENTS" << HEREDOC
|
||||
Your multi-line
|
||||
plan content
|
||||
goes here
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
### Option 3: Pipe Content
|
||||
|
||||
You can also pipe content:
|
||||
|
||||
```bash
|
||||
echo "Your content" | npx tsx scripts/create-plan.ts "$ARGUMENTS"
|
||||
```
|
||||
|
||||
## File Format
|
||||
|
||||
The created file will have:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-13
|
||||
title: Plan Title
|
||||
---
|
||||
|
||||
Your content here
|
||||
```
|
||||
|
||||
The title is automatically formatted from the slug (e.g., `my-feature` → `My Feature`).
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Use descriptive slugs in kebab-case (e.g., `user-authentication`, `api-integration`)
|
||||
- Include clear, actionable plan content
|
||||
- The unique ID ensures no filename conflicts
|
||||
- Files are automatically dated for organization
|
||||
|
||||
## Begin
|
||||
|
||||
Create a plan file using the slug from `$ARGUMENTS` and appropriate content for the planning task.
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
description: Create a new scratch file in .agents/scratches/
|
||||
argument-hint: <scratch-slug> [content]
|
||||
---
|
||||
|
||||
You are creating a new scratch file in the `.agents/scratches/` directory.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Determine the slug** - Use `$ARGUMENTS` as the file slug (kebab-case recommended)
|
||||
2. **Gather content** - Collect or generate the scratch content
|
||||
3. **Create the file** - Use the create-scratch script to generate the file
|
||||
|
||||
## Usage
|
||||
|
||||
The script will automatically:
|
||||
- Generate a unique three-word ID (e.g., `calm-teal-cloud`)
|
||||
- Create frontmatter with current date and formatted title
|
||||
- Save the file as `{id}-{slug}.md` in `.agents/scratches/`
|
||||
|
||||
## Creating the File
|
||||
|
||||
### Option 1: Direct Content
|
||||
|
||||
If you have the content ready, run:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-scratch.ts "$ARGUMENTS" "Your scratch content here"
|
||||
```
|
||||
|
||||
### Option 2: Multi-line Content (Heredoc)
|
||||
|
||||
For multi-line content, use heredoc:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-scratch.ts "$ARGUMENTS" << HEREDOC
|
||||
Your multi-line
|
||||
scratch content
|
||||
goes here
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
### Option 3: Pipe Content
|
||||
|
||||
You can also pipe content:
|
||||
|
||||
```bash
|
||||
echo "Your content" | npx tsx scripts/create-scratch.ts "$ARGUMENTS"
|
||||
```
|
||||
|
||||
## File Format
|
||||
|
||||
The created file will have:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-13
|
||||
title: Scratch Title
|
||||
---
|
||||
|
||||
Your content here
|
||||
```
|
||||
|
||||
The title is automatically formatted from the slug (e.g., `quick-notes` → `Quick Notes`).
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Use descriptive slugs in kebab-case (e.g., `exploration-ideas`, `temporary-notes`)
|
||||
- Scratch files are for temporary notes, explorations, or ideas
|
||||
- The unique ID ensures no filename conflicts
|
||||
- Files are automatically dated for organization
|
||||
|
||||
## Begin
|
||||
|
||||
Create a scratch file using the slug from `$ARGUMENTS` and appropriate content for notes or exploration.
|
||||
@@ -1,201 +0,0 @@
|
||||
---
|
||||
description: Generate MDX documentation for a module or feature
|
||||
argument-hint: <module-path-or-feature>
|
||||
---
|
||||
|
||||
You are creating proper MDX documentation for a module or feature in Documenso using Nextra.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Identify the scope** - What does `$ARGUMENTS` refer to? (file, directory, or feature name)
|
||||
2. **Read the source code** - Understand the public API, types, and behavior
|
||||
3. **Read existing docs** - Check if there's documentation to update or reference
|
||||
4. **Write comprehensive documentation** - Create or update MDX docs in the appropriate location
|
||||
5. **Update navigation** - Add entry to `_meta.js` if creating a new page
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
Create documentation in the appropriate location:
|
||||
|
||||
- **Developer docs**: `apps/documentation/pages/developers/`
|
||||
- **User docs**: `apps/documentation/pages/users/`
|
||||
|
||||
### File Format
|
||||
|
||||
All documentation files must be `.mdx` files with frontmatter:
|
||||
|
||||
```mdx
|
||||
---
|
||||
title: Page Title
|
||||
description: Brief description for SEO and meta tags
|
||||
---
|
||||
|
||||
# Page Title
|
||||
|
||||
Content starts here...
|
||||
```
|
||||
|
||||
### Navigation
|
||||
|
||||
Each directory should have a `_meta.js` file that defines the navigation structure:
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
index: 'Introduction',
|
||||
'feature-name': 'Feature Name',
|
||||
'another-feature': 'Another Feature',
|
||||
};
|
||||
```
|
||||
|
||||
If creating a new page, add it to the appropriate `_meta.js` file.
|
||||
|
||||
### Documentation Format
|
||||
|
||||
````mdx
|
||||
---
|
||||
title: <Module|Feature Name>
|
||||
description: Brief description of what this does and when to use it
|
||||
---
|
||||
|
||||
# <Module|Feature Name>
|
||||
|
||||
Brief description of what this module/feature does and when to use it.
|
||||
|
||||
## Installation
|
||||
|
||||
If there are specific packages or imports needed:
|
||||
|
||||
```bash
|
||||
npm install @documenso/package-name
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```jsx
|
||||
// Minimal working example
|
||||
import { Component } from '@documenso/package';
|
||||
|
||||
const Example = () => {
|
||||
return <Component />;
|
||||
};
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Component/Function Name
|
||||
|
||||
Description of what it does.
|
||||
|
||||
#### Props/Parameters
|
||||
|
||||
| Prop/Param | Type | Description |
|
||||
| ---------- | -------------------- | ------------------------- |
|
||||
| prop | `string` | Description of the prop |
|
||||
| optional | `boolean` (optional) | Optional prop description |
|
||||
|
||||
#### Example
|
||||
|
||||
```jsx
|
||||
import { Component } from '@documenso/package';
|
||||
|
||||
<Component prop="value" optional={true} />;
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
#### `TypeName`
|
||||
|
||||
```typescript
|
||||
type TypeName = {
|
||||
property: string;
|
||||
optional?: boolean;
|
||||
};
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Common Use Case
|
||||
|
||||
```jsx
|
||||
// Full working example
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
```jsx
|
||||
// More complex example
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Link to related documentation](/developers/path)
|
||||
- [Another related page](/users/path)
|
||||
````
|
||||
|
||||
## Guidelines
|
||||
|
||||
### Content Quality
|
||||
|
||||
- **Be accurate** - Verify behavior by reading the code
|
||||
- **Be complete** - Document all public API surface
|
||||
- **Be practical** - Include real, working examples
|
||||
- **Be concise** - Don't over-explain obvious things
|
||||
- **Be user-focused** - Write for the target audience (developers or users)
|
||||
|
||||
### Code Examples
|
||||
|
||||
- Use appropriate language tags: `jsx`, `tsx`, `typescript`, `bash`, `json`
|
||||
- Show imports when not obvious
|
||||
- Include expected output in comments where helpful
|
||||
- Progress from simple to complex
|
||||
- Use real examples from the codebase when possible
|
||||
|
||||
### Formatting
|
||||
|
||||
- Always include frontmatter with `title` and `description`
|
||||
- Use proper markdown headers (h1 for title, h2 for sections)
|
||||
- Use tables for props/parameters documentation (matching existing style)
|
||||
- Use code fences with appropriate language tags
|
||||
- Use Nextra components when appropriate:
|
||||
- `<Callout type="info">` for notes
|
||||
- `<Steps>` for step-by-step instructions
|
||||
- Use relative links for internal documentation (e.g., `/developers/embedding/react`)
|
||||
|
||||
### Nextra Components
|
||||
|
||||
You can import and use Nextra components:
|
||||
|
||||
```jsx
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
<Callout type="info">
|
||||
This is an informational note.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
<Steps.Step>First step</Steps.Step>
|
||||
<Steps.Step>Second step</Steps.Step>
|
||||
</Steps>
|
||||
```
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Include types inline so docs don't get stale
|
||||
- Reference source file locations for complex behavior
|
||||
- Keep examples up-to-date with the codebase
|
||||
- Update `_meta.js` when adding new pages
|
||||
|
||||
## Process
|
||||
|
||||
1. **Explore the code** - Read source files to understand the API
|
||||
2. **Identify the audience** - Is this for developers or users?
|
||||
3. **Check existing docs** - Look for similar pages to match style
|
||||
4. **Draft the structure** - Outline sections before writing
|
||||
5. **Write content** - Fill in each section with frontmatter
|
||||
6. **Add examples** - Create working code samples
|
||||
7. **Update navigation** - Add to `_meta.js` if needed
|
||||
8. **Review** - Read through for clarity and accuracy
|
||||
|
||||
## Begin
|
||||
|
||||
Analyze `$ARGUMENTS`, read the relevant source code, check existing documentation patterns, and create comprehensive MDX documentation following the Documenso documentation style.
|
||||
@@ -1,100 +0,0 @@
|
||||
---
|
||||
description: Implement a spec from the plans directory
|
||||
argument-hint: <spec-file-path>
|
||||
---
|
||||
|
||||
You are implementing a specification from the `.agents/plans/` directory. Work autonomously until the feature is complete and tests pass.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Read the spec** at `$ARGUMENTS`
|
||||
2. **Read CODE_STYLE.md** for formatting conventions
|
||||
3. **Plan the implementation** using the TodoWrite tool to break down the work
|
||||
4. **Implement the feature** following the spec and code style
|
||||
5. **Write E2E tests** only for non-trivial functionality (E2E tests are time-consuming)
|
||||
6. **Run tests** and fix any failures
|
||||
7. **Run typecheck and lint** and fix any issues
|
||||
|
||||
## Implementation Guidelines
|
||||
|
||||
### Before Coding
|
||||
|
||||
- Understand the spec's goals and scope
|
||||
- Identify the desired API from usage examples in the spec
|
||||
- Review related existing code to understand patterns
|
||||
- Break the work into discrete tasks using TodoWrite
|
||||
|
||||
### During Implementation
|
||||
|
||||
- Follow CODE_STYLE.md strictly (2-space indent, double quotes, braces always, etc.)
|
||||
- Follow workspace rules for TypeScript, React, TRPC patterns, and Remix conventions
|
||||
- Mark todos complete as you finish each task
|
||||
- Commit logical chunks of work
|
||||
|
||||
### Code Quality
|
||||
|
||||
- No stubbed implementations
|
||||
- Handle edge cases and error conditions
|
||||
- Include descriptive error messages with context
|
||||
- Use async/await for all I/O operations
|
||||
- Use AppError class when throwing errors
|
||||
- Use Zod for validation and react-hook-form for forms
|
||||
|
||||
### Testing
|
||||
|
||||
**Important**: E2E tests are time-consuming. Only write tests for non-trivial functionality.
|
||||
|
||||
- Write E2E tests in `packages/app-tests/e2e/` using Playwright
|
||||
- Test critical user flows and edge cases
|
||||
- Follow existing E2E test patterns in the codebase
|
||||
- Use descriptive test names that explain what is being tested
|
||||
- Skip tests for trivial changes (simple UI tweaks, minor refactors, etc.)
|
||||
|
||||
## Autonomous Workflow
|
||||
|
||||
Work continuously through these steps:
|
||||
|
||||
1. **Implement** - Write the code for the current task
|
||||
2. **Typecheck** - Run `npm run typecheck -w @documenso/remix` to verify types
|
||||
3. **Lint** - Run `npm run lint:fix` to fix linting issues
|
||||
4. **Test** - If non-trivial, run E2E tests: `npm run test:dev -w @documenso/app-tests`
|
||||
5. **Fix** - If tests fail, fix and re-run
|
||||
6. **Repeat** - Move to next task
|
||||
|
||||
## Stopping Conditions
|
||||
|
||||
**Stop and report success when:**
|
||||
|
||||
- All spec requirements are implemented
|
||||
- Typecheck passes
|
||||
- Lint passes
|
||||
- E2E tests pass (if written for non-trivial functionality)
|
||||
|
||||
**Stop and ask for help when:**
|
||||
|
||||
- The spec is ambiguous and you need clarification
|
||||
- You encounter a blocking issue you cannot resolve
|
||||
- You need to make a decision that significantly deviates from the spec
|
||||
- External dependencies are missing
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Type checking
|
||||
npm run typecheck -w @documenso/remix
|
||||
|
||||
# Linting
|
||||
npm run lint:fix
|
||||
|
||||
# E2E Tests (only for non-trivial work)
|
||||
npm run test:dev -w @documenso/app-tests # Run E2E tests in dev mode
|
||||
npm run test-ui:dev -w @documenso/app-tests # Run E2E tests with UI
|
||||
npm run test:e2e # Run full E2E test suite
|
||||
|
||||
# Development
|
||||
npm run dev # Start dev server
|
||||
```
|
||||
|
||||
## Begin
|
||||
|
||||
Read the spec file and CODE_STYLE.md, then start implementing. Use TodoWrite to track your progress throughout.
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
description: Deep-dive interview to flesh out a spec or design document
|
||||
agent: build
|
||||
argument-hint: <file-path>
|
||||
---
|
||||
|
||||
You are conducting a thorough interview to help flesh out and complete a specification or design document.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Read the document** at `$ARGUMENTS`
|
||||
2. **Analyze it deeply** - identify gaps, ambiguities, unexplored edge cases, and areas needing clarification
|
||||
3. **Interview the user** by providing a question with some pre-determined options
|
||||
4. **Write the completed spec** back to the file when the interview is complete
|
||||
|
||||
## Interview Guidelines
|
||||
|
||||
### Question Quality
|
||||
- Ask **non-obvious, insightful questions** - avoid surface-level queries
|
||||
- Focus on: technical implementation details, architectural decisions, edge cases, error handling, UX implications, security considerations, performance tradeoffs, integration points, migration strategies, rollback plans
|
||||
- Each question should reveal something that would otherwise be missed
|
||||
- Challenge assumptions embedded in the document
|
||||
- Explore second and third-order consequences of design decisions
|
||||
- Use the Web Search and other tools where required to ground questions (e.g. package recommendations)
|
||||
|
||||
### Question Strategy
|
||||
- Start by identifying the 3-5 most critical unknowns or ambiguities
|
||||
- Use the AskUserQuestion tool with well-crafted options that represent real tradeoffs
|
||||
- When appropriate, offer multiple valid approaches with their pros/cons as options
|
||||
- Don't ask about things that are already clearly specified
|
||||
- Probe deeper when answers reveal new areas of uncertainty
|
||||
|
||||
### Topics to Explore (as relevant)
|
||||
- **Technical**: Data models, API contracts, state management, concurrency, caching, validation
|
||||
- **UX**: Error states, loading states, empty states, edge cases, accessibility, mobile considerations
|
||||
- **Operations**: Deployment, monitoring, alerting, debugging, logging, feature flags
|
||||
- **Security**: Auth, authz, input validation, rate limiting, audit trails
|
||||
- **Scale**: Performance bottlenecks, data growth, traffic spikes, graceful degradation
|
||||
- **Integration**: Dependencies, backwards compatibility, versioning, migration path
|
||||
- **Failure modes**: What happens when X fails? How do we recover? What's the blast radius?
|
||||
|
||||
### Interview Flow
|
||||
1. Ask 2-4 questions at a time (use multiple questions in one when they're related)
|
||||
2. After each round, incorporate answers and identify follow-up questions
|
||||
3. Continue until all critical areas are addressed
|
||||
4. Signal when you believe the interview is complete, but offer to go deeper
|
||||
|
||||
## Output
|
||||
|
||||
When the interview is complete:
|
||||
1. Synthesize all gathered information
|
||||
2. Rewrite/expand the original document with the new details
|
||||
3. Preserve the document's original structure where sensible, but reorganize if needed
|
||||
4. Add new sections for areas that weren't originally covered
|
||||
5. Write the completed spec back to `$ARGUMENTS`
|
||||
|
||||
Begin by reading the file and identifying your first set of deep questions.
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
name: create-justification
|
||||
description: Create a new justification file in .agents/justifications/ with a unique three-word ID, frontmatter, and formatted title
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
metadata:
|
||||
audience: agents
|
||||
workflow: decision-making
|
||||
---
|
||||
|
||||
## What I do
|
||||
|
||||
I help you create new justification files in the `.agents/justifications/` directory. Each justification file gets:
|
||||
|
||||
- A unique three-word identifier (e.g., `swift-emerald-river`)
|
||||
- Frontmatter with the current date and formatted title
|
||||
- Content you provide
|
||||
|
||||
## How to use
|
||||
|
||||
Run the script with a slug and content:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-justification.ts "decision-name" "Justification content here"
|
||||
```
|
||||
|
||||
Or use heredoc for multi-line content:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-justification.ts "decision-name" << HEREDOC
|
||||
Multi-line
|
||||
justification content
|
||||
goes here
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
## File format
|
||||
|
||||
Files are created as: `{three-word-id}-{slug}.md`
|
||||
|
||||
Example: `swift-emerald-river-decision-name.md`
|
||||
|
||||
The file includes frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-13
|
||||
title: Decision Name
|
||||
---
|
||||
|
||||
Your content here
|
||||
```
|
||||
|
||||
## When to use me
|
||||
|
||||
Use this skill when you need to document the reasoning or justification for a decision, approach, or architectural choice. The unique ID ensures no filename conflicts, and the frontmatter provides metadata for organization.
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
name: create-plan
|
||||
description: Create a new plan file in .agents/plans/ with a unique three-word ID, frontmatter, and formatted title
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
metadata:
|
||||
audience: agents
|
||||
workflow: planning
|
||||
---
|
||||
|
||||
## What I do
|
||||
|
||||
I help you create new plan files in the `.agents/plans/` directory. Each plan file gets:
|
||||
|
||||
- A unique three-word identifier (e.g., `happy-blue-moon`)
|
||||
- Frontmatter with the current date and formatted title
|
||||
- Content you provide
|
||||
|
||||
## How to use
|
||||
|
||||
Run the script with a slug and content:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-plan.ts "feature-name" "Plan content here"
|
||||
```
|
||||
|
||||
Or use heredoc for multi-line content:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-plan.ts "feature-name" << HEREDOC
|
||||
Multi-line
|
||||
plan content
|
||||
goes here
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
## File format
|
||||
|
||||
Files are created as: `{three-word-id}-{slug}.md`
|
||||
|
||||
Example: `happy-blue-moon-feature-name.md`
|
||||
|
||||
The file includes frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-13
|
||||
title: Feature Name
|
||||
---
|
||||
|
||||
Your content here
|
||||
```
|
||||
|
||||
## When to use me
|
||||
|
||||
Use this skill when you need to create a new plan document for a feature, task, or project. The unique ID ensures no filename conflicts, and the frontmatter provides metadata for organization.
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
name: create-scratch
|
||||
description: Create a new scratch file in .agents/scratches/ with a unique three-word ID, frontmatter, and formatted title
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
metadata:
|
||||
audience: agents
|
||||
workflow: exploration
|
||||
---
|
||||
|
||||
## What I do
|
||||
|
||||
I help you create new scratch files in the `.agents/scratches/` directory. Each scratch file gets:
|
||||
|
||||
- A unique three-word identifier (e.g., `calm-teal-cloud`)
|
||||
- Frontmatter with the current date and formatted title
|
||||
- Content you provide
|
||||
|
||||
## How to use
|
||||
|
||||
Run the script with a slug and content:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-scratch.ts "note-name" "Scratch content here"
|
||||
```
|
||||
|
||||
Or use heredoc for multi-line content:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-scratch.ts "note-name" << HEREDOC
|
||||
Multi-line
|
||||
scratch content
|
||||
goes here
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
## File format
|
||||
|
||||
Files are created as: `{three-word-id}-{slug}.md`
|
||||
|
||||
Example: `calm-teal-cloud-note-name.md`
|
||||
|
||||
The file includes frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-13
|
||||
title: Note Name
|
||||
---
|
||||
|
||||
Your content here
|
||||
```
|
||||
|
||||
## When to use me
|
||||
|
||||
Use this skill when you need to create a temporary note, exploration document, or scratch pad for ideas. The unique ID ensures no filename conflicts, and the frontmatter provides metadata for organization.
|
||||
@@ -1,17 +0,0 @@
|
||||
node_modules
|
||||
.next
|
||||
public
|
||||
**/**/node_modules
|
||||
**/**/.next
|
||||
**/**/public
|
||||
packages/lib/translations/**/*.js
|
||||
|
||||
*.lock
|
||||
*.log
|
||||
*.test.ts
|
||||
|
||||
.gitignore
|
||||
.npmignore
|
||||
.prettierignore
|
||||
.DS_Store
|
||||
.eslintignore
|
||||
Vendored
+2
-14
@@ -1,22 +1,10 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "explicit"
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"eslint.validate": ["typescript", "typescriptreact", "javascript", "javascriptreact"],
|
||||
"javascript.preferences.importModuleSpecifier": "non-relative",
|
||||
"javascript.preferences.useAliasesForRenames": false,
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"files.eol": "\n",
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true,
|
||||
"[prisma]": {
|
||||
"editor.defaultFormatter": "Prisma.prisma"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"prisma.pinToPrisma6": true
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# General Issues
|
||||
Contact: https://github.com/documenso/documenso/issues/new?assignees=&labels=bug&projects=&template=bug-report.yml
|
||||
|
||||
# Report critical issues privately to let us take appropriate action before publishing.
|
||||
Contact: mailto:security@documenso.com
|
||||
Preferred-Languages: en
|
||||
Canonical: https://documenso.com/.well-known/security.txt
|
||||
@@ -1,59 +0,0 @@
|
||||
# Agent Guidelines for Documenso
|
||||
|
||||
## Build/Test/Lint Commands
|
||||
|
||||
- `npm run build` - Build all packages
|
||||
- `npm run lint` - Lint all packages
|
||||
- `npm run lint:fix` - Auto-fix linting issues
|
||||
- `npm run test:e2e` - Run E2E tests with Playwright
|
||||
- `npm run test:dev -w @documenso/app-tests` - Run single E2E test in dev mode
|
||||
- `npm run test-ui:dev -w @documenso/app-tests` - Run E2E tests with UI
|
||||
- `npm run format` - Format code with Prettier
|
||||
- `npm run dev` - Start development server for Remix app
|
||||
|
||||
**Important:** Do not run `npm run build` to verify changes unless explicitly asked. Builds take a long time (~2 minutes). Use `npx tsc --noEmit` for type checking specific packages if needed.
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
- Use TypeScript for all code; prefer `type` over `interface`
|
||||
- Use functional components with `const Component = () => {}`
|
||||
- Never use classes; prefer functional/declarative patterns
|
||||
- Use descriptive variable names with auxiliary verbs (isLoading, hasError)
|
||||
- Directory names: lowercase with dashes (auth-wizard)
|
||||
- Use named exports for components
|
||||
- Never use 'use client' directive
|
||||
- Never use 1-line if statements
|
||||
- Structure files: exported component, subcomponents, helpers, static content, types
|
||||
|
||||
## Error Handling & Validation
|
||||
|
||||
- Use custom AppError class when throwing errors
|
||||
- When catching errors on the frontend use `const error = AppError.parse(error)` to get the error code
|
||||
- Use early returns and guard clauses
|
||||
- Use Zod for form validation and react-hook-form for forms
|
||||
- Use error boundaries for unexpected errors
|
||||
|
||||
## UI & Styling
|
||||
|
||||
- Use Shadcn UI, Radix, and Tailwind CSS with mobile-first approach
|
||||
- Use `<Form>` `<FormItem>` elements with fieldset having `:disabled` attribute when loading
|
||||
- Use Lucide icons with longhand names (HomeIcon vs Home)
|
||||
|
||||
## TRPC Routes
|
||||
|
||||
- Each route in own file: `routers/teams/create-team.ts`
|
||||
- Associated types file: `routers/teams/create-team.types.ts`
|
||||
- Request/response schemas: `Z[RouteName]RequestSchema`, `Z[RouteName]ResponseSchema`
|
||||
- Only use GET and POST methods in OpenAPI meta
|
||||
- Deconstruct input argument on its own line
|
||||
- Prefer route names such as get/getMany/find/create/update/delete
|
||||
- "create" routes request schema should have the ID and data in the top level
|
||||
- "update" routes request schema should have the ID in the top level and the data in a nested "data" object
|
||||
|
||||
## Translations & Remix
|
||||
|
||||
- Use `<Trans>string</Trans>` for JSX translations from `@lingui/react/macro`
|
||||
- Use `t\`string\`` macro for TypeScript translations
|
||||
- Use `(params: Route.Params)` and `(loaderData: Route.LoaderData)` for routes
|
||||
- Directly return data from loaders, don't use `json()`
|
||||
- Use `superLoaderJson` when sending complex data through loaders such as dates or prisma decimals
|
||||
-692
@@ -1,692 +0,0 @@
|
||||
# Documenso Code Style Guide
|
||||
|
||||
This document captures the code style, patterns, and conventions used in the Documenso codebase. It covers both enforceable rules and subjective "taste" elements that make our code consistent and maintainable.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [General Principles](#general-principles)
|
||||
2. [TypeScript Conventions](#typescript-conventions)
|
||||
3. [Imports & Dependencies](#imports--dependencies)
|
||||
4. [Functions & Methods](#functions--methods)
|
||||
5. [React & Components](#react--components)
|
||||
6. [Error Handling](#error-handling)
|
||||
7. [Async/Await Patterns](#asyncawait-patterns)
|
||||
8. [Whitespace & Formatting](#whitespace--formatting)
|
||||
9. [Naming Conventions](#naming-conventions)
|
||||
10. [Pattern Matching](#pattern-matching)
|
||||
11. [Database & Prisma](#database--prisma)
|
||||
12. [TRPC Patterns](#trpc-patterns)
|
||||
|
||||
---
|
||||
|
||||
## General Principles
|
||||
|
||||
- **Functional over Object-Oriented**: Prefer functional programming patterns over classes
|
||||
- **Explicit over Implicit**: Be explicit about types, return values, and error cases
|
||||
- **Early Returns**: Use guard clauses and early returns to reduce nesting
|
||||
- **Immutability**: Favor `const` over `let`; avoid mutation where possible
|
||||
|
||||
---
|
||||
|
||||
## TypeScript Conventions
|
||||
|
||||
### Type Definitions
|
||||
|
||||
```typescript
|
||||
// ✅ Prefer `type` over `interface`
|
||||
type CreateDocumentOptions = {
|
||||
templateId: number;
|
||||
userId: number;
|
||||
recipients: Recipient[];
|
||||
};
|
||||
|
||||
// ❌ Avoid interfaces unless absolutely necessary
|
||||
interface CreateDocumentOptions {
|
||||
templateId: number;
|
||||
}
|
||||
```
|
||||
|
||||
### Type Imports
|
||||
|
||||
```typescript
|
||||
// ✅ Use `type` keyword for type-only imports
|
||||
import type { Document, Recipient } from '@prisma/client';
|
||||
import { DocumentStatus } from '@prisma/client';
|
||||
|
||||
// Types in function signatures
|
||||
export const findDocuments = async ({ userId, teamId }: FindDocumentsOptions) => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
### Inline Types for Function Parameters
|
||||
|
||||
```typescript
|
||||
// ✅ Extract inline types to named types
|
||||
type FinalRecipient = Pick<Recipient, 'name' | 'email' | 'role' | 'authOptions'> & {
|
||||
templateRecipientId: number;
|
||||
fields: Field[];
|
||||
};
|
||||
|
||||
const finalRecipients: FinalRecipient[] = [];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Imports & Dependencies
|
||||
|
||||
### Import Organization
|
||||
|
||||
Imports should be organized in the following order with blank lines between groups:
|
||||
|
||||
```typescript
|
||||
// 1. React imports
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
// 2. Third-party library imports (alphabetically)
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Document, Recipient } from '@prisma/client';
|
||||
import { DocumentStatus, RecipientRole } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
// 3. Internal package imports (from @documenso/*)
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
// 4. Relative imports
|
||||
import { getTeamById } from '../team/get-team';
|
||||
import type { FindResultResponse } from './types';
|
||||
```
|
||||
|
||||
### Destructuring Imports
|
||||
|
||||
```typescript
|
||||
// ✅ Destructure specific exports
|
||||
// ✅ Use type imports for types
|
||||
import type { Document } from '@prisma/client';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Functions & Methods
|
||||
|
||||
### Arrow Functions
|
||||
|
||||
```typescript
|
||||
// ✅ Always use arrow functions for functions
|
||||
export const createDocument = async ({
|
||||
userId,
|
||||
title,
|
||||
}: CreateDocumentOptions) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
// ✅ Callbacks and handlers
|
||||
const onSubmit = useCallback(async () => {
|
||||
// ...
|
||||
}, [dependencies]);
|
||||
|
||||
// ❌ Avoid regular function declarations
|
||||
function createDocument() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Function Parameters
|
||||
|
||||
```typescript
|
||||
// ✅ Use destructured object parameters for multiple params
|
||||
export const findDocuments = async ({
|
||||
userId,
|
||||
teamId,
|
||||
status = ExtendedDocumentStatus.ALL,
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
}: FindDocumentsOptions) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
// ✅ Destructure on separate line when needed
|
||||
const onFormSubmit = form.handleSubmit(onSubmit);
|
||||
|
||||
// ✅ Deconstruct nested properties explicitly
|
||||
const { user } = ctx;
|
||||
const { templateId } = input;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## React & Components
|
||||
|
||||
### Component Definition
|
||||
|
||||
```typescript
|
||||
// ✅ Use const with arrow function
|
||||
export const AddSignersFormPartial = ({
|
||||
documentFlow,
|
||||
recipients,
|
||||
fields,
|
||||
onSubmit,
|
||||
}: AddSignersFormProps) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
// ❌ Never use classes
|
||||
class MyComponent extends React.Component {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Hooks
|
||||
|
||||
```typescript
|
||||
// ✅ Group related hooks together with blank line separation
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { currentStep, totalSteps, previousStep } = useStep();
|
||||
|
||||
const form = useForm<TFormSchema>({
|
||||
resolver: zodResolver(ZFormSchema),
|
||||
defaultValues: {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Event Handlers
|
||||
|
||||
```typescript
|
||||
// ✅ Use arrow functions with descriptive names
|
||||
const onFormSubmit = async () => {
|
||||
await form.trigger();
|
||||
// ...
|
||||
};
|
||||
|
||||
const onFieldCopy = useCallback(
|
||||
(event?: KeyboardEvent | null) => {
|
||||
event?.preventDefault();
|
||||
// ...
|
||||
},
|
||||
[dependencies],
|
||||
);
|
||||
|
||||
// ✅ Inline handlers for simple operations
|
||||
<Button onClick={() => setOpen(false)}>Close</Button>
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
```typescript
|
||||
// ✅ Descriptive state names with auxiliary verbs
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [hasError, setHasError] = useState(false);
|
||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||
|
||||
// ✅ Complex state in single useState when related
|
||||
const [coords, setCoords] = useState({
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Try-Catch Blocks
|
||||
|
||||
```typescript
|
||||
// ✅ Use try-catch for operations that might fail
|
||||
try {
|
||||
const document = await getDocumentById({
|
||||
documentId: Number(documentId),
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: document,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
status: 404,
|
||||
body: {
|
||||
message: 'Document not found',
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Throwing Errors
|
||||
|
||||
```typescript
|
||||
// ✅ Use AppError for application errors
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Template not found',
|
||||
});
|
||||
|
||||
// ✅ Use descriptive error messages
|
||||
if (!template) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: `Template with ID ${templateId} not found`,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Error Parsing on Frontend
|
||||
|
||||
```typescript
|
||||
// ✅ Parse errors on the frontend
|
||||
try {
|
||||
await updateOrganisation({ organisationId, data });
|
||||
} catch (err) {
|
||||
const error = AppError.parseError(err);
|
||||
console.error(error);
|
||||
|
||||
toast({
|
||||
title: t`An error occurred`,
|
||||
description: error.message,
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Async/Await Patterns
|
||||
|
||||
### Async Function Definitions
|
||||
|
||||
```typescript
|
||||
// ✅ Mark async functions clearly
|
||||
export const createDocument = async ({
|
||||
userId,
|
||||
title,
|
||||
}: Options): Promise<Document> => {
|
||||
// ...
|
||||
};
|
||||
|
||||
// ✅ Use await for promises
|
||||
const document = await prisma.document.create({ data });
|
||||
|
||||
// ✅ Use Promise.all for parallel operations
|
||||
const [document, recipients] = await Promise.all([
|
||||
getDocumentById({ documentId }),
|
||||
getRecipientsForDocument({ documentId }),
|
||||
]);
|
||||
```
|
||||
|
||||
### Void for Fire-and-Forget
|
||||
|
||||
```typescript
|
||||
// ✅ Use void for intentionally unwaited promises
|
||||
void handleAutoSave();
|
||||
|
||||
// ✅ Or in event handlers
|
||||
onClick={() => void onFormSubmit()}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Whitespace & Formatting
|
||||
|
||||
### Blank Lines Between Concepts
|
||||
|
||||
```typescript
|
||||
// ✅ Blank line after imports
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export const findDocuments = async () => {
|
||||
// ...
|
||||
};
|
||||
|
||||
// ✅ Blank line between logical sections
|
||||
const user = await prisma.user.findFirst({ where: { id: userId } });
|
||||
|
||||
let team = null;
|
||||
|
||||
if (teamId !== undefined) {
|
||||
team = await getTeamById({ userId, teamId });
|
||||
}
|
||||
|
||||
// ✅ Blank line before return statements
|
||||
const result = await someOperation();
|
||||
|
||||
return result;
|
||||
```
|
||||
|
||||
### Function/Method Spacing
|
||||
|
||||
```typescript
|
||||
// ✅ No blank lines between chained methods in same operation
|
||||
const documents = await prisma.document
|
||||
.findMany({ where: { userId } })
|
||||
.then((docs) => docs.map(maskTokens));
|
||||
|
||||
// ✅ Blank line between different operations
|
||||
const document = await createDocument({ userId });
|
||||
|
||||
await sendDocument({ documentId: document.id });
|
||||
|
||||
return document;
|
||||
```
|
||||
|
||||
### Object and Array Formatting
|
||||
|
||||
```typescript
|
||||
// ✅ Multi-line when complex
|
||||
const options = {
|
||||
userId,
|
||||
teamId,
|
||||
status: ExtendedDocumentStatus.ALL,
|
||||
page: 1,
|
||||
};
|
||||
|
||||
// ✅ Single line when simple
|
||||
const coords = { x: 0, y: 0 };
|
||||
|
||||
// ✅ Array items on separate lines when objects
|
||||
const recipients = [
|
||||
{
|
||||
name: 'John',
|
||||
email: 'john@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Jane',
|
||||
email: 'jane@example.com',
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Variables
|
||||
|
||||
```typescript
|
||||
// ✅ camelCase for variables and functions
|
||||
const documentId = 123;
|
||||
const onSubmit = () => {};
|
||||
|
||||
// ✅ Descriptive names with auxiliary verbs for booleans
|
||||
const isLoading = false;
|
||||
const hasError = false;
|
||||
const canEdit = true;
|
||||
const shouldRender = true;
|
||||
|
||||
// ✅ Prefix with $ for DOM elements
|
||||
const $page = document.querySelector('.page');
|
||||
const $inputRef = useRef<HTMLInputElement>(null);
|
||||
```
|
||||
|
||||
### Types and Schemas
|
||||
|
||||
```typescript
|
||||
// ✅ PascalCase for types
|
||||
type CreateDocumentOptions = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
// ✅ Prefix Zod schemas with Z
|
||||
const ZCreateDocumentSchema = z.object({
|
||||
title: z.string(),
|
||||
});
|
||||
|
||||
// ✅ Prefix type from Zod schema with T
|
||||
type TCreateDocumentSchema = z.infer<typeof ZCreateDocumentSchema>;
|
||||
```
|
||||
|
||||
### Constants
|
||||
|
||||
```typescript
|
||||
// ✅ UPPER_SNAKE_CASE for true constants
|
||||
const DEFAULT_DOCUMENT_DATE_FORMAT = 'dd/MM/yyyy';
|
||||
const MAX_FILE_SIZE = 1024 * 1024 * 5;
|
||||
|
||||
// ✅ camelCase for const variables that aren't "constants"
|
||||
const userId = await getUserId();
|
||||
```
|
||||
|
||||
### Functions
|
||||
|
||||
```typescript
|
||||
// ✅ Verb-based names for functions
|
||||
const createDocument = async () => {};
|
||||
const findDocuments = async () => {};
|
||||
const updateDocument = async () => {};
|
||||
const deleteDocument = async () => {};
|
||||
|
||||
// ✅ On prefix for event handlers
|
||||
const onSubmit = () => {};
|
||||
const onClick = () => {};
|
||||
const onFieldCopy = () => {}; // 'on' is also acceptable
|
||||
```
|
||||
|
||||
### Clarity Over Brevity
|
||||
|
||||
```typescript
|
||||
// ✅ Prefer descriptive names over abbreviations
|
||||
const superLongMethodThatIsCorrect = () => {};
|
||||
const recipientAuthenticationOptions = {};
|
||||
const documentMetadata = {};
|
||||
|
||||
// ❌ Avoid abbreviations that sacrifice clarity
|
||||
const supLongMethThatIsCorrect = () => {};
|
||||
const recipAuthOpts = {};
|
||||
const docMeta = {};
|
||||
|
||||
// ✅ Common abbreviations that are widely understood are acceptable
|
||||
const userId = 123;
|
||||
const htmlElement = document.querySelector('div');
|
||||
const apiResponse = await fetch('/api');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pattern Matching
|
||||
|
||||
### Using ts-pattern
|
||||
|
||||
```typescript
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
// ✅ Use match for complex conditionals
|
||||
const result = match(status)
|
||||
.with(ExtendedDocumentStatus.DRAFT, () => ({
|
||||
status: 'draft',
|
||||
}))
|
||||
.with(ExtendedDocumentStatus.PENDING, () => ({
|
||||
status: 'pending',
|
||||
}))
|
||||
.with(ExtendedDocumentStatus.COMPLETED, () => ({
|
||||
status: 'completed',
|
||||
}))
|
||||
.exhaustive();
|
||||
|
||||
// ✅ Use .otherwise() for default case when not exhaustive
|
||||
const value = match(type)
|
||||
.with('text', () => 'Text field')
|
||||
.with('number', () => 'Number field')
|
||||
.otherwise(() => 'Unknown field');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database & Prisma
|
||||
|
||||
### Query Structure
|
||||
|
||||
```typescript
|
||||
// ✅ Destructure commonly used fields
|
||||
const { id, email, name } = user;
|
||||
|
||||
// ✅ Use select to limit returned fields
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
|
||||
// ✅ Use include for relations
|
||||
const document = await prisma.document.findFirst({
|
||||
where: { id: documentId },
|
||||
include: {
|
||||
recipients: true,
|
||||
fields: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Transactions
|
||||
|
||||
```typescript
|
||||
// ✅ Use transactions for related operations
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const document = await tx.document.create({ data });
|
||||
|
||||
await tx.field.createMany({ data: fieldsData });
|
||||
|
||||
await tx.documentAuditLog.create({ data: auditData });
|
||||
|
||||
return document;
|
||||
});
|
||||
```
|
||||
|
||||
### Where Clauses
|
||||
|
||||
```typescript
|
||||
// ✅ Build complex where clauses separately
|
||||
const whereClause: Prisma.DocumentWhereInput = {
|
||||
AND: [
|
||||
{ userId: user.id },
|
||||
{ deletedAt: null },
|
||||
{ status: { in: [DocumentStatus.DRAFT, DocumentStatus.PENDING] } },
|
||||
],
|
||||
};
|
||||
|
||||
const documents = await prisma.document.findMany({
|
||||
where: whereClause,
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TRPC Patterns
|
||||
|
||||
### Router Structure
|
||||
|
||||
```typescript
|
||||
// ✅ Destructure context and input at start
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: { templateId },
|
||||
});
|
||||
|
||||
return await getTemplateById({
|
||||
id: templateId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Request/Response Schemas
|
||||
|
||||
```typescript
|
||||
// ✅ Name schemas clearly
|
||||
const ZCreateDocumentRequestSchema = z.object({
|
||||
title: z.string(),
|
||||
recipients: z.array(ZRecipientSchema),
|
||||
});
|
||||
|
||||
const ZCreateDocumentResponseSchema = z.object({
|
||||
documentId: z.number(),
|
||||
status: z.string(),
|
||||
});
|
||||
```
|
||||
|
||||
### Error Handling in TRPC
|
||||
|
||||
```typescript
|
||||
// ✅ Catch and transform errors appropriately
|
||||
try {
|
||||
const result = await createDocument({ userId, data });
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
return AppError.toRestAPIError(err);
|
||||
}
|
||||
|
||||
// ✅ Or throw AppError directly
|
||||
if (!template) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Template not found',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Patterns
|
||||
|
||||
### Optional Chaining
|
||||
|
||||
```typescript
|
||||
// ✅ Use optional chaining for potentially undefined values
|
||||
const email = user?.email;
|
||||
const recipientToken = recipient?.token ?? '';
|
||||
|
||||
// ✅ Use nullish coalescing for defaults
|
||||
const pageSize = perPage ?? 10;
|
||||
const status = documentStatus ?? DocumentStatus.DRAFT;
|
||||
```
|
||||
|
||||
### Array Operations
|
||||
|
||||
```typescript
|
||||
// ✅ Use functional array methods
|
||||
const activeRecipients = recipients.filter((r) => r.signingStatus === 'SIGNED');
|
||||
const recipientEmails = recipients.map((r) => r.email);
|
||||
const hasSignedRecipients = recipients.some((r) => r.signingStatus === 'SIGNED');
|
||||
|
||||
// ✅ Use find instead of filter + [0]
|
||||
const recipient = recipients.find((r) => r.id === recipientId);
|
||||
```
|
||||
|
||||
### Conditional Rendering
|
||||
|
||||
```typescript
|
||||
// ✅ Use && for conditional rendering
|
||||
{isLoading && <Loader />}
|
||||
|
||||
// ✅ Use ternary for either/or
|
||||
{isLoading ? <Loader /> : <Content />}
|
||||
|
||||
// ✅ Extract complex conditions to variables
|
||||
const shouldShowAdvanced = isAdmin && hasPermission && !isDisabled;
|
||||
{shouldShowAdvanced && <AdvancedSettings />}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When in Doubt
|
||||
|
||||
- **Consistency**: Follow the patterns you see in similar files
|
||||
- **Readability**: Favor code that's easy to read over clever one-liners
|
||||
- **Explicitness**: Be explicit rather than implicit
|
||||
- **Whitespace**: Use blank lines to separate logical sections
|
||||
- **Early Returns**: Use guard clauses to reduce nesting
|
||||
- **Functional**: Prefer functional patterns over imperative ones
|
||||
@@ -52,53 +52,3 @@ You can build the project with:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## AI-Assisted Development with OpenCode
|
||||
|
||||
We use [OpenCode](https://opencode.ai) for AI-assisted development. OpenCode provides custom commands and skills to help maintain consistency and streamline common workflows.
|
||||
|
||||
OpenCode works with most major AI providers (Anthropic, OpenAI, Google, etc.) or you can use [Zen](https://opencode.ai/zen) for optimized coding models. Configure your preferred provider in the OpenCode settings.
|
||||
|
||||
> **Important**: All AI-generated code must be thoroughly reviewed by the contributor before submitting a PR. You are responsible for understanding and validating every line of code you submit. If we detect that contributors are simply throwing AI-generated code over the wall without proper review, they will be blocked from the repository.
|
||||
|
||||
### Getting Started
|
||||
|
||||
1. Install OpenCode (see [opencode.ai](https://opencode.ai) for other install methods):
|
||||
```bash
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
```
|
||||
2. Configure your AI provider (or use Zen for optimized models)
|
||||
3. Run `opencode` in the project root
|
||||
|
||||
### Available Commands
|
||||
|
||||
Use these commands in OpenCode by typing the command name:
|
||||
|
||||
| Command | Description |
|
||||
| ------------------------------ | -------------------------------------------------------- |
|
||||
| `/implement <spec-path>` | Implement a spec from `.agents/plans/` autonomously |
|
||||
| `/continue <spec-path>` | Continue implementing a spec from a previous session |
|
||||
| `/interview <file-path>` | Deep-dive interview to flesh out a spec or design |
|
||||
| `/document <module-path>` | Generate MDX documentation for a module or feature |
|
||||
| `/commit` | Create a conventional commit for staged changes |
|
||||
| `/create-plan <slug>` | Create a new plan file in `.agents/plans/` |
|
||||
| `/create-scratch <slug>` | Create a scratch file for notes in `.agents/scratches/` |
|
||||
| `/create-justification <slug>` | Create a justification file in `.agents/justifications/` |
|
||||
|
||||
### Typical Workflow
|
||||
|
||||
1. **Create a plan**: Use `/create-plan my-feature` to draft a spec for a new feature
|
||||
2. **Flesh out the spec**: Use `/interview .agents/plans/<file>.md` to refine requirements
|
||||
3. **Implement**: Use `/implement .agents/plans/<file>.md` to build the feature
|
||||
4. **Continue if needed**: Use `/continue .agents/plans/<file>.md` to pick up where you left off
|
||||
5. **Commit**: Use `/commit` to create a conventional commit
|
||||
|
||||
### Agent Files
|
||||
|
||||
The `.agents/` directory stores AI-generated artifacts:
|
||||
|
||||
- **`.agents/plans/`** - Feature specs and implementation plans
|
||||
- **`.agents/scratches/`** - Temporary notes and explorations
|
||||
- **`.agents/justifications/`** - Decision rationale and technical justifications
|
||||
|
||||
These files use a unique ID format (`{word}-{word}-{word}-{slug}.md`) to prevent conflicts.
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
·
|
||||
<a href="https://github.com/documenso/documenso/issues">Issues</a>
|
||||
·
|
||||
<a href="https://documen.so/live">Upcoming Releases</a>
|
||||
<a href="https://github.com/documenso/documenso/milestones">Roadmap</a>
|
||||
·
|
||||
<a href="https://documen.so/roadmap">Roadmap</a>
|
||||
<a href="https://documen.so/launches">Upcoming Launches</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
@@ -27,16 +27,29 @@
|
||||
<a href="https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/documenso/documenso">
|
||||
<img alt="open in devcontainer" src="https://img.shields.io/static/v1?label=Dev%20Containers&message=Enabled&color=blue&logo=visualstudiocode" />
|
||||
</a>
|
||||
<a href="CODE_OF_CONDUCT.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg" alt="Contributor Covenant"></a>
|
||||
<a href="code_of_conduct.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg" alt="Contributor Covenant"></a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://github.com/documenso/documenso/assets/13398220/d96ed533-6f34-4a97-be9b-442bdb189c69" style="width: 80%;" />
|
||||
<div>
|
||||
<img style="display: block; height: 120px; width: 24%"
|
||||
src="https://github.com/documenso/documenso/assets/1309312/67e08c98-c153-4115-aa2d-77979bb12c94)">
|
||||
<img style="display: block; height: 120px; width: 24%"
|
||||
src="https://github.com/documenso/documenso/assets/1309312/040cfbae-3438-4ca3-87f2-ce52c793dcaf">
|
||||
<img style="display: block; height: 120px; width: 24%"
|
||||
src="https://github.com/documenso/documenso/assets/1309312/72d445be-41e5-4936-bdba-87ef8e70fa09">
|
||||
<img style="display: block; height: 120px; width: 24%"
|
||||
src="https://github.com/documenso/documenso/assets/1309312/d7b86c0f-a755-4476-a022-a608db2c4633">
|
||||
<img style="display: block; height: 120px; width: 24%"
|
||||
src=https://github.com/documenso/documenso/assets/1309312/c0f55116-ab82-433f-a266-f3fc8571d69f">
|
||||
</div>
|
||||
|
||||
## About Documenso
|
||||
## About this project
|
||||
|
||||
Signing documents digitally should be fast and easy and should be the best practice for every document signed worldwide. This is technically quite easy today, but it also introduces a new party to every signature: The signing tool providers. While this is not a problem in itself, it should make us think about how we want these providers of trust to work. Documenso aims to be the world's most trusted document-signing tool. This trust is built by empowering you to self-host Documenso and review how it works under the hood.
|
||||
Signing documents digitally should be fast and easy and should be the best practice for every document signed worldwide.
|
||||
|
||||
This is technically quite easy today, but it also introduces a new party to every signature: The signing tool providers. While this is not a problem in itself, it should make us think about how we want these providers of trust to work.
|
||||
|
||||
Documenso aims to be the world's most trusted document-signing tool. This trust is built by empowering you to self-host Documenso and review how it works under the hood.
|
||||
|
||||
Join us in creating the next generation of open trust infrastructure.
|
||||
|
||||
@@ -49,6 +62,8 @@ Join us in creating the next generation of open trust infrastructure.
|
||||
|
||||
## Community and Next Steps 🎯
|
||||
|
||||
We're currently working on a redesign of the application, including a revamp of the codebase, so Documenso can be more intuitive to use and robust to develop upon.
|
||||
|
||||
- Check out the first source code release in this repository and test it.
|
||||
- Tell us what you think in the [Discussions](https://github.com/documenso/documenso/discussions).
|
||||
- Join the [Discord server](https://documen.so/discord) for any questions and getting to know to other community members.
|
||||
@@ -68,28 +83,19 @@ Contact us if you are interested in our Enterprise plan for large organizations
|
||||
|
||||
## Tech Stack
|
||||
|
||||
<p align="left">
|
||||
<a href="https://www.typescriptlang.org"><img src="https://shields.io/badge/TypeScript-3178C6?logo=TypeScript&logoColor=FFF&style=flat-square" alt="TypeScript"></a>
|
||||
<a href="https://prisma.io"><img width="122" height="20" src="http://made-with.prisma.io/indigo.svg" alt="Made with Prisma" /></a>
|
||||
<a href="https://tailwindcss.com/"><img src="https://img.shields.io/badge/tailwindcss-0F172A?&logo=tailwindcss" alt="Tailwind CSS"></a>
|
||||
<a href=""><img src="" alt=""></a>
|
||||
<a href=""><img src="" alt=""></a>
|
||||
<a href=""><img src="" alt=""></a>
|
||||
<a href=""><img src="" alt=""></a>
|
||||
<a href=""><img src="" alt=""></a>
|
||||
</p>
|
||||
|
||||
- [Typescript](https://www.typescriptlang.org/) - Language
|
||||
- [ReactRouter](https://reactrouter.com/) - Framework
|
||||
- [Next.js](https://nextjs.org/) - Framework
|
||||
- [Prisma](https://www.prisma.io/) - ORM
|
||||
- [Tailwind](https://tailwindcss.com/) - CSS
|
||||
- [shadcn/ui](https://ui.shadcn.com/) - Component Library
|
||||
- [NextAuth.js](https://next-auth.js.org/) - Authentication
|
||||
- [react-email](https://react.email/) - Email Templates
|
||||
- [tRPC](https://trpc.io/) - API
|
||||
- [@documenso/pdf-sign](https://www.npmjs.com/package/@documenso/pdf-sign) - PDF Signatures (launching soon)
|
||||
- [Node SignPDF](https://github.com/vbuch/node-signpdf) - Digital Signature
|
||||
- [React-PDF](https://github.com/wojtekmaj/react-pdf) - Viewing PDFs
|
||||
- [PDF-Lib](https://github.com/Hopding/pdf-lib) - PDF manipulation
|
||||
- [Stripe](https://stripe.com/) - Payments
|
||||
- [Vercel](https://vercel.com) - Hosting
|
||||
|
||||
<!-- - Support for [opensignpdf (requires Java on server)](https://github.com/open-pdf-sign) is currently planned. -->
|
||||
|
||||
@@ -99,7 +105,7 @@ Contact us if you are interested in our Enterprise plan for large organizations
|
||||
|
||||
To run Documenso locally, you will need
|
||||
|
||||
- Node.js (v22 or above)
|
||||
- Node.js
|
||||
- Postgres SQL Database
|
||||
- Docker (optional)
|
||||
|
||||
@@ -109,12 +115,10 @@ To run Documenso locally, you will need
|
||||
|
||||
Want to get up and running quickly? Follow these steps:
|
||||
|
||||
1. [Fork this repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) to your GitHub account.
|
||||
|
||||
After forking the repository, clone it to your local device by using the following command:
|
||||
1. [Clone the repository](https://help.github.com/articles/cloning-a-repository/) it to your local device.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/<your-username>/documenso
|
||||
git clone https://github.com/documenso/documenso
|
||||
```
|
||||
|
||||
2. Set up your `.env` file using the recommendations in the `.env.example` file. Alternatively, just run `cp .env.example .env` to get started with our handpicked defaults.
|
||||
@@ -135,25 +139,21 @@ npm run d
|
||||
|
||||
1. **App** - http://localhost:3000
|
||||
2. **Incoming Mail Access** - http://localhost:9000
|
||||
3. **Database Connection Details**
|
||||
|
||||
3. **Database Connection Details**
|
||||
- **Port**: 54320
|
||||
- **Connection**: Use your favorite database client to connect using the provided port.
|
||||
|
||||
4. **S3 Storage Dashboard** - http://localhost:9001
|
||||
|
||||
## Developer Setup
|
||||
|
||||
### Manual Setup
|
||||
|
||||
Follow these steps to setup Documenso on your local machine:
|
||||
|
||||
1. [Fork this repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) to your GitHub account.
|
||||
|
||||
After forking the repository, clone it to your local device by using the following command:
|
||||
1. [Clone the repository](https://help.github.com/articles/cloning-a-repository/) it to your local device.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/<your-username>/documenso
|
||||
git clone https://github.com/documenso/documenso
|
||||
```
|
||||
|
||||
2. Run `npm i` in the root directory
|
||||
@@ -162,8 +162,10 @@ git clone https://github.com/<your-username>/documenso
|
||||
|
||||
4. Set the following environment variables:
|
||||
|
||||
- NEXTAUTH_URL
|
||||
- NEXTAUTH_SECRET
|
||||
- NEXT_PUBLIC_WEBAPP_URL
|
||||
- NEXT_PUBLIC_MARKETING_URL
|
||||
- NEXT_PRIVATE_DATABASE_URL
|
||||
- NEXT_PRIVATE_DIRECT_DATABASE_URL
|
||||
- NEXT_PRIVATE_SMTP_FROM_NAME
|
||||
@@ -171,11 +173,9 @@ git clone https://github.com/<your-username>/documenso
|
||||
|
||||
5. Create the database schema by running `npm run prisma:migrate-dev`
|
||||
|
||||
6. Run `npm run translate:compile` in the root directory to compile lingui
|
||||
6. Run `npm run dev` in the root directory to start
|
||||
|
||||
7. Run `npm run dev` in the root directory to start
|
||||
|
||||
8. Register a new user at http://localhost:3000/signup
|
||||
7. Register a new user at http://localhost:3000/signup
|
||||
|
||||
---
|
||||
|
||||
@@ -193,29 +193,16 @@ git clone https://github.com/<your-username>/documenso
|
||||
|
||||
We support DevContainers for VSCode. [Click here to get started.](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/documenso/documenso)
|
||||
|
||||
### Video walkthrough
|
||||
|
||||
If you're a visual learner and prefer to watch a video walkthrough of setting up Documenso locally, check out this video:
|
||||
|
||||
[](https://youtu.be/Y0ppIQrEnZs)
|
||||
|
||||
## Docker
|
||||
|
||||
We provide a Docker container for Documenso, which is published on both DockerHub and GitHub Container Registry.
|
||||
|
||||
- DockerHub: [https://hub.docker.com/r/documenso/documenso](https://hub.docker.com/r/documenso/documenso)
|
||||
- GitHub Container Registry: [https://ghcr.io/documenso/documenso](https://ghcr.io/documenso/documenso)
|
||||
|
||||
You can pull the Docker image from either of these registries and run it with your preferred container hosting provider.
|
||||
|
||||
Please note that you will need to provide environment variables for connecting to the database, mailserver, and so forth.
|
||||
|
||||
For detailed instructions on how to configure and run the Docker container, please refer to the [Docker README](./docker/README.md) in the `docker` directory.
|
||||
🚧 Docker containers and images are current in progress. We are actively working on bringing a simple Docker build and publish pipeline for Documenso.
|
||||
|
||||
## Self Hosting
|
||||
|
||||
We support a variety of deployment methods, and are actively working on adding more. Stay tuned for updates!
|
||||
|
||||
> Please note that the below deployment methods are for v0.9, we will update these to v1.0 once it has been released.
|
||||
|
||||
### Fetch, configure, and build
|
||||
|
||||
First, clone the code from Github:
|
||||
@@ -232,33 +219,34 @@ cp .env.example .env
|
||||
|
||||
The following environment variables must be set:
|
||||
|
||||
- `NEXTAUTH_SECRET`
|
||||
- `NEXT_PUBLIC_WEBAPP_URL`
|
||||
- `NEXT_PRIVATE_DATABASE_URL`
|
||||
- `NEXT_PRIVATE_DIRECT_DATABASE_URL`
|
||||
- `NEXT_PRIVATE_SMTP_FROM_NAME`
|
||||
- `NEXT_PRIVATE_SMTP_FROM_ADDRESS`
|
||||
* `NEXTAUTH_URL`
|
||||
* `NEXTAUTH_SECRET`
|
||||
* `NEXT_PUBLIC_WEBAPP_URL`
|
||||
* `NEXT_PUBLIC_MARKETING_URL`
|
||||
* `NEXT_PRIVATE_DATABASE_URL`
|
||||
* `NEXT_PRIVATE_DIRECT_DATABASE_URL`
|
||||
* `NEXT_PRIVATE_SMTP_FROM_NAME`
|
||||
* `NEXT_PRIVATE_SMTP_FROM_ADDRESS`
|
||||
|
||||
> If you are using a reverse proxy in front of Documenso, don't forget to provide the public URL for the `NEXT_PUBLIC_WEBAPP_URL` variable!
|
||||
> If you are using a reverse proxy in front of Documenso, don't forget to provide the public URL for both `NEXTAUTH_URL` and `NEXT_PUBLIC_WEBAPP_URL` variables!
|
||||
|
||||
Now you can install the dependencies and build it:
|
||||
|
||||
```
|
||||
npm i
|
||||
npm run build
|
||||
npm run:build:web
|
||||
npm run prisma:migrate-deploy
|
||||
```
|
||||
|
||||
Finally, you can start it with:
|
||||
|
||||
```
|
||||
cd apps/remix
|
||||
npm run start
|
||||
```
|
||||
|
||||
This will start the server on `localhost:3000`. For now, any reverse proxy can then do the frontend and SSL termination.
|
||||
|
||||
> If you want to run with another port than 3000, you can start the application with `next -p <ANY PORT>` from the `apps/remix` folder.
|
||||
> If you want to run with another port than 3000, you can start the application with `next -p <ANY PORT>` from the `apps/web` folder.
|
||||
|
||||
### Run as a service
|
||||
|
||||
@@ -273,7 +261,7 @@ After=network.target
|
||||
Environment=PATH=/path/to/your/node/binaries
|
||||
Type=simple
|
||||
User=www-data
|
||||
WorkingDirectory=/var/www/documenso/apps/remix
|
||||
WorkingDirectory=/var/www/documenso/apps/web
|
||||
ExecStart=/usr/bin/next start -p 3500
|
||||
TimeoutSec=15
|
||||
Restart=always
|
||||
@@ -284,20 +272,12 @@ WantedBy=multi-user.target
|
||||
|
||||
### Railway
|
||||
|
||||
[](https://railway.app/template/bG6D4p)
|
||||
[](https://railway.app/template/DjrRRX)
|
||||
|
||||
### Render
|
||||
|
||||
[](https://render.com/deploy?repo=https://github.com/documenso/documenso)
|
||||
|
||||
### Koyeb
|
||||
|
||||
[](https://app.koyeb.com/deploy?type=git&repository=github.com/documenso/documenso&branch=main&name=documenso-app&builder=dockerfile&dockerfile=/docker/Dockerfile)
|
||||
|
||||
## Elestio
|
||||
|
||||
[](https://elest.io/open-source/documenso)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### I'm not receiving any emails when using the developer quickstart.
|
||||
@@ -308,7 +288,7 @@ The Web UI can be found at http://localhost:9000, while the SMTP port will be on
|
||||
|
||||
### Support IPv6
|
||||
|
||||
If you are deploying to a cluster that uses only IPv6, You can use a custom command to pass a parameter to the Remix start command
|
||||
If you are deploying to a cluster that uses only IPv6, You can use a custom command to pass a parameter to the Next.js start command
|
||||
|
||||
For local docker run
|
||||
|
||||
|
||||
+5
-17
@@ -10,26 +10,14 @@ For the digital signature of your documents you need a signing certificate in .p
|
||||
|
||||
`openssl req -new -x509 -key private.key -out certificate.crt -days 365`
|
||||
|
||||
This will prompt you to enter some information, such as the Common Name (CN) for the certificate. Make sure you enter the correct information. The `-days` parameter sets the number of days for which the certificate is valid.
|
||||
This will prompt you to enter some information, such as the Common Name (CN) for the certificate. Make sure you enter the correct information. The -days parameter sets the number of days for which the certificate is valid.
|
||||
|
||||
3. Combine the private key and the self-signed certificate to create the p12 certificate. You can run the following commands to do this:
|
||||
3. Combine the private key and the self-signed certificate to create the p12 certificate. You can run the following command to do this:
|
||||
|
||||
```bash
|
||||
# Set certificate password securely (won't appear in command history)
|
||||
read -s -p "Enter certificate password: " CERT_PASS
|
||||
echo
|
||||
|
||||
# Create the p12 certificate using the environment variable
|
||||
openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt \
|
||||
-password env:CERT_PASS \
|
||||
-keypbe PBE-SHA1-3DES \
|
||||
-certpbe PBE-SHA1-3DES \
|
||||
-macalg sha1
|
||||
```
|
||||
`openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt`
|
||||
|
||||
4. **IMPORTANT**: A certificate password is required to prevent signing failures. Make sure to use a strong password (minimum 4 characters) when prompted. Certificates without passwords will cause "Failed to get private key bags" errors during document signing.
|
||||
|
||||
5. Place the certificate `/apps/remix/resources/certificate.p12` (If the path does not exist, it needs to be created)
|
||||
4. You will be prompted to enter a password for the p12 file. Choose a strong password and remember it, as you will need it to use the certificate (**can be empty for dev certificates**)
|
||||
5. Place the certificate `/apps/web/resources/certificate.p12`
|
||||
|
||||
## Docker
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# next-sitemap output
|
||||
/public/sitemap.xml
|
||||
/public/robots.txt
|
||||
/public/sitemap-*.xml
|
||||
@@ -1 +0,0 @@
|
||||
# @documenso/documentation
|
||||
@@ -1,35 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
|
||||
type CallToActionProps = {
|
||||
className?: string;
|
||||
utmSource?: string;
|
||||
};
|
||||
|
||||
export const CallToAction = ({ className, utmSource = 'generic-cta' }: CallToActionProps) => {
|
||||
return (
|
||||
<Card spotlight className={className}>
|
||||
<CardContent className="flex flex-col items-center justify-center p-12">
|
||||
<h2 className="text-center text-2xl font-bold">Looking for the managed solution?</h2>
|
||||
|
||||
<p className="text-muted-foreground mt-4 max-w-[55ch] text-center leading-normal">
|
||||
You can get started with Documenso in minutes. We handle the infrastructure, so you can
|
||||
focus on signing documents.
|
||||
</p>
|
||||
|
||||
<Button
|
||||
className="focus-visible:ring-ring ring-offset-background bg-primary text-primary-foreground hover:bg-primary/90text-sm mt-8 inline-flex items-center justify-center rounded-full border font-medium no-underline transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
|
||||
variant="default"
|
||||
size="lg"
|
||||
asChild
|
||||
>
|
||||
<Link href={`https://app.documenso.com/signup?utm_source=${utmSource}`} target="_blank">
|
||||
Get started
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
/** @type {import('next-sitemap').IConfig} */
|
||||
module.exports = {
|
||||
siteUrl: 'https://docs.documenso.com', // Replace with your actual site URL
|
||||
generateRobotsTxt: true, // Generates robots.txt
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { NextConfig } from 'next';
|
||||
|
||||
import nextra from 'nextra';
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
transpilePackages: [
|
||||
'@documenso/assets',
|
||||
'@documenso/lib',
|
||||
'@documenso/tailwind-config',
|
||||
'@documenso/trpc',
|
||||
'@documenso/ui',
|
||||
],
|
||||
};
|
||||
|
||||
const withNextra = nextra({
|
||||
theme: 'nextra-theme-docs',
|
||||
themeConfig: './theme.config.tsx',
|
||||
codeHighlight: true,
|
||||
});
|
||||
|
||||
export default withNextra(nextConfig);
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "@documenso/documentation",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3002",
|
||||
"build": "next build",
|
||||
"start": "next start -p 3002",
|
||||
"lint:fix": "next lint --fix",
|
||||
"clean": "rimraf .next && rimraf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@documenso/assets": "*",
|
||||
"@documenso/lib": "*",
|
||||
"@documenso/tailwind-config": "*",
|
||||
"@documenso/trpc": "*",
|
||||
"@documenso/ui": "*",
|
||||
"next": "15.5.9",
|
||||
"next-plausible": "^3.12.5",
|
||||
"nextra": "^3",
|
||||
"nextra-theme-docs": "^3",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "18.3.27",
|
||||
"@types/react-dom": "^18",
|
||||
"pagefind": "^1.2.0",
|
||||
"typescript": "5.6.2"
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React from 'react';
|
||||
|
||||
import { PlausibleProvider } from '../providers/plausible';
|
||||
import '../styles.css';
|
||||
|
||||
export type AppProps = {
|
||||
Component: React.ComponentType<any>;
|
||||
pageProps: any;
|
||||
};
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<PlausibleProvider>
|
||||
<Component {...pageProps} />
|
||||
</PlausibleProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
export default {
|
||||
index: {
|
||||
type: 'page',
|
||||
title: 'Home',
|
||||
display: 'hidden',
|
||||
theme: {
|
||||
timestamp: false,
|
||||
},
|
||||
},
|
||||
users: {
|
||||
type: 'page',
|
||||
title: 'Users',
|
||||
},
|
||||
developers: {
|
||||
type: 'page',
|
||||
title: 'Developers',
|
||||
},
|
||||
updates: {
|
||||
title: "What's New",
|
||||
type: 'menu',
|
||||
items: {
|
||||
changelog: {
|
||||
title: 'Changelog',
|
||||
href: 'https://documenso.com/changelog',
|
||||
newWindow: true,
|
||||
},
|
||||
blog: {
|
||||
title: 'Blog',
|
||||
href: 'https://documenso.com/blog',
|
||||
newWindow: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
export default {
|
||||
index: 'Introduction',
|
||||
'-- Development & Deployment': {
|
||||
type: 'separator',
|
||||
title: 'Development & Deployment',
|
||||
},
|
||||
'local-development': 'Local Development',
|
||||
'developer-mode': 'Developer Mode',
|
||||
'self-hosting': 'Self Hosting',
|
||||
contributing: 'Contributing',
|
||||
'-- API & Integration Guides': {
|
||||
type: 'separator',
|
||||
title: 'API & Integration Guides',
|
||||
},
|
||||
'public-api': 'Public API',
|
||||
embedding: 'Embedded Signing',
|
||||
'embedded-authoring': 'Embedded Authoring',
|
||||
webhooks: 'Webhooks',
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
export default {
|
||||
index: 'Getting Started',
|
||||
'contributing-translations': 'Contributing Translations',
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
---
|
||||
title: Contributing Translations
|
||||
description: Learn how to contribute translations to Documenso and become part of our community.
|
||||
---
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# Contributing Translations
|
||||
|
||||
We are always open for help with translations! Currently we utilise AI to generate the initial translations for new languages, which are then improved over time by our awesome community.
|
||||
|
||||
If you are looking for development notes on translations, you can find them [here](/developers/local-development/translations).
|
||||
|
||||
<Callout type="info">
|
||||
Contributions are made through GitHub Pull Requests, so you will need a GitHub account to
|
||||
contribute.
|
||||
</Callout>
|
||||
|
||||
## Overview
|
||||
|
||||
We store our translations in PO files, which are located in our GitHub repository [here](https://github.com/documenso/documenso/tree/main/packages/lib/translations).
|
||||
|
||||
The translation files are organized into folders represented by their respective language codes (`en` for English, `de` for German, etc).
|
||||
|
||||
Each PO file contains translations which look like this:
|
||||
|
||||
```po
|
||||
#: apps/remix/app/(signing)/sign/[token]/no-longer-available.tsx:61
|
||||
msgid "Want to send slick signing links like this one? <0>Check out Documenso.</0>"
|
||||
msgstr "Möchten Sie auffällige Signatur-Links wie diesen senden? <0>Überprüfen Sie Documenso.</0>"
|
||||
```
|
||||
|
||||
- `msgid`: The original text in English (never edit this manually)
|
||||
- `msgstr`: The translated text in the target language
|
||||
|
||||
<Callout type="warning">
|
||||
Notice the `<0>` tags? These represent HTML elements and must remain in both the `msgid` and `msgstr`. Make sure to translate the content between these tags while keeping the tags intact.
|
||||
</Callout>
|
||||
|
||||
## How to Contribute
|
||||
|
||||
### Updating Existing Translations
|
||||
|
||||
1. Fork the repository.
|
||||
2. Navigate to the appropriate language folder and open the PO file you want to update.
|
||||
3. Make your changes, ensuring you follow the PO file format.
|
||||
4. Commit your changes with a message such as `chore: update German translations`
|
||||
5. Create a Pull Request.
|
||||
|
||||
### Adding a New Language
|
||||
|
||||
If you want to add translations for a language that doesn't exist yet:
|
||||
|
||||
1. Create an issue in our GitHub repository requesting the addition of the new language.
|
||||
2. Wait for our team to review and approve the request.
|
||||
3. Once approved, we will set up the necessary files and kickstart the translations with AI to provide initial coverage.
|
||||
|
||||
## Need Help?
|
||||
|
||||
<Callout type="info">
|
||||
If you have any questions, hop into our [Discord](https://documen.so/discord) and ask us directly!
|
||||
</Callout>
|
||||
|
||||
Thank you for helping make Documenso more accessible to users around the world!
|
||||
@@ -1,117 +0,0 @@
|
||||
---
|
||||
title: Getting started
|
||||
description: Learn how to contribute to Documenso and become part of our community.
|
||||
---
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# Contributing to Documenso
|
||||
|
||||
If you plan to contribute to Documenso, please take a moment to feel awesome. People like you are what open source is about. Any contributions, no matter how big or small, are highly appreciated.
|
||||
|
||||
This guide will help you get started with contributing to Documenso.
|
||||
|
||||
## Before Getting Started
|
||||
|
||||
<Steps>
|
||||
|
||||
### Check the Existing Issues and Pull Requests
|
||||
|
||||
Search the existing [issues](https://github.com/documenso/documenso/issues) to see if someone else reported the same issue. Or, check the [existing PRs](https://github.com/documenso/documenso/pulls) to see if someone else is already working on the same thing.
|
||||
|
||||
### Creating a New Issue
|
||||
|
||||
If there is no issue or PR for the problem you are facing, feel free to create a new issue. Make sure to provide as much detail as possible, including the steps to reproduce the issue.
|
||||
|
||||
### Picking an Existing Issue
|
||||
|
||||
If you pick an existing issue, take into consideration the discussion on the issue.
|
||||
|
||||
### Contributor License Agreement
|
||||
|
||||
Accept the [Contributor License Agreement](https://documen.so/cla) to ensure we can accept your contributions.
|
||||
|
||||
</Steps>
|
||||
|
||||
## Taking Issues
|
||||
|
||||
Before taking an issue, ensure that:
|
||||
|
||||
- The issue has been assigned the public label.
|
||||
- The issue is clearly defined and understood.
|
||||
- No one has been assigned to the issue.
|
||||
- No one has expressed the intention to work on it.
|
||||
|
||||
After that:
|
||||
|
||||
1. Comment on the issue with your intention to work on it.
|
||||
2. Start working on the issue.
|
||||
|
||||
Feel free to ask for help, clarification or guidance if needed. We are here to help you.
|
||||
|
||||
## Developing
|
||||
|
||||
The development branch is `main`, and all pull requests should be made against this branch. Here's how you can get started with developing:
|
||||
|
||||
<Steps>
|
||||
|
||||
### Set Up Documenso Locally
|
||||
|
||||
To set up your local environment, check out the [local development](/developers/local-development) guide.
|
||||
|
||||
### Pick a Task
|
||||
|
||||
Find an issue to work on or create a new one.
|
||||
|
||||
> Before working on an issue, ensure that no one else is working on it. If no one is assigned to the issue, you can pick it up by leaving a comment and asking to assign it to you.
|
||||
|
||||
Before creating a new issue, check the existing issues to see if someone else has already reported it.
|
||||
|
||||
### Create a New Branch
|
||||
|
||||
After you're assigned an issue, you can start working on it. Create a new branch for your feature or bug fix.
|
||||
|
||||
When creating a branch, make sure that the branch name:
|
||||
|
||||
- starts with the correct prefix: `feat/` for new features, `fix/` for bug fixes, etc.
|
||||
- includes the issue ID you are working on (if applicable).
|
||||
- is descriptive.
|
||||
|
||||
```sh
|
||||
git checkout -b feat/issue-id-your-branch-name
|
||||
|
||||
## Example
|
||||
git checkout -b feat/1234-add-share-button-to-articles
|
||||
```
|
||||
|
||||
In the pull request description, include `references #yyyy` or `fixes #yyyy` to link it to the issue you are working on.
|
||||
|
||||
### Implement Your Changes
|
||||
|
||||
Start working on the issue you picked up and implement the changes. Make sure to test your changes locally and ensure that they work as expected.
|
||||
|
||||
### Open a Pull Request
|
||||
|
||||
After implementing your changes, open a pull request against the `main` branch.
|
||||
|
||||
</Steps>
|
||||
|
||||
<Callout type="info">
|
||||
If you need help getting started, [join us on Discord](https://documen.so/discord).
|
||||
</Callout>
|
||||
|
||||
## Building
|
||||
|
||||
Before pushing code or creating pull requests, please ensure you can successfully create a successful production build. You can build the project by running the following command in your terminal:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Once the project builds successfully, you can push your code changes or create a pull request.
|
||||
|
||||
<Callout type="info">
|
||||
Remember to run tests and perform any necessary checks before finalizing your changes. As a
|
||||
result, we can collaborate more effectively and maintain a high standard of code quality in our
|
||||
project.
|
||||
</Callout>
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
title: Field Coordinates
|
||||
description: Learn how to get the coordinates of a field in a document.
|
||||
---
|
||||
|
||||
## Field Coordinates
|
||||
|
||||
Field coordinates represent the position of a field in a document. They are returned in the `pageX`, `pageY`, `width` and `height` properties of the field.
|
||||
|
||||
To enable field coordinates, you can use the `devmode` query parameter.
|
||||
|
||||
```bash
|
||||
# Legacy editor
|
||||
|
||||
https://app.documenso.com/t/<team-url>/documents/<envelope-id>/legacy_editor?devmode=true
|
||||
```
|
||||
|
||||

|
||||
|
||||
```bash
|
||||
# New editor
|
||||
|
||||
https://app.documenso.com/t/<team-url>/documents/<envelope-id>/edit?step=addFields&devmode=true
|
||||
```
|
||||
|
||||

|
||||
@@ -1,396 +0,0 @@
|
||||
---
|
||||
title: Embedded Authoring
|
||||
description: Learn how to use embedded authoring to create documents and templates in your application
|
||||
---
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="info">
|
||||
The embedded authoring feature is an enterprise only feature. Please contact us if you are
|
||||
interested in using it.
|
||||
</Callout>
|
||||
|
||||
# Embedded Authoring
|
||||
|
||||
In addition to embedding signing experiences, Documenso now supports embedded authoring, allowing you to integrate document and template creation and editing directly within your application.
|
||||
|
||||
## Embedded Signing vs Embedded Authoring
|
||||
|
||||
Embedded signing allows you to embed your Documenso documents into your application for signing. Your users will be able to sign the document directly in your application.
|
||||
|
||||
Embedded authoring allows you to integrate Documenso's document and template creation and editing into your application. You will be able to create and edit documents and templates directly in your application.
|
||||
|
||||
## How Embedded Authoring Works
|
||||
|
||||
The embedded authoring feature enables your users to create and edit documents and templates without leaving your application. This process works through secure presign tokens that authenticate the embedding session and manage permissions.
|
||||
|
||||
## Available Components
|
||||
|
||||
The SDK provides four authoring components:
|
||||
|
||||
- **`EmbedCreateDocumentV1`** - Create new documents
|
||||
- **`EmbedCreateTemplateV1`** - Create new templates
|
||||
- **`EmbedUpdateDocumentV1`** - Edit existing documents
|
||||
- **`EmbedUpdateTemplateV1`** - Edit existing templates
|
||||
|
||||
React Example:
|
||||
|
||||
```jsx
|
||||
import {
|
||||
EmbedCreateDocumentV1,
|
||||
EmbedCreateTemplateV1,
|
||||
EmbedUpdateDocumentV1,
|
||||
EmbedUpdateTemplateV1,
|
||||
} from '@documenso/embed-react';
|
||||
```
|
||||
|
||||
## Creating Documents
|
||||
|
||||
To implement document creation in your application, use the `EmbedCreateDocumentV1` component:
|
||||
|
||||
```jsx
|
||||
import { EmbedCreateDocumentV1 } from '@documenso/embed-react';
|
||||
|
||||
const DocumentCreator = () => {
|
||||
// You'll need to obtain a presign token using your API key
|
||||
const presignToken = 'YOUR_PRESIGN_TOKEN';
|
||||
|
||||
return (
|
||||
<div style={{ height: '800px', width: '100%' }}>
|
||||
<EmbedCreateDocument
|
||||
presignToken={presignToken}
|
||||
externalId="order-12345"
|
||||
onDocumentCreated={(data) => {
|
||||
console.log('Document created with ID:', data.documentId);
|
||||
console.log('External reference ID:', data.externalId);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Creating Templates
|
||||
|
||||
To create templates, use the `EmbedCreateTemplateV1` component:
|
||||
|
||||
```jsx
|
||||
import { EmbedCreateTemplateV1 } from '@documenso/embed-react';
|
||||
|
||||
const TemplateCreator = () => {
|
||||
const presignToken = 'YOUR_PRESIGN_TOKEN';
|
||||
|
||||
return (
|
||||
<div style={{ height: '800px', width: '100%' }}>
|
||||
<EmbedCreateTemplate
|
||||
presignToken={presignToken}
|
||||
externalId="template-12345"
|
||||
onTemplateCreated={(data) => {
|
||||
console.log('Template created with ID:', data.templateId);
|
||||
console.log('External reference ID:', data.externalId);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Updating Documents
|
||||
|
||||
To edit existing documents, use the `EmbedUpdateDocumentV1` component:
|
||||
|
||||
```jsx
|
||||
import { EmbedUpdateDocumentV1 } from '@documenso/embed-react';
|
||||
|
||||
const DocumentEditor = () => {
|
||||
const presignToken = 'YOUR_PRESIGN_TOKEN';
|
||||
const documentId = 123; // The ID of the document to edit
|
||||
|
||||
return (
|
||||
<div style={{ height: '800px', width: '100%' }}>
|
||||
<EmbedUpdateDocument
|
||||
presignToken={presignToken}
|
||||
documentId={documentId}
|
||||
externalId="order-12345"
|
||||
onlyEditFields={false}
|
||||
onDocumentUpdated={(data) => {
|
||||
console.log('Document updated:', data.documentId);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Updating Templates
|
||||
|
||||
To edit existing templates, use the `EmbedUpdateTemplateV1` component:
|
||||
|
||||
```jsx
|
||||
import { EmbedUpdateTemplateV1 } from '@documenso/embed-react';
|
||||
|
||||
const TemplateEditor = () => {
|
||||
const presignToken = 'YOUR_PRESIGN_TOKEN';
|
||||
const templateId = 456; // The ID of the template to edit
|
||||
|
||||
return (
|
||||
<div style={{ height: '800px', width: '100%' }}>
|
||||
<EmbedUpdateTemplate
|
||||
presignToken={presignToken}
|
||||
templateId={templateId}
|
||||
externalId="template-12345"
|
||||
onlyEditFields={false}
|
||||
onTemplateUpdated={(data) => {
|
||||
console.log('Template updated:', data.templateId);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Obtaining a Presign Token
|
||||
|
||||
Before using any of the authoring components, you'll need to obtain a presign token from your backend. This token authorizes the embedding session.
|
||||
|
||||
You can create a presign token by making a request to:
|
||||
|
||||
```
|
||||
POST /api/v2/embedding/create-presign-token
|
||||
```
|
||||
|
||||
This API endpoint requires authentication with your Documenso API key. The token has a default expiration of 1 hour, but you can customize this duration based on your security requirements.
|
||||
|
||||
You can find more details on this request at our [API Documentation](https://openapi.documenso.com/reference#tag/embedding)
|
||||
|
||||
## Configuration Options
|
||||
|
||||
All authoring components accept the following configuration options:
|
||||
|
||||
| Option | Type | Description |
|
||||
| ------------------ | ------- | -------------------------------------------------------------------------- |
|
||||
| `presignToken` | string | **Required**. The authentication token for the embedding session. |
|
||||
| `externalId` | string | Optional reference ID from your system to link with the document/template. |
|
||||
| `host` | string | Optional custom host URL. Defaults to `https://app.documenso.com`. |
|
||||
| `css` | string | Optional custom CSS to style the embedded component. |
|
||||
| `cssVars` | object | Optional CSS variables for colors, spacing, and more. |
|
||||
| `darkModeDisabled` | boolean | Optional flag to disable dark mode. |
|
||||
| `className` | string | Optional CSS class name for the iframe. |
|
||||
| `additionalProps` | object | Optional additional props to pass to the iframe (for testing features). |
|
||||
| `features` | object | Optional feature toggles to customize the authoring experience. |
|
||||
|
||||
### Update Component Specific Props
|
||||
|
||||
The `EmbedUpdateDocument` and `EmbedUpdateTemplate` components also accept:
|
||||
|
||||
| Option | Type | Description |
|
||||
| ---------------- | ------- | -------------------------------------------------------------------------------------------------------------- |
|
||||
| `documentId` | number | **Required for EmbedUpdateDocument**. The ID of the document to edit. |
|
||||
| `templateId` | number | **Required for EmbedUpdateTemplate**. The ID of the template to edit. |
|
||||
| `onlyEditFields` | boolean | Optional flag to restrict editing to fields only skipping the recipient configuration step (default: `false`). |
|
||||
|
||||
## Feature Toggles
|
||||
|
||||
You can customize the authoring experience by enabling or disabling specific features:
|
||||
|
||||
```jsx
|
||||
<EmbedCreateDocument
|
||||
presignToken="YOUR_PRESIGN_TOKEN"
|
||||
features={{
|
||||
allowConfigureSignatureTypes: true,
|
||||
allowConfigureLanguage: true,
|
||||
allowConfigureDateFormat: true,
|
||||
allowConfigureTimezone: true,
|
||||
allowConfigureRedirectUrl: true,
|
||||
allowConfigureCommunication: true,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## Handling Events
|
||||
|
||||
Each component provides callbacks for handling completion events:
|
||||
|
||||
### Document Events
|
||||
|
||||
```jsx
|
||||
<EmbedCreateDocument
|
||||
presignToken="YOUR_PRESIGN_TOKEN"
|
||||
externalId="order-12345"
|
||||
onDocumentCreated={(data) => {
|
||||
// Navigate to a success page
|
||||
navigate(`/documents/success?id=${data.documentId}`);
|
||||
|
||||
// Or update your database with the document ID
|
||||
updateOrderDocument(data.externalId, data.documentId);
|
||||
}}
|
||||
/>
|
||||
|
||||
<EmbedUpdateDocument
|
||||
presignToken="YOUR_PRESIGN_TOKEN"
|
||||
documentId={123}
|
||||
onDocumentUpdated={(data) => {
|
||||
console.log('Document updated:', data.documentId);
|
||||
// Handle document update
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### Template Events
|
||||
|
||||
```jsx
|
||||
<EmbedCreateTemplate
|
||||
presignToken="YOUR_PRESIGN_TOKEN"
|
||||
externalId="template-12345"
|
||||
onTemplateCreated={(data) => {
|
||||
console.log('Template created:', data.templateId);
|
||||
// Handle template creation
|
||||
}}
|
||||
/>
|
||||
|
||||
<EmbedUpdateTemplate
|
||||
presignToken="YOUR_PRESIGN_TOKEN"
|
||||
templateId={456}
|
||||
onTemplateUpdated={(data) => {
|
||||
console.log('Template updated:', data.templateId);
|
||||
// Handle template update
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
All event callbacks receive an object with:
|
||||
|
||||
- `documentId` or `templateId` - The ID of the created/updated document or template
|
||||
- `externalId` - Your external reference ID (if provided)
|
||||
|
||||
## Styling the Embedded Component
|
||||
|
||||
You can customize the appearance of the embedded component using standard CSS classes, custom CSS, and CSS variables:
|
||||
|
||||
```jsx
|
||||
<EmbedCreateDocument
|
||||
className="h-screen w-full rounded-lg border-none shadow-md"
|
||||
presignToken="YOUR_PRESIGN_TOKEN"
|
||||
css={`
|
||||
.documenso-embed {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
`}
|
||||
cssVars={{
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## Complete Integration Example
|
||||
|
||||
Here's a complete example of integrating document creation in a React application:
|
||||
|
||||
```tsx
|
||||
import { useState } from 'react';
|
||||
|
||||
import { EmbedCreateDocumentV1, EmbedUpdateDocumentV1 } from '@documenso/embed-react';
|
||||
|
||||
function DocumentManager() {
|
||||
// In a real application, you would fetch this token from your backend
|
||||
// using your API key at /api/v2/embedding/create-presign-token
|
||||
const presignToken = 'YOUR_PRESIGN_TOKEN';
|
||||
const [documentId, setDocumentId] = useState<number | null>(null);
|
||||
const [mode, setMode] = useState<'create' | 'edit'>('create');
|
||||
|
||||
if (documentId && mode === 'create') {
|
||||
return (
|
||||
<div>
|
||||
<h2>Document Created Successfully!</h2>
|
||||
<p>Document ID: {documentId}</p>
|
||||
<div>
|
||||
<button onClick={() => setMode('edit')}>Edit Document</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setDocumentId(null);
|
||||
setMode('create');
|
||||
}}
|
||||
>
|
||||
Create Another Document
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (mode === 'edit' && documentId) {
|
||||
return (
|
||||
<div style={{ height: '800px', width: '100%' }}>
|
||||
<button onClick={() => setMode('create')}>Back to Create</button>
|
||||
<EmbedUpdateDocument
|
||||
presignToken={presignToken}
|
||||
documentId={documentId}
|
||||
externalId="order-12345"
|
||||
onDocumentUpdated={(data) => {
|
||||
console.log('Document updated:', data.documentId);
|
||||
setMode('create');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ height: '800px', width: '100%' }}>
|
||||
<EmbedCreateDocument
|
||||
presignToken={presignToken}
|
||||
externalId="order-12345"
|
||||
features={{
|
||||
allowConfigureSignatureTypes: true,
|
||||
allowConfigureLanguage: true,
|
||||
allowConfigureDateFormat: true,
|
||||
allowConfigureTimezone: true,
|
||||
allowConfigureRedirectUrl: true,
|
||||
allowConfigureCommunication: true,
|
||||
}}
|
||||
onDocumentCreated={(data) => {
|
||||
setDocumentId(data.documentId);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DocumentManager;
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Using Additional Props
|
||||
|
||||
You can pass additional props to the iframe for testing features before they're officially supported:
|
||||
|
||||
```jsx
|
||||
<EmbedCreateDocument
|
||||
presignToken="YOUR_PRESIGN_TOKEN"
|
||||
additionalProps={{
|
||||
experimentalFeature: true,
|
||||
customSetting: 'value',
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### Restricting To Only Field Editing
|
||||
|
||||
When updating documents or templates, you can restrict editing to fields only skipping the recipient configuration step:
|
||||
|
||||
```jsx
|
||||
<EmbedUpdateDocument
|
||||
presignToken="YOUR_PRESIGN_TOKEN"
|
||||
documentId={123}
|
||||
onlyEditFields={true}
|
||||
onDocumentUpdated={(data) => {
|
||||
console.log('Fields updated:', data.documentId);
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
With embedded authoring, your users can seamlessly create and edit documents and templates within your application, enhancing the overall user experience and streamlining document workflows.
|
||||
@@ -1,10 +0,0 @@
|
||||
export default {
|
||||
index: 'Get Started',
|
||||
react: 'React Integration',
|
||||
vue: 'Vue Integration',
|
||||
svelte: 'Svelte Integration',
|
||||
solid: 'Solid Integration',
|
||||
preact: 'Preact Integration',
|
||||
angular: 'Angular Integration',
|
||||
'css-variables': 'CSS Variables',
|
||||
};
|
||||
@@ -1,90 +0,0 @@
|
||||
---
|
||||
title: Angular Integration
|
||||
description: Learn how to use our embedding SDK within your Angular application.
|
||||
---
|
||||
|
||||
# Angular Integration
|
||||
|
||||
Our Angular SDK provides a simple way to embed a signing experience within your Angular application. It supports both direct link templates and signing tokens.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the SDK, run the following command:
|
||||
|
||||
```bash
|
||||
npm install @documenso/embed-angular
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To embed a signing experience, you'll need to provide the token for the document you want to embed. This can be done in a few different ways, depending on your use case.
|
||||
|
||||
### Direct Link Template
|
||||
|
||||
If you have a direct link template, you can simply provide the token for the template to the `EmbedDirectTemplate` component.
|
||||
|
||||
```typescript
|
||||
import { Component } from '@angular/core';
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-embedding',
|
||||
template: `
|
||||
<embed-direct-template [token]="token" />
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [EmbedDirectTemplate],
|
||||
})
|
||||
export class EmbeddingComponent {
|
||||
token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
}
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| email | string (optional) | The email the signer that will be used by default for signing |
|
||||
| lockEmail | boolean (optional) | Whether or not the email field should be locked disallowing modifications |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
| onFieldSigned | function (optional) | A callback function that will be called when a field is signed |
|
||||
| onFieldUnsigned | function (optional) | A callback function that will be called when a field is unsigned |
|
||||
|
||||
### Signing Token
|
||||
|
||||
If you have a signing token, you can provide it to the `EmbedSignDocument` component.
|
||||
|
||||
```typescript
|
||||
import { Component } from '@angular/core';
|
||||
import { EmbedSignDocument } from '@documenso/embed-angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-embedding',
|
||||
template: `
|
||||
<embed-sign-document [token]="token" />
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [EmbedSignDocument],
|
||||
})
|
||||
export class EmbeddingComponent {
|
||||
token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
}
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
@@ -1,197 +0,0 @@
|
||||
---
|
||||
title: CSS Variables
|
||||
description: Learn about all available CSS variables for customizing your embedded signing experience
|
||||
---
|
||||
|
||||
# CSS Variables
|
||||
|
||||
Platform customers have access to a comprehensive set of CSS variables that can be used to customize the appearance of the embedded signing experience. These variables control everything from colors to spacing and can be used to match your application's design system.
|
||||
|
||||
## Available Variables
|
||||
|
||||
### Colors
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ----------------------- | ---------------------------------- | -------------- |
|
||||
| `background` | Base background color | System default |
|
||||
| `foreground` | Base text color | System default |
|
||||
| `muted` | Muted/subtle background color | System default |
|
||||
| `mutedForeground` | Muted/subtle text color | System default |
|
||||
| `popover` | Popover/dropdown background color | System default |
|
||||
| `popoverForeground` | Popover/dropdown text color | System default |
|
||||
| `card` | Card background color | System default |
|
||||
| `cardBorder` | Card border color | System default |
|
||||
| `cardBorderTint` | Card border tint/highlight color | System default |
|
||||
| `cardForeground` | Card text color | System default |
|
||||
| `fieldCard` | Field card background color | System default |
|
||||
| `fieldCardBorder` | Field card border color | System default |
|
||||
| `fieldCardForeground` | Field card text color | System default |
|
||||
| `widget` | Widget background color | System default |
|
||||
| `widgetForeground` | Widget text color | System default |
|
||||
| `border` | Default border color | System default |
|
||||
| `input` | Input field border color | System default |
|
||||
| `primary` | Primary action/button color | System default |
|
||||
| `primaryForeground` | Primary action/button text color | System default |
|
||||
| `secondary` | Secondary action/button color | System default |
|
||||
| `secondaryForeground` | Secondary action/button text color | System default |
|
||||
| `accent` | Accent/highlight color | System default |
|
||||
| `accentForeground` | Accent/highlight text color | System default |
|
||||
| `destructive` | Destructive/danger action color | System default |
|
||||
| `destructiveForeground` | Destructive/danger text color | System default |
|
||||
| `ring` | Focus ring color | System default |
|
||||
| `warning` | Warning/alert color | System default |
|
||||
|
||||
### Spacing and Layout
|
||||
|
||||
| Variable | Description | Default |
|
||||
| -------- | ------------------------------- | -------------- |
|
||||
| `radius` | Border radius size in REM units | System default |
|
||||
|
||||
## Usage Example
|
||||
|
||||
Here's how to use these variables in your embedding implementation:
|
||||
|
||||
```jsx
|
||||
const cssVars = {
|
||||
// Colors
|
||||
background: '#ffffff',
|
||||
foreground: '#000000',
|
||||
primary: '#0000ff',
|
||||
primaryForeground: '#ffffff',
|
||||
accent: '#4f46e5',
|
||||
destructive: '#ef4444',
|
||||
|
||||
// Spacing
|
||||
radius: '0.5rem'
|
||||
};
|
||||
|
||||
// React/Preact
|
||||
<EmbedDirectTemplate
|
||||
token={token}
|
||||
cssVars={cssVars}
|
||||
/>
|
||||
|
||||
// Vue
|
||||
<EmbedDirectTemplate
|
||||
:token="token"
|
||||
:cssVars="cssVars"
|
||||
/>
|
||||
|
||||
// Svelte
|
||||
<EmbedDirectTemplate
|
||||
{token}
|
||||
cssVars={cssVars}
|
||||
/>
|
||||
|
||||
// Solid
|
||||
<EmbedDirectTemplate
|
||||
token={token}
|
||||
cssVars={cssVars}
|
||||
/>
|
||||
```
|
||||
|
||||
## Color Format
|
||||
|
||||
Colors can be specified in any valid CSS color format:
|
||||
|
||||
- Hexadecimal: `#ff0000`
|
||||
- RGB: `rgb(255, 0, 0)`
|
||||
- HSL: `hsl(0, 100%, 50%)`
|
||||
- Named colors: `red`
|
||||
|
||||
The colors will be automatically converted to the appropriate format internally.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Maintain Contrast**: When customizing colors, ensure there's sufficient contrast between background and foreground colors for accessibility.
|
||||
|
||||
2. **Test Dark Mode**: If you haven't disabled dark mode, test your color variables in both light and dark modes.
|
||||
|
||||
3. **Use Your Brand Colors**: Align the primary and accent colors with your brand's color scheme for a cohesive look.
|
||||
|
||||
4. **Consistent Radius**: Use a consistent border radius value that matches your application's design system.
|
||||
|
||||
## CSS Class Targets
|
||||
|
||||
In addition to CSS variables, specific components in the embedded experience can be targeted using CSS classes for more granular styling:
|
||||
|
||||
### Component Classes
|
||||
|
||||
| Class Name | Description |
|
||||
| --------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `.embed--Root` | Main container for the embedded signing experience |
|
||||
| `.embed--DocumentContainer` | Container for the document and signing widget |
|
||||
| `.embed--DocumentViewer` | Container for the document viewer |
|
||||
| `.embed--DocumentWidget` | The signing widget container |
|
||||
| `.embed--DocumentWidgetContainer` | Outer container for the signing widget, handles positioning |
|
||||
| `.embed--DocumentWidgetHeader` | Header section of the signing widget |
|
||||
| `.embed--DocumentWidgetContent` | Main content area of the signing widget |
|
||||
| `.embed--DocumentWidgetForm` | Form section within the signing widget |
|
||||
| `.embed--DocumentWidgetFooter` | Footer section of the signing widget |
|
||||
| `.embed--WaitingForTurn` | Container for the waiting screen when it's not the user's turn to sign |
|
||||
| `.embed--DocumentCompleted` | Container for the completion screen after signing |
|
||||
| `.field--FieldRootContainer` | Base container for document fields (signatures, text, checkboxes, etc.) |
|
||||
|
||||
Field components also expose several data attributes that can be used for styling different states:
|
||||
|
||||
| Data Attribute | Values | Description |
|
||||
| ------------------- | ---------------------------------------------- | ------------------------------------ |
|
||||
| `[data-field-type]` | `SIGNATURE`, `TEXT`, `CHECKBOX`, `RADIO`, etc. | The type of field |
|
||||
| `[data-inserted]` | `true`, `false` | Whether the field has been filled |
|
||||
| `[data-validate]` | `true`, `false` | Whether the field is being validated |
|
||||
|
||||
### Field Styling Example
|
||||
|
||||
```css
|
||||
/* Style all field containers */
|
||||
.field--FieldRootContainer {
|
||||
transition: all 200ms ease;
|
||||
}
|
||||
|
||||
/* Style specific field types */
|
||||
.field--FieldRootContainer[data-field-type='SIGNATURE'] {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
/* Style inserted fields */
|
||||
.field--FieldRootContainer[data-inserted='true'] {
|
||||
background-color: var(--primary);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
/* Style fields being validated */
|
||||
.field--FieldRootContainer[data-validate='true'] {
|
||||
border-color: orange;
|
||||
}
|
||||
```
|
||||
|
||||
### Example Usage
|
||||
|
||||
```css
|
||||
/* Custom styles for the document widget */
|
||||
.embed--DocumentWidget {
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
/* Custom styles for the waiting screen */
|
||||
.embed--WaitingForTurn {
|
||||
background-color: #f9fafb;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Responsive adjustments for the document container */
|
||||
@media (min-width: 768px) {
|
||||
.embed--DocumentContainer {
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [React Integration](/developers/embedding/react)
|
||||
- [Vue Integration](/developers/embedding/vue)
|
||||
- [Svelte Integration](/developers/embedding/svelte)
|
||||
- [Solid Integration](/developers/embedding/solid)
|
||||
- [Preact Integration](/developers/embedding/preact)
|
||||
@@ -1,200 +0,0 @@
|
||||
---
|
||||
title: Get Started
|
||||
description: Learn how to use embedding to bring signing to your own website or application
|
||||
---
|
||||
|
||||
# Embedded Signing
|
||||
|
||||
Our embedding feature lets you integrate our document signing experience into your own application or website. Whether you're building with React, Preact, Vue, Svelte, Solid, Angular, or using generalized web components, this guide will help you get started with embedding Documenso.
|
||||
|
||||
## Embedded Signing vs Embedded Authoring
|
||||
|
||||
Embedded signing allows you to embed your Documenso documents into your application for signing. Your users will be able to sign the document directly in your application.
|
||||
|
||||
Embedded authoring allows you to integrate Documenso's document and template creation and editing into your application. You will be able to create and edit documents and templates directly in your application.
|
||||
|
||||
## Availability
|
||||
|
||||
Embedding is currently available for all users on a **Teams Plan** and above, as well as **Early Adopter's** within a team (Early Adopters can create a team for free).
|
||||
|
||||
Our **Platform Plan** offers enhanced customization features including:
|
||||
|
||||
- Custom CSS and styling variables
|
||||
- Dark mode controls
|
||||
- The removal of Documenso branding from the embedding experience
|
||||
|
||||
## How Embedding Works
|
||||
|
||||
Embedding with Documenso allows you to handle document signing in two main ways:
|
||||
|
||||
1. **Using Direct Templates**: Using direct templates you can have an evergreen template that upon completion will create a new document within Documenso.
|
||||
2. **Using a Signing Token**: A more advanced option for those running rich integrations with Documenso already. Given a recipients signing token you can embed the signing experience in your application rather than direct the recipient to Documenso.
|
||||
|
||||
_For most use-cases we recommend using direct templates, however if you have a need for a more advanced integration, we are happy to help you get started._
|
||||
|
||||
## Customization Options
|
||||
|
||||
### Styling and Theming
|
||||
|
||||
Platform customers have access to advanced styling options to customize the embedding experience:
|
||||
|
||||
1. **Custom CSS**: You can provide custom CSS to style the embedded component:
|
||||
|
||||
```jsx
|
||||
<EmbedDirectTemplate
|
||||
token={token}
|
||||
css={`
|
||||
.documenso-embed {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
`}
|
||||
/>
|
||||
```
|
||||
|
||||
2. **CSS Variables**: Fine-tune the appearance using CSS variables for colors, spacing, and more:
|
||||
|
||||
```jsx
|
||||
<EmbedDirectTemplate
|
||||
token={token}
|
||||
cssVars={{
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
For a complete list of available CSS variables and their usage, see our [CSS Variables](/developers/embedding/css-variables) documentation.
|
||||
|
||||
3. **Dark Mode Control**: Disable dark mode if it doesn't match your application's theme:
|
||||
|
||||
```jsx
|
||||
<EmbedDirectTemplate token={token} darkModeDisabled={true} />
|
||||
```
|
||||
|
||||
These customization options are available for both Direct Templates and Signing Token embeds.
|
||||
|
||||
## Supported Frameworks
|
||||
|
||||
We support embedding across a range of popular JavaScript frameworks, including:
|
||||
|
||||
| Framework | Package |
|
||||
| --------- | ---------------------------------------------------------------------------------- |
|
||||
| React | [@documenso/embed-react](https://www.npmjs.com/package/@documenso/embed-react) |
|
||||
| Preact | [@documenso/embed-preact](https://www.npmjs.com/package/@documenso/embed-preact) |
|
||||
| Vue | [@documenso/embed-vue](https://www.npmjs.com/package/@documenso/embed-vue) |
|
||||
| Svelte | [@documenso/embed-svelte](https://www.npmjs.com/package/@documenso/embed-svelte) |
|
||||
| Solid | [@documenso/embed-solid](https://www.npmjs.com/package/@documenso/embed-solid) |
|
||||
| Angular | [@documenso/embed-angular](https://www.npmjs.com/package/@documenso/embed-angular) |
|
||||
|
||||
Additionally, we provide **web components** for more generalized use. However, please note that web components are still in their early stages and haven't been extensively tested.
|
||||
|
||||
## Embedding with Direct Templates
|
||||
|
||||
#### Instructions
|
||||
|
||||
To get started with embedding using a Direct Template we will need the URL segment which is also referred to as the token for the template.
|
||||
|
||||
You can find your URL/Token by performing the following steps:
|
||||
|
||||
1. **Navigate to your team's templates within Documenso**
|
||||
|
||||

|
||||
|
||||
2. **Click on the direct link template you want to embed**
|
||||
|
||||
This will copy the URL to your clipboard, e.g. `https://stg-app.documenso.com/d/-WoSwWVT-fYOERS2MI37k`
|
||||
|
||||
**For the above url the token is `-WoSwWVT-fYOERS2MI37k`**
|
||||
|
||||
3. Provide the token to the `EmbedDirectTemplate` component in your frameworks SDK
|
||||
|
||||
```jsx
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-react';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
const token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
|
||||
return <EmbedDirectTemplate token={token} />;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Converting a regular template to a direct link template**
|
||||
|
||||
If you don't currently have any direct link templates you can easily create one by selecting the "Direct Link" option within the actions dropdown on the templates table.
|
||||
|
||||
This will show a dialog which will ask you to configure which recipient should be used as the direct link signer.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Embedding with Signing Tokens
|
||||
|
||||
To embed the signing process for an ordinary document, you'll need a **document signing token** for the recipient. This token provides the necessary access to load the document and facilitate the signing process securely.
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. Retrieve the signing token for the recipient document you want to embed
|
||||
|
||||
This will typically be done using an API integration where signing tokens are provided as part of the response when creating a document. Alternatively you can manually get a signing link by clicking hovering over a recipients avatar and clicking their email on a document that you own.
|
||||
|
||||

|
||||
|
||||
With the signing url on our clipboard we can extract the token the same way we did for the direct link template.
|
||||
|
||||
So `https://stg-app.documenso.com/sign/lm7Tp2_yhvFfzdeJQzYQF` will become `lm7Tp2_yhvFfzdeJQzYQF`
|
||||
|
||||
2. Provide the token to the `EmbedSignDocument` component in your frameworks SDK
|
||||
|
||||
```jsx
|
||||
import { EmbedSignDocument } from '@documenso/embed-react';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
const token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
|
||||
return <EmbedSignDocument token={token} />;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Using Embedding in Your Application
|
||||
|
||||
Once you've obtained the appropriate tokens, you can integrate the signing experience into your application. For framework-specific instructions, please refer to the guides provided in our documentation for:
|
||||
|
||||
- [React](/developers/embedding/react)
|
||||
- [Preact](/developers/embedding/preact)
|
||||
- [Vue](/developers/embedding/vue)
|
||||
- [Svelte](/developers/embedding/svelte)
|
||||
- [Solid](/developers/embedding/solid)
|
||||
- [Angular](/developers/embedding/angular)
|
||||
|
||||
If you're using **web components**, the integration process is slightly different. Keep in mind that web components are currently less tested but can still provide flexibility for general use cases.
|
||||
|
||||
## Embedded Authoring
|
||||
|
||||
In addition to embedding signing experiences, Documenso now supports **embedded authoring**, allowing your users to create documents and templates directly within your application.
|
||||
|
||||
With embedded authoring, you can:
|
||||
|
||||
- Create new documents with custom fields
|
||||
- Configure document properties and settings
|
||||
- Set up recipients and signing workflows
|
||||
- Customize the authoring experience
|
||||
|
||||
For detailed implementation instructions and code examples, see our [Embedded Authoring](/developers/embedding/authoring) guide.
|
||||
|
||||
## Related
|
||||
|
||||
- [React Integration](/developers/embedding/react)
|
||||
- [Vue Integration](/developers/embedding/vue)
|
||||
- [Svelte Integration](/developers/embedding/svelte)
|
||||
- [Solid Integration](/developers/embedding/solid)
|
||||
- [Preact Integration](/developers/embedding/preact)
|
||||
- [Angular Integration](/developers/embedding/angular)
|
||||
- [CSS Variables](/developers/embedding/css-variables)
|
||||
- [Embedded Authoring](/developers/embedding/authoring)
|
||||
@@ -1,107 +0,0 @@
|
||||
---
|
||||
title: Preact Integration
|
||||
description: Learn how to use our embedding SDK within your Preact application.
|
||||
---
|
||||
|
||||
# Preact Integration
|
||||
|
||||
Our Preact SDK provides a simple way to embed a signing experience within your Preact application. It supports both direct link templates and signing tokens.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the SDK, run the following command:
|
||||
|
||||
```bash
|
||||
npm install @documenso/embed-preact
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To embed a signing experience, you'll need to provide the token for the document you want to embed. This can be done in a few different ways, depending on your use case.
|
||||
|
||||
### Direct Link Template
|
||||
|
||||
If you have a direct link template, you can simply provide the token for the template to the `EmbedDirectTemplate` component.
|
||||
|
||||
```jsx
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-preact';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
const token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
|
||||
return <EmbedDirectTemplate token={token} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| email | string (optional) | The email the signer that will be used by default for signing |
|
||||
| lockEmail | boolean (optional) | Whether or not the email field should be locked disallowing modifications |
|
||||
| externalId | string (optional) | The external ID to be used for the document that will be created upon completion |
|
||||
| css | string (optional) | Custom CSS to style the embedded component (Platform Plan only) |
|
||||
| cssVars | object (optional) | CSS variables for customizing colors, spacing, etc. (Platform Plan only) |
|
||||
| darkModeDisabled | boolean (optional) | Disable dark mode functionality (Platform Plan only) |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
| onFieldSigned | function (optional) | A callback function that will be called when a field has been signed |
|
||||
| onFieldUnsigned | function (optional) | A callback function that will be called when a field has been unsigned |
|
||||
|
||||
### Signing Token
|
||||
|
||||
If you have a signing token, you can provide it to the `EmbedSignDocument` component.
|
||||
|
||||
```jsx
|
||||
import { EmbedSignDocument } from '@documenso/embed-preact';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
const token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
|
||||
return <EmbedSignDocument token={token} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
|
||||
### Styling and Theming (Platform Plan)
|
||||
|
||||
Platform customers have access to advanced styling options:
|
||||
|
||||
```jsx
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-preact';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
const token = 'your-token';
|
||||
const customCss = `
|
||||
.documenso-embed {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
`;
|
||||
const cssVars = {
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
};
|
||||
|
||||
return (
|
||||
<EmbedDirectTemplate token={token} css={customCss} cssVars={cssVars} darkModeDisabled={true} />
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -1,111 +0,0 @@
|
||||
---
|
||||
title: React Integration
|
||||
description: Learn how to use our embedding SDK within your React application.
|
||||
---
|
||||
|
||||
# React Integration
|
||||
|
||||
Our React SDK provides a simple way to embed a signing experience within your React application. It supports both direct link templates and signing tokens.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the SDK, run the following command:
|
||||
|
||||
```bash
|
||||
npm install @documenso/embed-react
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To embed a signing experience, you'll need to provide the token for the document you want to embed. This can be done in a few different ways, depending on your use case.
|
||||
|
||||
### Direct Link Template
|
||||
|
||||
If you have a direct link template, you can simply provide the token for the template to the `EmbedDirectTemplate` component.
|
||||
|
||||
```jsx
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-react';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
const token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
|
||||
return <EmbedDirectTemplate token={token} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| email | string (optional) | The email the signer that will be used by default for signing |
|
||||
| lockEmail | boolean (optional) | Whether or not the email field should be locked disallowing modifications |
|
||||
| externalId | string (optional) | The external ID to be used for the document that will be created upon completion |
|
||||
| css | string (optional) | Custom CSS to style the embedded component (Platform Plan only) |
|
||||
| cssVars | object (optional) | CSS variables for customizing colors, spacing, etc. (Platform Plan only) |
|
||||
| darkModeDisabled | boolean (optional) | Disable dark mode functionality (Platform Plan only) |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
| onFieldSigned | function (optional) | A callback function that will be called when a field has been signed |
|
||||
| onFieldUnsigned | function (optional) | A callback function that will be called when a field has been unsigned |
|
||||
|
||||
### Signing Token
|
||||
|
||||
If you have a signing token, you can provide it to the `EmbedSignDocument` component.
|
||||
|
||||
```jsx
|
||||
import { EmbedSignDocument } from '@documenso/embed-react';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
const token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
|
||||
return <EmbedSignDocument token={token} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
|
||||
### Styling and Theming (Platform Plan)
|
||||
|
||||
Platform customers have access to advanced styling options:
|
||||
|
||||
```jsx
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-react';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
return (
|
||||
<EmbedDirectTemplate
|
||||
token="your-token"
|
||||
// Custom CSS
|
||||
css={`
|
||||
.documenso-embed {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
`}
|
||||
// CSS Variables
|
||||
cssVars={{
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
}}
|
||||
// Dark Mode Control
|
||||
darkModeDisabled={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -1,107 +0,0 @@
|
||||
---
|
||||
title: Solid.js Integration
|
||||
description: Learn how to use our embedding SDK within your Solid.js application.
|
||||
---
|
||||
|
||||
# Solid.js Integration
|
||||
|
||||
Our Solid.js SDK provides a simple way to embed a signing experience within your Solid.js application. It supports both direct link templates and signing tokens.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the SDK, run the following command:
|
||||
|
||||
```bash
|
||||
npm install @documenso/embed-solid
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To embed a signing experience, you'll need to provide the token for the document you want to embed. This can be done in a few different ways, depending on your use case.
|
||||
|
||||
### Direct Link Template
|
||||
|
||||
If you have a direct link template, you can simply provide the token for the template to the `EmbedDirectTemplate` component.
|
||||
|
||||
```jsx
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-solid';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
const token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
|
||||
return <EmbedDirectTemplate token={token} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| email | string (optional) | The email the signer that will be used by default for signing |
|
||||
| lockEmail | boolean (optional) | Whether or not the email field should be locked disallowing modifications |
|
||||
| externalId | string (optional) | The external ID to be used for the document that will be created upon completion |
|
||||
| css | string (optional) | Custom CSS to style the embedded component (Platform Plan only) |
|
||||
| cssVars | object (optional) | CSS variables for customizing colors, spacing, etc. (Platform Plan only) |
|
||||
| darkModeDisabled | boolean (optional) | Disable dark mode functionality (Platform Plan only) |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
| onFieldSigned | function (optional) | A callback function that will be called when a field has been signed |
|
||||
| onFieldUnsigned | function (optional) | A callback function that will be called when a field has been unsigned |
|
||||
|
||||
### Signing Token
|
||||
|
||||
If you have a signing token, you can provide it to the `EmbedSignDocument` component.
|
||||
|
||||
```jsx
|
||||
import { EmbedSignDocument } from '@documenso/embed-solid';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
const token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
|
||||
return <EmbedSignDocument token={token} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
|
||||
### Styling and Theming (Platform Plan)
|
||||
|
||||
Platform customers have access to advanced styling options:
|
||||
|
||||
```jsx
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-solid';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
const token = 'your-token';
|
||||
const customCss = `
|
||||
.documenso-embed {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
`;
|
||||
const cssVars = {
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
};
|
||||
|
||||
return (
|
||||
<EmbedDirectTemplate token={token} css={customCss} cssVars={cssVars} darkModeDisabled={true} />
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -1,107 +0,0 @@
|
||||
---
|
||||
title: Svelte Integration
|
||||
description: Learn how to use our embedding SDK within your Svelte application.
|
||||
---
|
||||
|
||||
# Svelte Integration
|
||||
|
||||
Our Svelte SDK provides a simple way to embed a signing experience within your Svelte application. It supports both direct link templates and signing tokens.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the SDK, run the following command:
|
||||
|
||||
```bash
|
||||
npm install @documenso/embed-svelte
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To embed a signing experience, you'll need to provide the token for the document you want to embed. This can be done in a few different ways, depending on your use case.
|
||||
|
||||
### Direct Link Template
|
||||
|
||||
If you have a direct link template, you can simply provide the token for the template to the `EmbedDirectTemplate` component.
|
||||
|
||||
```html
|
||||
<script lang="ts">
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-svelte';
|
||||
|
||||
const token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EmbedDirectTemplate {token} />
|
||||
</template>
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| email | string (optional) | The email the signer that will be used by default for signing |
|
||||
| lockEmail | boolean (optional) | Whether or not the email field should be locked disallowing modifications |
|
||||
| externalId | string (optional) | The external ID to be used for the document that will be created upon completion |
|
||||
| css | string (optional) | Custom CSS to style the embedded component (Platform Plan only) |
|
||||
| cssVars | object (optional) | CSS variables for customizing colors, spacing, etc. (Platform Plan only) |
|
||||
| darkModeDisabled | boolean (optional) | Disable dark mode functionality (Platform Plan only) |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
| onFieldSigned | function (optional) | A callback function that will be called when a field has been signed |
|
||||
| onFieldUnsigned | function (optional) | A callback function that will be called when a field has been unsigned |
|
||||
|
||||
### Signing Token
|
||||
|
||||
If you have a signing token, you can provide it to the `EmbedSignDocument` component.
|
||||
|
||||
```jsx
|
||||
import { EmbedSignDocument } from '@documenso/embed-svelte';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
const token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
|
||||
return <EmbedSignDocument token={token} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
|
||||
### Styling and Theming (Platform Plan)
|
||||
|
||||
Platform customers have access to advanced styling options:
|
||||
|
||||
```html
|
||||
<script lang="ts">
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-svelte';
|
||||
|
||||
const token = 'your-token';
|
||||
const customCss = `
|
||||
.documenso-embed {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
`;
|
||||
const cssVars = {
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
};
|
||||
</script>
|
||||
|
||||
<EmbedDirectTemplate {token} css="{customCss}" cssVars="{cssVars}" darkModeDisabled="{true}" />
|
||||
```
|
||||
@@ -1,114 +0,0 @@
|
||||
---
|
||||
title: Vue Integration
|
||||
description: Learn how to use our embedding SDK within your Vue application.
|
||||
---
|
||||
|
||||
# Vue Integration
|
||||
|
||||
Our Vue SDK provides a simple way to embed a signing experience within your Vue application. It supports both direct link templates and signing tokens.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the SDK, run the following command:
|
||||
|
||||
```bash
|
||||
npm install @documenso/embed-vue
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To embed a signing experience, you'll need to provide the token for the document you want to embed. This can be done in a few different ways, depending on your use case.
|
||||
|
||||
### Direct Link Template
|
||||
|
||||
If you have a direct link template, you can simply provide the token for the template to the `EmbedDirectTemplate` component.
|
||||
|
||||
```html
|
||||
<script setup lang="ts">
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-vue';
|
||||
|
||||
const token = ref('YOUR_TOKEN_HERE'); // Replace with the actual token
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EmbedDirectTemplate :token="token" />
|
||||
</template>
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| email | string (optional) | The email the signer that will be used by default for signing |
|
||||
| lockEmail | boolean (optional) | Whether or not the email field should be locked disallowing modifications |
|
||||
| externalId | string (optional) | The external ID to be used for the document that will be created upon completion |
|
||||
| css | string (optional) | Custom CSS to style the embedded component (Platform Plan only) |
|
||||
| cssVars | object (optional) | CSS variables for customizing colors, spacing, etc. (Platform Plan only) |
|
||||
| darkModeDisabled | boolean (optional) | Disable dark mode functionality (Platform Plan only) |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
| onFieldSigned | function (optional) | A callback function that will be called when a field has been signed |
|
||||
| onFieldUnsigned | function (optional) | A callback function that will be called when a field has been unsigned |
|
||||
|
||||
### Signing Token
|
||||
|
||||
If you have a signing token, you can provide it to the `EmbedSignDocument` component.
|
||||
|
||||
```jsx
|
||||
import { EmbedSignDocument } from '@documenso/embed-vue';
|
||||
|
||||
const MyEmbeddingComponent = () => {
|
||||
const token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
|
||||
return <EmbedSignDocument token={token} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
|
||||
### Styling and Theming (Platform Plan)
|
||||
|
||||
Platform customers have access to advanced styling options:
|
||||
|
||||
```html
|
||||
<script setup lang="ts">
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-vue';
|
||||
|
||||
const token = ref('your-token');
|
||||
const customCss = `
|
||||
.documenso-embed {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
`;
|
||||
const cssVars = {
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EmbedDirectTemplate
|
||||
:token="token"
|
||||
:css="customCss"
|
||||
:cssVars="cssVars"
|
||||
:darkModeDisabled="true"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
title: Developer Documentation
|
||||
description: Learn how to run Documenso locally, use our API, integrate webhooks, contribute to the project, and self-host Documenso.
|
||||
---
|
||||
|
||||
import { Cards } from 'nextra/components';
|
||||
|
||||
# Developer Documentation
|
||||
|
||||
The developer documentation is a comprehensive guide to help you:
|
||||
|
||||
<Cards>
|
||||
<Cards.Card title="Set up dev environment" href="/developers/local-development" />
|
||||
<Cards.Card title="Use the API" href="/developers/public-api" />
|
||||
<Cards.Card title="Integrate webhooks" href="/developers/webhooks" />
|
||||
<Cards.Card title="Contribute to the project" href="/developers/contributing" />
|
||||
<Cards.Card title="Self-host Documenso" href="/developers/self-hosting" />
|
||||
</Cards>
|
||||
@@ -1,8 +0,0 @@
|
||||
export default {
|
||||
index: 'Get Started',
|
||||
quickstart: 'Developer Quickstart',
|
||||
manual: 'Manual Setup',
|
||||
gitpod: 'Gitpod',
|
||||
'signing-certificate': 'Signing Certificate',
|
||||
translations: 'Translations',
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
title: Run in Gitpod
|
||||
description: Get started with Documenso in a ready-to-use Gitpod workspace in your browser.
|
||||
---
|
||||
|
||||
# Run in Gitpod
|
||||
|
||||
Click below to launch a ready-to-use Gitpod workspace in your browser.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/documenso/documenso)
|
||||
@@ -1,61 +0,0 @@
|
||||
---
|
||||
title: Local Development
|
||||
description: Learn how to set up Documenso for local development.
|
||||
---
|
||||
|
||||
# Local development
|
||||
|
||||
There are multiple ways of setting up Documenso for local development. At the moment of writing this documentation, there are 3 ways of running Documenso locally:
|
||||
|
||||
- [Using the developer quickstart with Docker](/developers/local-development/quickstart)
|
||||
- [Manually setting up the development environment](/developers/local-development/manual)
|
||||
- [Using Gitpod](/developers/local-development/gitpod)
|
||||
|
||||
Pick the one that fits your needs the best.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- [Typescript](https://www.typescriptlang.org/) - Language
|
||||
- [React Router](https://reactrouter.com/) - Framework
|
||||
- [Prisma](https://www.prisma.io/) - ORM
|
||||
- [Tailwind](https://tailwindcss.com/) - CSS
|
||||
- [shadcn/ui](https://ui.shadcn.com/) - Component Library
|
||||
- [react-email](https://react.email/) - Email Templates
|
||||
- [tRPC](https://trpc.io/) - API
|
||||
- [@documenso/pdf-sign](https://www.npmjs.com/package/@documenso/pdf-sign) - PDF Signatures
|
||||
- [React-PDF](https://github.com/wojtekmaj/react-pdf) - Viewing PDFs
|
||||
- [PDF-Lib](https://github.com/Hopding/pdf-lib) - PDF manipulation
|
||||
- [Stripe](https://stripe.com/) - Payments
|
||||
|
||||
<div className="mt-16 flex items-center justify-center gap-4">
|
||||
<a href="https://documen.so/discord">
|
||||
<img
|
||||
src="https://img.shields.io/badge/Discord-documen.so/discord-%235865F2"
|
||||
alt="Join Documenso on Discord"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://github.com/documenso/documenso/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/documenso/documenso" alt="Github Stars" />
|
||||
</a>
|
||||
<a href="https://github.com/documenso/documenso/blob/main/LICENSE">
|
||||
<img src="https://img.shields.io/badge/license-AGPLv3-purple" alt="License" />
|
||||
</a>
|
||||
<a href="https://github.com/documenso/documenso/pulse">
|
||||
<img
|
||||
src="https://img.shields.io/github/commit-activity/m/documenso/documenso"
|
||||
alt="Commits-per-month"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/documenso/documenso">
|
||||
<img
|
||||
alt="open in devcontainer"
|
||||
src="https://img.shields.io/static/v1?label=Dev%20Containers&message=Enabled&color=blue&logo=visualstudiocode"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://github.com/documenso/documenso/blob/main/CODE_OF_CONDUCT.md">
|
||||
<img
|
||||
src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg"
|
||||
alt="Contributor Covenant"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
title: Manual Setup
|
||||
description: Manually set up Documenso on your machine for local development.
|
||||
---
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# Manual Setup
|
||||
|
||||
Follow these steps to set up Documenso on your local machine:
|
||||
|
||||
<Steps>
|
||||
|
||||
### Fork Documenso
|
||||
|
||||
Fork the [Documenso repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) to your GitHub account.
|
||||
|
||||
### Clone Repository
|
||||
|
||||
After forking the repository, clone it to your local device by using the following command:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/<your-username>/documenso
|
||||
```
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
Run `npm i` in the root directory to install the dependencies required for the project.
|
||||
|
||||
### Set Up Environment Variables
|
||||
|
||||
Set up the following environment variables in the `.env` file:
|
||||
|
||||
```bash
|
||||
NEXTAUTH_SECRET
|
||||
NEXT_PUBLIC_WEBAPP_URL
|
||||
NEXT_PRIVATE_DATABASE_URL
|
||||
NEXT_PRIVATE_DIRECT_DATABASE_URL
|
||||
NEXT_PRIVATE_SMTP_FROM_NAME
|
||||
NEXT_PRIVATE_SMTP_FROM_ADDRESS
|
||||
```
|
||||
|
||||
Alternatively, you can run `cp .env.example .env` to get started with our handpicked defaults.
|
||||
|
||||
### Create Database Schema
|
||||
|
||||
Create the database schema by running the following command:
|
||||
|
||||
```bash
|
||||
npm run prisma:migrate-dev
|
||||
```
|
||||
|
||||
### Optional: Seed the Database
|
||||
|
||||
Seed the database with test data by running the following command:
|
||||
|
||||
```bash
|
||||
npm run prisma:seed -w @documenso/prisma
|
||||
```
|
||||
|
||||
### Start the Application
|
||||
|
||||
Run `npm run dev` in the root directory to start the application.
|
||||
|
||||
### Access the Application
|
||||
|
||||
Access the Documenso application by visiting `http://localhost:3000` in your web browser.
|
||||
|
||||
</Steps>
|
||||
|
||||
<Callout type="info">
|
||||
Optional: Create your signing certificate. To generate your own using these steps and a Linux
|
||||
Terminal or Windows Subsystem for Linux (WSL), see **[Create your signing
|
||||
certificate](/developers/local-development/signing-certificate)**.
|
||||
</Callout>
|
||||
@@ -1,66 +0,0 @@
|
||||
---
|
||||
title: Developer Quickstart
|
||||
description: Quickly set up Documenso on your machine for local development with Docker and Docker Compose.
|
||||
---
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# Developer Quickstart
|
||||
|
||||
<Callout type="info">
|
||||
**Note**: This guide assumes that you have both [docker](https://docs.docker.com/get-docker/) and
|
||||
[docker-compose](https://docs.docker.com/compose/) installed on your machine.
|
||||
</Callout>
|
||||
|
||||
Want to get up and running quickly? Follow these steps:
|
||||
|
||||
<Steps>
|
||||
|
||||
### Fork Documenso
|
||||
|
||||
Fork the [Documenso repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) to your GitHub account.
|
||||
|
||||
### Clone Repository
|
||||
|
||||
After forking the repository, clone it to your local device by using the following command:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/<your-username>/documenso
|
||||
```
|
||||
|
||||
### Set Up Environment Variables
|
||||
|
||||
Set up your environment variables in the `.env` file using the `.env.example` file as a reference.
|
||||
|
||||
Alternatively, you can run `cp .env.example .env` to get started with our handpicked defaults.
|
||||
|
||||
### Start Database and Mail Server
|
||||
|
||||
Run `npm run dx` in the root directory.
|
||||
|
||||
This will spin up a Postgres database and inbucket mailserver in a docker container.
|
||||
|
||||
### Start the Application
|
||||
|
||||
Run `npm run dev` in the root directory to start the application.
|
||||
|
||||
### (Optional) Fasten the Process
|
||||
|
||||
Want it even faster? Just use:
|
||||
|
||||
```sh
|
||||
npm run d
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
### Access Points for the Project
|
||||
|
||||
You can access the following services:
|
||||
|
||||
- Main application - http://localhost:3000
|
||||
- Incoming Mail Access - http://localhost:9000
|
||||
- Database Connection Details:
|
||||
- Port: 54320
|
||||
- Connection: Use your favorite database client to connect to the database.
|
||||
- S3 Storage Dashboard - http://localhost:9001
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
title: Signing Certificate
|
||||
description: Learn how to create a free, self-signed certificate for local development.
|
||||
---
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# Create Your Signing Certificate
|
||||
|
||||
Digitally signing documents requires a signing certificate in `.p12` format. You can either purchase one or create a free self-signed certificate.
|
||||
|
||||
Follow the steps below to create a free, self-signed certificate for local development.
|
||||
|
||||
<Callout type="warning">
|
||||
These steps should be run on a UNIX based system, otherwise you may run into an error.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
|
||||
### Generate Private Key
|
||||
|
||||
Generate a private key using OpenSSL by running the following command:
|
||||
|
||||
```bash
|
||||
openssl genrsa -out private.key 2048
|
||||
```
|
||||
|
||||
This command generates a 2048-bit RSA key.
|
||||
|
||||
### Generate Self-Signed Certificate
|
||||
|
||||
Using the private key, generate a self-signed certificate by running the following command:
|
||||
|
||||
```bash
|
||||
openssl req -new -x509 -key private.key -out certificate.crt -days 365
|
||||
```
|
||||
|
||||
You will be prompted to enter some information, such as the certificate's Common Name (CN). Ensure that you provide the correct details. The `—days` parameter specifies the certificate's validity period.
|
||||
|
||||
### Create `p12` Certificate
|
||||
|
||||
Combine the private key and the self-signed certificate to create a `.p12` certificate. Use the following command:
|
||||
|
||||
```bash
|
||||
openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt -legacy
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
When running the application in Docker, you may encounter permission issues when attempting to sign documents using your certificate (.p12) file. This happens because the application runs as a non-root user inside the container and needs read access to the certificate.
|
||||
|
||||
To resolve this, you'll need to update the certificate file permissions to allow the container user 1001, which runs NextJS, to read it:
|
||||
|
||||
```bash
|
||||
sudo chown 1001 certificate.p12
|
||||
```
|
||||
|
||||
</Callout>
|
||||
|
||||
### `p12` Certificate Password
|
||||
|
||||
When you create the `.p12` certificate, you will be prompted to enter a password. Enter a strong password and keep it secure. Remember this password, as it will be required when using the certificate.
|
||||
|
||||
Note that for local development, the password can be left empty.
|
||||
|
||||
### Add Certificate to the Project
|
||||
|
||||
Use the `NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH` environment variable to point at the certificate you created.
|
||||
|
||||
Details about environment variables associated with certificates can be found [here](/developers/self-hosting/signing-certificate#configure-documenso-to-use-the-certificate).
|
||||
|
||||
</Steps>
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
title: Translations
|
||||
description: Handling translations in code.
|
||||
---
|
||||
|
||||
# About
|
||||
|
||||
Documenso uses the following stack to handle translations:
|
||||
|
||||
- [Lingui](https://lingui.dev/) - React i10n library
|
||||
- [Crowdin](https://crowdin.com/) - Handles syncing translations
|
||||
- [OpenAI](https://openai.com/) - Provides AI translations
|
||||
|
||||
Additional reading can be found in the [Lingui documentation](https://lingui.dev/introduction).
|
||||
|
||||
## Quick guide
|
||||
|
||||
If you require more in-depth information, please see the [Lingui documentation](https://lingui.dev/introduction).
|
||||
|
||||
### HTML
|
||||
|
||||
Wrap all text to translate in **`<Trans></Trans>`** tags exported from **@lingui/react/macro**.
|
||||
|
||||
```html
|
||||
<h1>
|
||||
<Trans>Title</Trans>
|
||||
</h1>
|
||||
```
|
||||
|
||||
For text that is broken into elements, but represent a whole sentence, you must wrap it in a Trans tag so ensure the full message is extracted correctly.
|
||||
|
||||
```html
|
||||
<h1>
|
||||
<Trans>
|
||||
This is one
|
||||
<span className="text-foreground/60">full</span>
|
||||
<a href="https://documenso.com">sentence</a>
|
||||
</Trans>
|
||||
</h1>
|
||||
```
|
||||
|
||||
### Constants outside of react components
|
||||
|
||||
```tsx
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
// Wrap text in msg`text to translate` when it's in a constant here, or another file/package.
|
||||
export const CONSTANT_WITH_MSG = {
|
||||
foo: msg`Hello`,
|
||||
bar: msg`World`,
|
||||
};
|
||||
|
||||
export const SomeComponent = () => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* This will render the correct translated text. */}
|
||||
<p>{_(CONSTANT_WITH_MSG.foo)}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Plurals
|
||||
|
||||
Lingui provides a Plural component to make it easy. See full documentation [here.](https://lingui.dev/ref/macro#plural-1)
|
||||
|
||||
```tsx
|
||||
// Basic usage.
|
||||
<Plural one="1 Recipient" other="# Recipients" value={recipients.length} />
|
||||
```
|
||||
|
||||
### Dates
|
||||
|
||||
Lingui provides a [DateTime instance](https://lingui.dev/ref/core#i18n.date) with the configured locale.
|
||||
|
||||
```tsx
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
export const SomeComponent = () => {
|
||||
const { i18n } = useLingui();
|
||||
|
||||
return <Trans>The current date is {i18n.date(new Date(), { dateStyle: 'short' })}</Trans>;
|
||||
};
|
||||
```
|
||||
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
index: 'Get Started',
|
||||
authentication: 'Authentication',
|
||||
'rate-limits': 'Rate Limits',
|
||||
versioning: 'Versioning',
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
title: API Authentication
|
||||
description: Learn how to create a Documenso API key and authenticate your API requests.
|
||||
---
|
||||
|
||||
# API Authentication
|
||||
|
||||
Documenso uses API keys for authentication. An API key is a unique token that is generated for each client. The client must provide the key whenever it makes an API call. This way, Documenso can identify the clients making the requests and authorize their access to the API.
|
||||
|
||||
## Creating an API Key
|
||||
|
||||
To create an API key, navigate to the user settings page. Click on your avatar in the top right corner of the dashboard and select "Team Settings" from the dropdown menu.
|
||||
|
||||

|
||||
|
||||
Once you're on the settings page, navigate to the **API Tokens** tab. This page lists your existing keys and enables you to create new ones.
|
||||
|
||||

|
||||
|
||||
To create a new API key, you must:
|
||||
|
||||
- Choose a name (e.g. "zapier-key")
|
||||
- We recommend using a descriptive name that helps you quickly identify the key and its purpose.
|
||||
- Choose an expiration date
|
||||
- You can set the key never to expire or choose when to become invalid: 7 days, 1 month, 3 months, 6 months, or 1 year.
|
||||
|
||||
After providing the required information, click the "Create token" button to generate the API key.
|
||||
|
||||

|
||||
|
||||
Once you've created the token, Documenso will display the key on the screen. Make sure to copy the key and store it securely. You won't be able to see the key again once you refresh/leave the page.
|
||||
|
||||
## Using the API Key
|
||||
|
||||
You must include the API key in the `Authorization` request header to authenticate your API requests. The format is `Authorization: api_xxxxxxxxxxxxxxxx`.
|
||||
|
||||
Here's a sample API request using cURL:
|
||||
|
||||
```bash
|
||||
curl --location 'https://app.documenso.com/api/v2/envelope/create' \
|
||||
--header 'Authorization: api_xxxxxxxxxxxxxxxx' \
|
||||
--form 'payload={ "title": "Some Title", "type": "DOCUMENT" }'
|
||||
```
|
||||
|
||||
Here's a sample response from the API based on the above cURL request:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "envelope_xxxxxxxxxxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
The API key has access to your account and all its resources. Please keep it secure and do not share it with others. If you suspect your key has been compromised, you can revoke it from the same page you created it from.
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
title: Public API
|
||||
description: Learn how to interact with your documents programmatically using the Documenso public API.
|
||||
---
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# Public API
|
||||
|
||||
Documenso provides a public REST API enabling you to interact with your documents programmatically. The API exposes various HTTP endpoints that allow you to perform operations such as:
|
||||
|
||||
- Retrieving, uploading, deleting, and sending documents for signing
|
||||
- Creating, updating, and deleting recipients
|
||||
- Creating, updating, and deleting document fields
|
||||
|
||||
The documentation walks you through creating API keys and using them to authenticate your API requests. You'll also learn about the available endpoints, request and response formats, and how to use the API.
|
||||
|
||||
## API V2 - Stable
|
||||
|
||||
Check out the [API V2 documentation](https://documen.so/api-v2-docs) for details about the API endpoints, request parameters, response formats, and authentication methods.
|
||||
|
||||
Our new API V2 supports the following typed SDKs:
|
||||
|
||||
- [TypeScript](https://github.com/documenso/sdk-typescript)
|
||||
- [Python](https://github.com/documenso/sdk-python)
|
||||
- [Go](https://github.com/documenso/sdk-go)
|
||||
|
||||
<Callout type="info">
|
||||
For the staging API, please use the following base URL: `https://stg-app.documenso.com/api/v2/`
|
||||
</Callout>
|
||||
|
||||
## API V1 - Deprecated
|
||||
|
||||
<Callout type="warning">
|
||||
<strong>API V1 is deprecated.</strong>
|
||||
<br />
|
||||
The V1 API will continue to be supported for the foreseeable future, but it is limited to
|
||||
<strong>Legacy Documents</strong> (Documents created using the old non-envelope editor).
|
||||
|
||||
<strong>Important:</strong> To work with the new <strong>Envelope</strong> document system, you
|
||||
must use the
|
||||
<strong> V2 API</strong>.
|
||||
</Callout>
|
||||
|
||||
Check out the [API V1 documentation](https://app.documenso.com/api/v1/openapi) for details about the API endpoints, request parameters, response formats, and authentication methods.
|
||||
|
||||
## Availability
|
||||
|
||||
The API is available to individual users, teams and higher plans. [Fair Use](https://documen.so/fair) applies.
|
||||
@@ -1,59 +0,0 @@
|
||||
---
|
||||
title: Rate Limits
|
||||
description: Learn about the rate limits for the Documenso Public API.
|
||||
---
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# Rate Limits
|
||||
|
||||
Documenso enforces rate limits on all API endpoints to ensure service stability.
|
||||
|
||||
## HTTP Rate Limits
|
||||
|
||||
**Limit:** 100 requests per minute per IP address
|
||||
**Response:** 429 Too Many Requests
|
||||
|
||||
### Rate Limit Response
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Too many requests, please try again later."
|
||||
}
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
No rate limit headers are currently provided. When you receive a 429 response, wait at least 60
|
||||
seconds before retrying.
|
||||
</Callout>
|
||||
|
||||
## Resource Limits
|
||||
|
||||
Beyond HTTP rate limits, your account has usage limits based on your subscription plan.
|
||||
|
||||
### Plan Limits
|
||||
|
||||
| Resource | Free | Paid | Self-hosted | Enterprise |
|
||||
| ---------------- | ---- | --------- | ----------- | ---------- |
|
||||
| Documents/month | 5 | Unlimited | Unlimited | Unlimited |
|
||||
| Total Recipients | 10 | Unlimited | Unlimited | Unlimited |
|
||||
| Direct Templates | 3 | Unlimited | Unlimited | Unlimited |
|
||||
|
||||
### Error Response
|
||||
|
||||
When you exceed a resource limit:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "You have reached your document limit for this month. Please upgrade your plan.",
|
||||
"code": "LIMIT_EXCEEDED",
|
||||
"statusCode": 400
|
||||
}
|
||||
```
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Code | Status | Description |
|
||||
| ------------------- | ------ | ----------------------------- |
|
||||
| `TOO_MANY_REQUESTS` | 429 | HTTP rate limit exceeded |
|
||||
| `LIMIT_EXCEEDED` | 400 | Resource usage limit exceeded |
|
||||
@@ -1,456 +0,0 @@
|
||||
---
|
||||
title: API Reference
|
||||
description: Reference documentation for the Documenso public API.
|
||||
---
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# API Reference
|
||||
|
||||
On this page we will refer to both Documents and Templates as Envelopes for convenience, unless otherwise specified.
|
||||
|
||||
<Callout type="info">
|
||||
This page is used to demonstrate some of the endpoints and how to use them. For the full API
|
||||
reference, please see the [API Reference](https://openapi.documenso.com/).
|
||||
</Callout>
|
||||
|
||||
## Get Envelope
|
||||
|
||||
For the vast majority of use cases, you will need to retrieve the envelope to get details for further operations.
|
||||
|
||||
The main details you generally want to extract are the following:
|
||||
|
||||
- **Recipients** - The individuals who will be signing the document
|
||||
- **Fields** - The fields the user will sign
|
||||
- **Items** - The PDF files the user will see and sign
|
||||
|
||||
This is done by doing a the following GET [request](https://openapi.documenso.com/reference#tag/envelope/POST/envelope/create)
|
||||
|
||||
```sh
|
||||
curl -X GET "https://app.documenso.com/api/v2/envelope/envelope_xxxxxxxxxxxxx" \
|
||||
-H "Authorization: api_xxxxxxxxxxxxxx"
|
||||
```
|
||||
|
||||
A successful response looks as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"internalVersion": 2,
|
||||
"type": "TEMPLATE",
|
||||
"status": "DRAFT",
|
||||
"source": "TEMPLATE",
|
||||
"visibility": "EVERYONE",
|
||||
"templateType": "PRIVATE",
|
||||
"id": "envelope_xxxxxxxxxxxxxxxx",
|
||||
"secondaryId": "template_9",
|
||||
"externalId": null,
|
||||
"createdAt": "2025-11-12T11:36:38.391Z",
|
||||
"updatedAt": "2025-11-12T11:36:55.648Z",
|
||||
"completedAt": null,
|
||||
"deletedAt": null,
|
||||
"title": "Title",
|
||||
"recipients": [
|
||||
{
|
||||
"envelopeId": "envelope_xxxxxxxxxxxxxxxx",
|
||||
"role": "SIGNER",
|
||||
"readStatus": "NOT_OPENED",
|
||||
"signingStatus": "NOT_SIGNED",
|
||||
"sendStatus": "NOT_SENT",
|
||||
"id": 0,
|
||||
"email": "recipient+1@documenso.com",
|
||||
"name": "",
|
||||
"token": "cxH2Ss79Hj94M1U3PxRAG",
|
||||
"documentDeletedAt": null,
|
||||
"expired": null,
|
||||
"signedAt": null,
|
||||
"authOptions": {
|
||||
"accessAuth": [],
|
||||
"actionAuth": []
|
||||
},
|
||||
"signingOrder": 1,
|
||||
"rejectionReason": null
|
||||
}
|
||||
]
|
||||
"envelopeItems": [
|
||||
{
|
||||
"envelopeId": "envelope_xxxxxxxxxxxxxxxx",
|
||||
"id": "envelope_item_xxxxxxxxxxxxxxxxx",
|
||||
"title": "FileOne",
|
||||
"order": 1
|
||||
}
|
||||
],
|
||||
// Other attributes
|
||||
}
|
||||
```
|
||||
|
||||
## Create Envelope
|
||||
|
||||
This example request will create a document with the following:
|
||||
|
||||
- Two PDF files
|
||||
- Two recipients with one field each
|
||||
|
||||
You will need to:
|
||||
|
||||
- Replace api_xxxxxxxxxxxxxx with your API key
|
||||
- Replace the file paths with the actual paths to your files
|
||||
|
||||
Note that the identifier on a field is linking to the files you upload. It can either be the index of the file or the filename. In this example it uses the index of the file.
|
||||
|
||||
```sh
|
||||
curl -X POST "https://app.documenso.com/api/v2/envelope/create" \
|
||||
-H "Authorization: api_xxxxxxxxxxxxxx" \
|
||||
-H "Content-Type: multipart/form-data" \
|
||||
-F 'payload={
|
||||
"type": "DOCUMENT",
|
||||
"title": "Envelope Full Field Test",
|
||||
"recipients": [
|
||||
{
|
||||
"email": "recipient+1@example.com",
|
||||
"name": "Recipient One",
|
||||
"role": "SIGNER",
|
||||
"fields": [
|
||||
{
|
||||
"identifier": 0,
|
||||
"type": "SIGNATURE",
|
||||
"page": 1,
|
||||
"positionX": 0,
|
||||
"positionY": 0,
|
||||
"width": 50,
|
||||
"height": 50
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"email": "recipient-2@example.com",
|
||||
"name": "Recipient Two",
|
||||
"role": "SIGNER",
|
||||
"fields": [
|
||||
{
|
||||
"identifier": 1,
|
||||
"type": "SIGNATURE",
|
||||
"page": 1,
|
||||
"positionX": 0,
|
||||
"positionY": 0,
|
||||
"width": 50,
|
||||
"height": 50
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}' \
|
||||
-F "files=@./field-font-alignment.pdf;type=application/pdf" \
|
||||
-F "files=@./field-meta.pdf;type=application/pdf"
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
See the full request and response formats for the create endpoint
|
||||
[here](https://openapi.documenso.com/reference#tag/envelope/POST/envelope/create)
|
||||
</Callout>
|
||||
|
||||
## Use Template
|
||||
|
||||
Documenso allows you to generate documents from templates. This is useful when you have a standard document format you want to reuse.
|
||||
|
||||
The API endpoint for generating a document from a template is `/api/v2/envelopes/use`, and it takes a JSON payload with the following fields:
|
||||
|
||||
<Callout type="info">
|
||||
See the full request and response formats for the use endpoint
|
||||
[here](https://openapi.documenso.com/reference#tag/envelope/POST/envelope/use)
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
|
||||
### Grab the Template ID
|
||||
|
||||
The first step is to retrieve the ID of the template you want to use. This can be done via the API or from the Documenso UI.
|
||||
|
||||
You can get the ID by navigating to the template page and looking at the URL. The ID is the last part of the URL which looks like this "envelope_xxxxxxxx"
|
||||
|
||||
### Retrieve the Recipient(s) ID(s)
|
||||
|
||||
Once you have the template ID, you will want to retrieve the full template so you can see the recipients.
|
||||
|
||||
This is optional, and you only will want to do this if you want to modify the recipient details prior to creating the new document.
|
||||
|
||||
See the [Get Envelope](#get-envelope) section for more details on how to retrieve the envelope.
|
||||
|
||||
### Generate the Document
|
||||
|
||||
To generate a document from the template, you need to make a POST request to the [`/api/v2/envelope/use`](https://openapi.documenso.com/reference#tag/envelope/POST/envelope/use) endpoint.
|
||||
|
||||
You will need to replace the following:
|
||||
|
||||
- `api_xxxxxxxxxxxxxx` with your API key
|
||||
- `envelope_xxxxxxxxxxxxxxx` with the template ID
|
||||
- `RECIPIENT_ID_HERE` with the recipient ID you want to modify prior to sending
|
||||
- `RECIPIENT_EMAIL_HERE` with the new recipient email you want to use
|
||||
- `RECIPIENT_NAME_HERE` with the new recipient name you want to use
|
||||
|
||||
If you want to send the document immediately, you can add the `distributeDocument` parameter to the payload.
|
||||
|
||||
```sh
|
||||
curl -X POST "https://app.documenso.com/api/v2/envelope/use" \
|
||||
-H "Authorization: api_xxxxxxxxxxxxxx" \
|
||||
-H "Content-Type: multipart/form-data" \
|
||||
-F 'payload={
|
||||
"envelopeId": "envelope_xxxxxxxxxxxxxxx",
|
||||
"recipients": [
|
||||
{
|
||||
"id": RECIPIENT_ID_HERE,
|
||||
"email": "RECIPIENT_EMAIL_HERE",
|
||||
"name": "RECIPIENT_NAME_HERE"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
#### Generate the Document with Custom Files
|
||||
|
||||
You can also generate a document from a template using custom PDF files to replace the template's default PDF files.
|
||||
|
||||
To do this, you will need to grab the `envelopeItemId` of the item you want to replace, this is found in the GET request in the previous step.
|
||||
|
||||
You will need to replace the following:
|
||||
|
||||
- `CUSTOM_FILE_NAME_HERE.pdf` with the custom file name
|
||||
- `envelope_item_xxxxxxxxxxxxxxxxx` with the `envelopeItemId` of the item you want to replace
|
||||
|
||||
```sh
|
||||
curl -X POST "https://app.documenso.com/api/v2/envelope/use" \
|
||||
-H "Authorization: api_xxxxxxxxxxxxxx" \
|
||||
-H "Content-Type: multipart/form-data" \
|
||||
-F 'payload={
|
||||
"envelopeId": "envelope_xxxxxxxxxxxxxxx",
|
||||
"recipients": [
|
||||
{
|
||||
"id": RECIPIENT_ID_HERE,
|
||||
"email": "RECIPIENT_EMAIL_HERE",
|
||||
"name": "RECIPIENT_NAME_HERE"
|
||||
}
|
||||
],
|
||||
"customDocumentData": [
|
||||
{
|
||||
"identifier": "CUSTOM_FILE_NAME_HERE.pdf",
|
||||
"envelopeItemId": "envelope_item_xxxxxxxxxxxxxxxxx"
|
||||
}
|
||||
]
|
||||
}' \
|
||||
-F "files=@./CUSTOM_FILE_NAME_HERE.pdf;type=application/pdf"
|
||||
```
|
||||
|
||||
You can now access the document in your Documenso account dashboard.
|
||||
|
||||
#### Pre-fill Fields On Document Creation
|
||||
|
||||
The API allows you to pre-fill fields on document creation. This is useful when you want to create a document from an existing template and pre-fill the fields with specific values.
|
||||
|
||||
```json
|
||||
{
|
||||
"prefillFields": [
|
||||
{
|
||||
"id": 21,
|
||||
"type": "text",
|
||||
"label": "my-label",
|
||||
"placeholder": "my-placeholder",
|
||||
"value": "my-value"
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "number",
|
||||
"label": "my-label",
|
||||
"placeholder": "my-placeholder",
|
||||
"value": "123"
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"type": "checkbox",
|
||||
"label": "my-label",
|
||||
"placeholder": "my-placeholder",
|
||||
"value": ["option-1", "option-2"]
|
||||
}
|
||||
]
|
||||
// Other payload properties...
|
||||
}
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## Add Recipients
|
||||
|
||||
The API allows you to add recipients to an envelope via the [`/api/v2/envelope/recipient/create-many`](https://openapi.documenso.com/reference#tag/envelope-recipients/POST/envelope/recipient/create-many) endpoint.
|
||||
|
||||
The following is an example of a request which creates 2 new recipients on the envelope.
|
||||
|
||||
```sh
|
||||
curl https://app.documenso.com/api/v2/envelope/recipient/create-many \
|
||||
--request POST \
|
||||
--header 'Authorization: api_xxxxxxxxxxxxxx' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"envelopeId": "envelope_brxkyaxywuxkyeuv",
|
||||
"data": [
|
||||
{
|
||||
"name": "Recipient Name",
|
||||
"email": "example@documenso.com",
|
||||
"role": "SIGNER"
|
||||
},
|
||||
{
|
||||
"name": "Recipient Two",
|
||||
"email": "example+2@documenso.com",
|
||||
"role": "APPROVER"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
## Add Fields
|
||||
|
||||
The API allows you to add fields to an envelope via the [`/api/v2/envelope/field/create-many`](https://openapi.documenso.com/reference#tag/envelope-fields/POST/envelope/field/create-many) endpoint.
|
||||
|
||||
Before adding fields to an envelope, you will need the following details:
|
||||
|
||||
- **Envelope ID** - Which envelope the field will be added to
|
||||
- **Recipient ID** - Which recipient the field will be added to
|
||||
- **Envelope Item ID** - Which file the field will be added to. If blank, the field will be added to the first file.
|
||||
|
||||
See the [Get Envelope](#get-envelope) section for more details on how to retrieve these details.
|
||||
|
||||
### Coordinate-Based Positioning
|
||||
|
||||
The following is an example of a request which creates 2 new fields on the first page of the envelope.
|
||||
|
||||
Note that width, height, positionX and positionY are percentage numbers between 0 and 100, which scale the field relative to the size of the PDF.
|
||||
|
||||
```sh
|
||||
curl https://app.documenso.com/api/v2/envelope/field/create-many \
|
||||
--request POST \
|
||||
--header 'Authorization: api_xxxxxxxxxxxxxx' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"envelopeId": "envelope_xxxxxxxxxx",
|
||||
"data": [
|
||||
{
|
||||
"recipientId": recipient_id_here,
|
||||
"envelopeItemId": "envelope_item_id_here",
|
||||
"type": "TEXT",
|
||||
"page": 1,
|
||||
"positionX": 0,
|
||||
"positionY": 0,
|
||||
"width": 50,
|
||||
"height": 50,
|
||||
"fieldMeta": {
|
||||
"type": "text",
|
||||
"label": "First test field"
|
||||
}
|
||||
},
|
||||
{
|
||||
"recipientId": recipient_id_here,
|
||||
"envelopeItemId": "envelope_item_id_here",
|
||||
"type": "TEXT",
|
||||
"page": 1,
|
||||
"positionX": 50,
|
||||
"positionY": 50,
|
||||
"width": 50,
|
||||
"height": 50,
|
||||
"fieldMeta": {
|
||||
"type": "text",
|
||||
"label": "Second test field"
|
||||
}
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### Placeholder-Based Positioning
|
||||
|
||||
Instead of specifying exact coordinates, you can position fields using placeholder text in the PDF. The API will search for the text and place the field at that location.
|
||||
|
||||
This is useful when:
|
||||
|
||||
- You have PDFs with designated placeholder text (e.g., `{{signature}}`, `[SIGN HERE]`)
|
||||
- You want field positions to adapt to document content changes
|
||||
- You're working with templated documents generated from other systems
|
||||
|
||||
```sh
|
||||
curl https://app.documenso.com/api/v2/envelope/field/create-many \
|
||||
--request POST \
|
||||
--header 'Authorization: api_xxxxxxxxxxxxxx' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"envelopeId": "envelope_xxxxxxxxxx",
|
||||
"data": [
|
||||
{
|
||||
"recipientId": recipient_id_here,
|
||||
"type": "SIGNATURE",
|
||||
"placeholder": "{{signature}}"
|
||||
},
|
||||
{
|
||||
"recipientId": recipient_id_here,
|
||||
"type": "NAME",
|
||||
"placeholder": "{{name}}",
|
||||
"width": 30,
|
||||
"height": 5
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
#### Placeholder Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| ------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| `placeholder` | string | Yes | Text to search for in the PDF. The field is placed at the location of this text. |
|
||||
| `width` | number | No | Override the field width (percentage). If omitted, uses the placeholder text width. |
|
||||
| `height` | number | No | Override the field height (percentage). If omitted, uses the placeholder text height. |
|
||||
| `matchAll` | boolean | No | When `true`, creates a field at every occurrence of the placeholder. Default is `false` (first occurrence only). |
|
||||
|
||||
<Callout type="info">
|
||||
The placeholder text is automatically covered with a white rectangle after field creation, so it
|
||||
won't appear in the final signed document.
|
||||
</Callout>
|
||||
|
||||
#### Multiple Occurrences
|
||||
|
||||
If your PDF contains the same placeholder text multiple times (e.g., initials on every page), use `matchAll: true` to create fields at all occurrences:
|
||||
|
||||
```json
|
||||
{
|
||||
"recipientId": 123,
|
||||
"type": "INITIALS",
|
||||
"placeholder": "{{initials}}",
|
||||
"matchAll": true
|
||||
}
|
||||
```
|
||||
|
||||
This will create one INITIALS field for each occurrence of `{{initials}}` in the PDF.
|
||||
|
||||
#### Mixing Positioning Methods
|
||||
|
||||
You can combine coordinate-based and placeholder-based positioning in the same request:
|
||||
|
||||
```json
|
||||
{
|
||||
"envelopeId": "envelope_xxxxxxxxxx",
|
||||
"data": [
|
||||
{
|
||||
"recipientId": 123,
|
||||
"type": "SIGNATURE",
|
||||
"placeholder": "{{signature}}"
|
||||
},
|
||||
{
|
||||
"recipientId": 123,
|
||||
"type": "DATE",
|
||||
"page": 1,
|
||||
"positionX": 70,
|
||||
"positionY": 85,
|
||||
"width": 20,
|
||||
"height": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Field meta allows you to further configure fields, for example it will allow you to add multiple items for checkboxes or radios.
|
||||
|
||||
A successful request will return a JSON response with the newly added fields.
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
title: API Versioning
|
||||
description: Versioning information for the Documenso public API.
|
||||
---
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# API Versioning
|
||||
|
||||
Documenso uses API versioning to manage changes to the public API. This allows us to introduce new features, fix bugs, and make other changes without breaking existing integrations.
|
||||
|
||||
<Callout type="info">The current version of the API is `v2`.</Callout>
|
||||
|
||||
The API version is specified in the URL. For example, the base URL for the `v2` API is `https://app.documenso.com/api/v2`.
|
||||
|
||||
We may make changes to the API without incrementing the version number. We will always try to avoid breaking changes, but in some cases, it may be necessary to make changes that are not backward compatible. In these cases, we will increment the version number and provide information about the changes in the release notes.
|
||||
|
||||
Also, we may deprecate certain features or endpoints in the API. When we deprecate a feature or endpoint, we will provide information about the deprecation in the release notes and give a timeline for when the feature or endpoint will be removed.
|
||||
@@ -1,8 +0,0 @@
|
||||
export default {
|
||||
index: 'Getting Started',
|
||||
'signing-certificate': 'Signing Certificate',
|
||||
'how-to': 'How To',
|
||||
'setting-up-oauth-providers': 'Setting up OAuth Providers',
|
||||
telemetry: 'Telemetry',
|
||||
'ai-features': 'AI Recipient & Field Detection',
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
title: AI Recipient & Field Detection (Self-hosting)
|
||||
description: Configure Google Vertex AI so Documenso can detect recipients and fields automatically.
|
||||
---
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# AI Recipient & Field Detection (Self-hosting)
|
||||
|
||||
This guide covers how to enable the AI recipient and field detection features when you self-host Documenso.
|
||||
|
||||
## What this enables
|
||||
|
||||
- Detect recipients from uploaded PDFs (roles, names, emails when present).
|
||||
- Detect and place fields (signature, initials, name, email, date, text, number, radio, checkbox) onto draft envelopes.
|
||||
- Built-in rate limits (3 requests per minute per IP) to prevent abuse.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A Google Cloud project with the **Vertex AI API** enabled and billing active.
|
||||
- A **Vertex AI Express API key** with access to Gemini models (create via the [Vertex AI Express flow](https://cloud.google.com/vertex-ai/generative-ai/docs/start/express-mode/overview) and manage keys in [API keys](https://cloud.google.com/vertex-ai/generative-ai/docs/start/api-keys)).
|
||||
- Documenso version that includes the AI detection feature and the corresponding database migration.
|
||||
|
||||
## Configure environment variables
|
||||
|
||||
Add these variables to your deployment `.env` (or secret manager):
|
||||
|
||||
```
|
||||
GOOGLE_VERTEX_PROJECT_ID="<your-gcp-project-id>"
|
||||
GOOGLE_VERTEX_API_KEY="<your-vertex-api-key>"
|
||||
# Optional, defaults to "global"
|
||||
GOOGLE_VERTEX_LOCATION="global"
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
Use a region close to your users if you need data residency considerations (e.g. `europe-west1`).
|
||||
If you omit the location, Documenso uses `global`. Not all models are available in every region;
|
||||
if a model is unavailable, switch to a supported region.
|
||||
</Callout>
|
||||
|
||||
## Deploy with the published container
|
||||
|
||||
- Use the official Documenso image (DockerHub or GHCR) and supply the Vertex env vars above.
|
||||
- Ensure migrations run on startup (the container runs `prisma migrate deploy` in production mode).
|
||||
- Restart the container after adding or changing Vertex env vars.
|
||||
|
||||
## Enable the feature in Documenso
|
||||
|
||||
Once the service is running with the Vertex env vars:
|
||||
|
||||
<Steps>
|
||||
### Organisation settings
|
||||
|
||||
Go to **Settings → Document Preferences → AI Features** and set to **Enabled**. Teams that inherit organisation defaults will pick this up.
|
||||
|
||||
### Team settings
|
||||
|
||||
If a team overrides organisation defaults, go to **Team Settings → Document Preferences → AI Features** and choose **Enabled** (or **Inherit** to follow the organisation).
|
||||
|
||||
### Verify in the editor
|
||||
|
||||
Open a draft envelope. In **Recipients**, you should see the sparkle button for AI detection. In **Fields**, you should see **Detect with AI** available.
|
||||
|
||||
</Steps>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Too many requests**: Wait a minute or two and retry (rate limit is 3/min per IP).
|
||||
- **AI options hidden**: Ensure the env vars are set, the server was restarted after setting them, and `aiFeaturesEnabled` is enabled at organisation/team level.
|
||||
- **Detection fails immediately**: Confirm the Vertex API key is valid and the project has Vertex AI enabled. Check server logs for status codes from Vertex.
|
||||
|
||||
If issues persist, recheck env vars, restart the service, and confirm the Prisma migration was applied.
|
||||
@@ -1,399 +0,0 @@
|
||||
---
|
||||
title: How to Self-Host Documenso
|
||||
description: Learn how to self-host Documenso on your server or cloud infrastructure using Docker or other deployment methods.
|
||||
---
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
import { CallToAction } from '../../../components/call-to-action';
|
||||
|
||||
# Self Hosting
|
||||
|
||||
We support various deployment methods and are actively working on adding more. Please let us know if you have a specific deployment method in mind!
|
||||
|
||||
## Manual Deployment
|
||||
|
||||
The following guide will walk you through setting up Documenso manually on your server or cloud infrastructure.
|
||||
|
||||
<Steps>
|
||||
### Clone the Repository
|
||||
|
||||
The first step is to clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/documenso/documenso.git
|
||||
```
|
||||
|
||||
### Configure the Environment Variables
|
||||
|
||||
Navigate to the `documenso` folder and create a `.env` file from the example `.env.example` file:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Open the `.env` file and fill in the following variables:
|
||||
|
||||
```bash
|
||||
- NEXTAUTH_SECRET
|
||||
- NEXT_PUBLIC_WEBAPP_URL
|
||||
- NEXT_PRIVATE_DATABASE_URL
|
||||
- NEXT_PRIVATE_DIRECT_DATABASE_URL
|
||||
- NEXT_PRIVATE_SMTP_FROM_NAME
|
||||
- NEXT_PRIVATE_SMTP_FROM_ADDRESS
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
If you use a reverse proxy in front of Documenso, don't forget to provide the public URL for the
|
||||
`NEXT_PUBLIC_WEBAPP_URL` variable!
|
||||
</Callout>
|
||||
|
||||
### Install the Dependencies
|
||||
|
||||
Install the project dependencies as follows:
|
||||
|
||||
```bash
|
||||
npm i
|
||||
npm run build
|
||||
npm run prisma:migrate-deploy
|
||||
```
|
||||
|
||||
### Start the Application
|
||||
|
||||
Finally, start the application:
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
This will start the server on `localhost:3000`. Any reverse proxy can handle the front end and SSL termination.
|
||||
|
||||
<Callout type="info">
|
||||
If you want to run with another port than `3000`, you can start the application with `next -p <ANY PORT>` from the `apps/remix` folder.
|
||||
</Callout>
|
||||
|
||||
</Steps>
|
||||
|
||||
## Docker
|
||||
|
||||
The following guide will walk you through setting up Documenso using Docker. You can choose between a Docker Compose production setup or a standalone container.
|
||||
|
||||
We provide a Docker container for Documenso, published on both DockerHub and GitHub Container Registry.
|
||||
|
||||
- [DockerHub](https://hub.docker.com/r/documenso/documenso)
|
||||
- [GitHub Container Registry](https://ghcr.io/documenso/documenso)
|
||||
|
||||
You can pull the Docker image from either of these registries and run it with your preferred container hosting provider.
|
||||
|
||||
Please note that you must provide environment variables for connecting to the database, mail server, and other services.
|
||||
|
||||
### Option 1: Production Docker Compose Setup
|
||||
|
||||
This setup includes a PostgreSQL database and the Documenso application. You will need to provide your own SMTP details using environment variables.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Download the Docker Compose File
|
||||
|
||||
Download the Docker Compose file from the Documenso repository - [compose.yml](https://raw.githubusercontent.com/documenso/documenso/release/docker/production/compose.yml).
|
||||
|
||||
### Navigate to the `compose.yml` File
|
||||
|
||||
Once downloaded, navigate to the directory containing the `compose.yml` file.
|
||||
|
||||
### Set Up Environment Variables
|
||||
|
||||
Create a `.env` file in the same directory as the `compose.yml` file.
|
||||
|
||||
Then add your SMTP details as well as the following environment variables:
|
||||
|
||||
```bash
|
||||
NEXTAUTH_SECRET="<your-secret>"
|
||||
NEXT_PRIVATE_ENCRYPTION_KEY="<your-key>"
|
||||
NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY="<your-secondary-key>"
|
||||
NEXT_PUBLIC_WEBAPP_URL="<your-url>"
|
||||
NEXT_PRIVATE_SMTP_TRANSPORT="smtp-auth"
|
||||
NEXT_PRIVATE_SMTP_HOST="<your-host>"
|
||||
NEXT_PRIVATE_SMTP_PORT=<your-port>
|
||||
NEXT_PRIVATE_SMTP_USERNAME="<your-username>"
|
||||
NEXT_PRIVATE_SMTP_PASSWORD="<your-password>"
|
||||
```
|
||||
|
||||
For full AI setup details (including model availability notes), see the [AI Recipient & Field Detection (Self-hosting)](./ai-features) page.
|
||||
|
||||
### Set Up Your Signing Certificate
|
||||
|
||||
<Callout type="warning">
|
||||
This is the most common source of issues for self-hosters. Please follow these steps carefully.
|
||||
</Callout>
|
||||
|
||||
The `cert.p12` file is required to sign and encrypt documents. You have three options:
|
||||
|
||||
#### Option A: Generate Certificate Inside Container (Recommended)
|
||||
|
||||
This method avoids file permission issues by creating the certificate directly inside the Docker container:
|
||||
|
||||
1. Start your containers:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
2. Set certificate password securely and generate certificate inside the container:
|
||||
|
||||
```bash
|
||||
# Set certificate password securely (won't appear in command history)
|
||||
read -s -p "Enter certificate password: " CERT_PASS
|
||||
echo
|
||||
|
||||
# Generate certificate inside container using environment variable
|
||||
docker exec -e CERT_PASS="$CERT_PASS" -it documenso-production-documenso-1 bash -c "
|
||||
mkdir -p /app/certs && \
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout /tmp/private.key \
|
||||
-out /tmp/certificate.crt \
|
||||
-subj '/C=US/ST=State/L=City/O=Organization/CN=localhost' && \
|
||||
openssl pkcs12 -export -out /app/certs/cert.p12 \
|
||||
-inkey /tmp/private.key -in /tmp/certificate.crt \
|
||||
-passout env:CERT_PASS && \
|
||||
rm /tmp/private.key /tmp/certificate.crt
|
||||
"
|
||||
```
|
||||
|
||||
3. Add the certificate passphrase to your `.env` file:
|
||||
|
||||
```bash
|
||||
NEXT_PRIVATE_SIGNING_PASSPHRASE="your_password_here"
|
||||
```
|
||||
|
||||
4. Restart the container to apply changes:
|
||||
```bash
|
||||
docker-compose restart documenso
|
||||
```
|
||||
|
||||
#### Option B: Use an Existing Certificate File
|
||||
|
||||
If you have an existing `.p12` certificate file:
|
||||
|
||||
1. **Place your certificate file** in an accessible location on your host system
|
||||
2. **Set proper permissions:**
|
||||
|
||||
```bash
|
||||
# Make sure the certificate is readable
|
||||
chmod 644 /path/to/your/cert.p12
|
||||
|
||||
# For Docker, ensure proper ownership
|
||||
chown 1001:1001 /path/to/your/cert.p12
|
||||
```
|
||||
|
||||
3. **Update the volume binding** in the `compose.yml` file:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /path/to/your/cert.p12:/opt/documenso/cert.p12:ro
|
||||
```
|
||||
|
||||
4. **Add certificate configuration** to your `.env` file:
|
||||
```bash
|
||||
NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/opt/documenso/cert.p12
|
||||
NEXT_PRIVATE_SIGNING_PASSPHRASE=your_certificate_password
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
Your certificate MUST have a password. Certificates without passwords will cause "Failed to get
|
||||
private key bags" errors.
|
||||
</Callout>
|
||||
|
||||
After setting up your certificate, save the `compose.yml` file and run the following command to start the containers:
|
||||
|
||||
```bash
|
||||
docker-compose --env-file ./.env up -d
|
||||
```
|
||||
|
||||
The command will start the PostgreSQL database and the Documenso application containers.
|
||||
|
||||
### Access the Application
|
||||
|
||||
Access the Documenso application by visiting `http://localhost:3000` in your web browser.
|
||||
|
||||
</Steps>
|
||||
|
||||
### Option 2: Standalone Docker Container
|
||||
|
||||
If you prefer to host the Documenso application on a specific container provider, use the pre-built Docker image from DockerHub or GitHub's Package Registry. You will need to provide your own database and SMTP host.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Pull the Docker Image
|
||||
|
||||
Pull the Documenso Docker image from DockerHub:
|
||||
|
||||
```bash
|
||||
docker pull documenso/documenso
|
||||
```
|
||||
|
||||
Or, pull the image from GitHub Container Registry:
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/documenso/documenso
|
||||
```
|
||||
|
||||
### Run the Docker Container
|
||||
|
||||
Run the Docker container with the required environment variables:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
-p 3000:3000 \
|
||||
-e NEXTAUTH_SECRET="<your-nextauth-secret>"
|
||||
-e NEXT_PRIVATE_ENCRYPTION_KEY="<your-next-private-encryption-key>"
|
||||
-e NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY="<your-next-private-encryption-secondary-key>"
|
||||
-e NEXT_PUBLIC_WEBAPP_URL="<your-next-public-webapp-url>"
|
||||
-e NEXT_PRIVATE_DATABASE_URL="<your-next-private-database-url>"
|
||||
-e NEXT_PRIVATE_DIRECT_DATABASE_URL="<your-next-private-database-url>"
|
||||
-e NEXT_PRIVATE_SMTP_TRANSPORT="<your-next-private-smtp-transport>"
|
||||
-e NEXT_PRIVATE_SMTP_FROM_NAME="<your-next-private-smtp-from-name>"
|
||||
-e NEXT_PRIVATE_SMTP_FROM_ADDRESS="<your-next-private-smtp-from-address>"
|
||||
-v /path/to/your/keyfile.p12:/opt/documenso/cert.p12
|
||||
documenso/documenso
|
||||
```
|
||||
|
||||
Replace the placeholders with the actual values.
|
||||
|
||||
### Access the Application
|
||||
|
||||
You can access the Documenso application by visiting the URL you provided for the `NEXT_PUBLIC_WEBAPP_URL` environment variable in your web browser.
|
||||
|
||||
</Steps>
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
The environment variables listed above are a subset of those available for configuring Documenso. The table below provides a complete list of environment variables and their descriptions.
|
||||
|
||||
For AI setup specifics, see the [AI Recipient & Field Detection (Self-hosting)](./ai-features) page.
|
||||
|
||||
| Variable | Description |
|
||||
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `PORT` | The port on which the Documenso application runs. It defaults to `3000`. |
|
||||
| `NEXTAUTH_SECRET` | The secret key used by NextAuth.js for encryption and signing. |
|
||||
| `NEXT_PRIVATE_ENCRYPTION_KEY` | The primary encryption key for symmetric encryption and decryption (at least 32 characters). |
|
||||
| `NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY` | The secondary encryption key for symmetric encryption and decryption (at least 32 characters). |
|
||||
| `NEXT_PRIVATE_GOOGLE_CLIENT_ID` | The Google client ID for Google authentication (optional). |
|
||||
| `NEXT_PRIVATE_GOOGLE_CLIENT_SECRET` | The Google client secret for Google authentication (optional). |
|
||||
| `NEXT_PRIVATE_MICROSOFT_CLIENT_ID` | The Microsoft client ID for Microsoft authentication (optional). |
|
||||
| `NEXT_PRIVATE_MICROSOFT_CLIENT_SECRET` | The Microsoft client secret for Microsoft authentication (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_CLIENT_ID` | The OIDC client ID for OIDC authentication (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_CLIENT_SECRET` | The OIDC client secret for OIDC authentication (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_WELL_KNOWN` | The well-known URL for the OIDC provider (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_PROVIDER_LABEL` | The label to display for the OIDC provider button (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_SKIP_VERIFY` | Whether to skip email verification for OIDC accounts (optional, default `false`). |
|
||||
| `NEXT_PUBLIC_WEBAPP_URL` | The URL for the web application. |
|
||||
| `NEXT_PUBLIC_SUPPORT_EMAIL` | The support email address displayed to users (default `support@documenso.com`). |
|
||||
| `NEXT_PRIVATE_DATABASE_URL` | The URL for the primary database connection (with connection pooling). |
|
||||
| `NEXT_PRIVATE_DIRECT_DATABASE_URL` | The URL for the direct database connection (without connection pooling). |
|
||||
| `NEXT_PRIVATE_SIGNING_TRANSPORT` | The signing transport to use. Available options: local (default), gcloud-hsm |
|
||||
| `NEXT_PRIVATE_SIGNING_PASSPHRASE` | The passphrase for the key file. |
|
||||
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS` | The base64-encoded contents of the key file will be used instead of the file path. |
|
||||
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH` | The path to the key file, default `/opt/documenso/cert.p12`. |
|
||||
| `NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY` | Comma-separated list of timestamp authority URLs for PDF signing. Enables LTV and archival timestamps. |
|
||||
| `NEXT_PUBLIC_SIGNING_CONTACT_INFO` | Contact info to embed in PDF signatures. Defaults to the webapp URL. |
|
||||
| `NEXT_PRIVATE_USE_LEGACY_SIGNING_SUBFILTER` | Set to "true" to use the legacy adbe.pkcs7.detached subfilter instead of ETSI.CAdES.detached. |
|
||||
| `NEXT_PUBLIC_UPLOAD_TRANSPORT` | The transport for file uploads (database or s3). |
|
||||
| `NEXT_PRIVATE_UPLOAD_ENDPOINT` | The endpoint for the S3 storage transport (for third-party S3-compatible providers). |
|
||||
| `NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE` | Whether to force path-style URLs for the S3 storage transport. |
|
||||
| `NEXT_PRIVATE_UPLOAD_REGION` | The region for the S3 storage transport (defaults to us-east-1). |
|
||||
| `NEXT_PRIVATE_UPLOAD_BUCKET` | The bucket to use for the S3 storage transport. |
|
||||
| `NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID` | The access key ID for the S3 storage transport. |
|
||||
| `NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY` | The secret access key for the S3 storage transport. |
|
||||
| `NEXT_PRIVATE_SMTP_TRANSPORT` | The transport to send emails (smtp-auth, smtp-api, resend, or mailchannels). |
|
||||
| `NEXT_PRIVATE_SMTP_HOST` | The host for the SMTP server for SMTP transports. |
|
||||
| `NEXT_PRIVATE_SMTP_PORT` | The port for the SMTP server for SMTP transports. |
|
||||
| `NEXT_PRIVATE_SMTP_USERNAME` | The username for the SMTP server for the `smtp-auth` transport. |
|
||||
| `NEXT_PRIVATE_SMTP_PASSWORD` | The password for the SMTP server for the `smtp-auth` transport. |
|
||||
| `NEXT_PRIVATE_SMTP_APIKEY_USER` | The API key user for the SMTP server for the `smtp-api` transport. |
|
||||
| `NEXT_PRIVATE_SMTP_APIKEY` | The API key for the SMTP server for the `smtp-api` transport. |
|
||||
| `NEXT_PRIVATE_SMTP_SECURE` | Whether to force the use of TLS for the SMTP server for SMTP transports. |
|
||||
| `NEXT_PRIVATE_SMTP_UNSAFE_IGNORE_TLS` | Whether to ignore TLS errors for the SMTP server (useful for self-signed certificates). |
|
||||
| `NEXT_PRIVATE_SMTP_FROM_ADDRESS` | The email address for the "from" address. |
|
||||
| `NEXT_PRIVATE_SMTP_FROM_NAME` | The sender name for the "from" address. |
|
||||
| `NEXT_PRIVATE_RESEND_API_KEY` | The API key for Resend.com for the `resend` transport. |
|
||||
| `NEXT_PRIVATE_MAILCHANNELS_API_KEY` | The optional API key for MailChannels (if using a proxy) for the `mailchannels` transport. |
|
||||
| `NEXT_PRIVATE_MAILCHANNELS_ENDPOINT` | The optional endpoint for the MailChannels API (if using a proxy) for the `mailchannels` transport. |
|
||||
| `NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN` | The domain for DKIM signing with MailChannels for the `mailchannels` transport. |
|
||||
| `NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR` | The selector for DKIM signing with MailChannels for the `mailchannels` transport. |
|
||||
| `NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY` | The private key for DKIM signing with MailChannels for the `mailchannels` transport. |
|
||||
| `NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT` | The maximum document upload limit displayed to the user (in MB). |
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | The optional PostHog key for analytics and feature flags. |
|
||||
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Whether to disable user signups through the /signup page. |
|
||||
| `NEXT_PRIVATE_BROWSERLESS_URL` | The URL for a Browserless.io instance to generate PDFs (optional). |
|
||||
| `DOCUMENSO_DISABLE_TELEMETRY` | Set to `true` to disable anonymous telemetry (see [Telemetry](#telemetry) section below). |
|
||||
| `GOOGLE_VERTEX_PROJECT_ID` | Google Cloud project ID used for Vertex AI (required for AI detection). |
|
||||
| `GOOGLE_VERTEX_API_KEY` | Vertex AI Express API key with access to Gemini models (required for AI detection). See [AI Recipient & Field Detectionfor](./ai-features) for details. |
|
||||
| `GOOGLE_VERTEX_LOCATION` | Optional Vertex region, defaults to `global`. Not all models are available in every region. |
|
||||
|
||||
## Telemetry
|
||||
|
||||
Documenso collects anonymous telemetry data to help us understand how the software is being used and improve the product. This telemetry is **enabled by default** for self-hosted instances.
|
||||
|
||||
### What We Collect
|
||||
|
||||
We collect minimal, privacy-preserving data:
|
||||
|
||||
- **App Version**: The version of Documenso you are running
|
||||
- **Installation ID**: A unique identifier for your installation (stored in your database)
|
||||
- **Node ID**: A unique identifier for each server/container instance (stored in the OS temp directory)
|
||||
|
||||
We do **not** collect any personal data, document contents, user information, or usage patterns.
|
||||
|
||||
### Events
|
||||
|
||||
- **Server Startup**: Captured once when the server starts
|
||||
- **Server Heartbeat**: Captured every hour while the server is running
|
||||
|
||||
### Disabling Telemetry
|
||||
|
||||
To disable telemetry, set the following environment variable:
|
||||
|
||||
```bash
|
||||
DOCUMENSO_DISABLE_TELEMETRY=true
|
||||
```
|
||||
|
||||
This will completely disable all telemetry data collection.
|
||||
|
||||
## Run as a Service
|
||||
|
||||
You can run the application using a `systemd.service` file. Here is a simple example of the service running on port `3500` (using `3000` by default):
|
||||
|
||||
```bash
|
||||
[Unit]
|
||||
Description=documenso
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Environment=PATH=/path/to/your/node/binaries
|
||||
Type=simple
|
||||
User=www-data
|
||||
WorkingDirectory=/var/www/documenso/apps/remix
|
||||
ExecStart=/usr/bin/next start -p 3500
|
||||
TimeoutSec=15
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
We offer several alternative deployment methods for Documenso if you need more options.
|
||||
|
||||
## Railway
|
||||
|
||||
[](https://railway.app/template/bG6D4p)
|
||||
|
||||
## Render
|
||||
|
||||
[](https://render.com/deploy?repo=https://github.com/documenso/documenso)
|
||||
|
||||
## Koyeb
|
||||
|
||||
[](https://app.koyeb.com/deploy?type=git&repository=github.com/documenso/documenso&branch=main&name=documenso-app&builder=dockerfile&dockerfile=/docker/Dockerfile)
|
||||
|
||||
<CallToAction className="mt-12" utmSource="self-hosting" />
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
title: Getting Started with Self-Hosting
|
||||
description: A step-by-step guide to setting up and hosting your own Documenso instance.
|
||||
---
|
||||
|
||||
import { CallToAction } from '../../../components/call-to-action';
|
||||
|
||||
# Getting Started with Self-Hosting
|
||||
|
||||
This is a step-by-step guide to setting up and hosting your own Documenso instance. Before getting started, [select the right license for you](/users/licenses).
|
||||
|
||||
<CallToAction className="mt-12" utmSource="self-hosting" />
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user