Compare commits

..

29 Commits

Author SHA1 Message Date
Ephraim Atta-Duncan 5103477e7b fix: new get stats query 2024-12-11 00:40:42 +00:00
Ephraim Atta-Duncan b19b57dbc9 fix: get stats query 2024-12-10 23:33:14 +00:00
Mythie 2b3ab9a3b7 fix: remove compiled translation files 2024-11-27 13:57:45 +11:00
Mythie cac262fcea Merge branch 'main' into feat/delete-archive 2024-11-27 10:57:13 +11:00
Ephraim Atta-Duncan 0cd7c25718 fix: avoid delete duplicate button 2024-11-22 11:26:36 +00:00
Ephraim Atta-Duncan 79eec5f451 chore: translations 2024-11-22 11:24:26 +00:00
Ephraim Atta-Duncan 7ca0975650 fix: build errrors 2024-11-22 11:22:25 +00:00
Ephraim Atta-Duncan 870b3fb3d7 fix: match translations with main 2024-11-22 11:18:32 +00:00
Ephraim Atta-Duncan 43ea76fae3 fix: merge conflicts 2024-11-22 11:12:49 +00:00
Ephraim Atta-Duncan 2dd122aed3 fix: merge conflicts
againnnn
2024-10-16 15:41:41 +00:00
Ephraim Atta-Duncan d3872e86f1 fix: merge conflicts 2024-10-16 15:39:59 +00:00
Ephraim Atta-Duncan 171398ae2d fix: merge conflicts 2024-09-21 09:07:16 +00:00
Ephraim Atta-Duncan 492350612e fix: wrong count on user 2024-09-21 08:19:24 +00:00
Ephraim Atta-Duncan f9935adb57 fix: incorrect counts and query for teams 2024-09-20 06:28:08 +00:00
Mythie d2b99303f9 fix: simplify deleted query 2024-08-21 13:13:03 +10:00
github-actions d33bbe71e7 chore: extract translations 2024-08-21 01:44:57 +00:00
Lucas Smith 6bf62e0ecb Merge branch 'main' into feat/delete-archive 2024-08-21 11:44:05 +10:00
Ephraim Atta-Duncan 16527f01e7 fix: build errors 2024-07-29 15:18:54 +10:00
Ephraim Atta-Duncan 7f25508c3c chore: remove unused email template 2024-07-29 15:18:54 +10:00
Ephraim Atta-Duncan 754e9e6428 chore: refactor find documents 2024-07-29 15:18:54 +10:00
Ephraim Atta-Duncan 2837b178fb fix: soft delete a document when the owner deletes it 2024-07-29 15:18:54 +10:00
Ephraim Atta-Duncan 26ccdc1b23 fix: filter on recipients 2024-07-29 15:18:54 +10:00
Ephraim Atta-Duncan ea63b45a13 fix: add recipients filter for bin 2024-07-29 15:18:54 +10:00
Ephraim Atta-Duncan feef4b1a12 feat: restore deleted document 2024-07-29 15:18:52 +10:00
Ephraim Atta-Duncan 1a55f4253b fix: tab count for teams 2024-07-29 14:41:02 +10:00
Ephraim Atta-Duncan 8311e0cc29 fix: show correct tab count 2024-07-29 14:41:02 +10:00
Ephraim Atta-Duncan a9adc36732 fix: date filter for deleted documents 2024-07-29 14:41:02 +10:00
Ephraim Atta-Duncan 73e375938c fix: show deleted counts on tab 2024-07-29 14:41:02 +10:00
Ephraim Atta-Duncan c6393b7a9e feat: add bin tab to show soft deleted documents 2024-07-29 14:41:02 +10:00
2660 changed files with 178312 additions and 321786 deletions
View File
View File
@@ -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,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
View File
+16 -43
View File
@@ -1,7 +1,4 @@
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
@@ -9,25 +6,20 @@ Code Style and Structure:
- 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 TypeScript for all code; prefer interfaces over types
- Avoid enums; use maps instead
- Use functional components with TypeScript interfaces
Syntax and Formatting:
- Create functions using `const fn = () => {}`
- Use the "function" keyword for pure functions
- 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
@@ -36,40 +28,21 @@ Error Handling and Validation:
- 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
Performance Optimization:
- Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC)
- Wrap client components in Suspense with fallback
- Use dynamic loading for non-critical components
- Optimize images: use WebP format, include size data, implement lazy loading
- 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
Key Conventions:
- Use 'nuqs' for URL search parameter state management
- Optimize Web Vitals (LCP, CLS, FID)
- Limit 'use client':
- Favor server components and Next.js SSR
- Use only for Web API access in small components
- Avoid for data fetching or state management
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
Follow Next.js docs for Data Fetching, Rendering, and Routing
+8 -2
View File
@@ -10,7 +10,13 @@
"ghcr.io/devcontainers/features/node:1": {}
},
"onCreateCommand": "./.devcontainer/on-create.sh",
"forwardPorts": [3000, 54320, 9000, 2500, 1100],
"forwardPorts": [
3000,
54320,
9000,
2500,
1100
],
"customizations": {
"vscode": {
"extensions": [
@@ -29,4 +35,4 @@
]
}
}
}
}
+13 -53
View File
@@ -1,4 +1,5 @@
# [[AUTH]]
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="secret"
# [[CRYPTO]]
@@ -13,30 +14,22 @@ NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY="DEADBEEF"
# 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"
# This can be used to still allow signups for OIDC connections
# when signup is disabled via `NEXT_PUBLIC_DISABLE_SIGNUP`
NEXT_PRIVATE_OIDC_ALLOW_SIGNUP=""
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"
NEXT_PUBLIC_MARKETING_URL="http://localhost:3001"
# 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
# [[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.
@@ -59,18 +52,6 @@ NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH=
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=
# [[STORAGE]]
# OPTIONAL: Defines the storage transport to use. Available options: database (default) | s3
@@ -129,18 +110,16 @@ 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=
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID=
NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID=
# [[BACKGROUND JOBS]]
NEXT_PRIVATE_JOBS_PROVIDER="local"
NEXT_PRIVATE_TRIGGER_API_KEY=
NEXT_PRIVATE_TRIGGER_API_URL=
NEXT_PRIVATE_INNGEST_EVENT_KEY=
# [[FEATURES]]
@@ -150,32 +129,13 @@ NEXT_PUBLIC_POSTHOG_KEY=""
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=
-1
View File
@@ -5,7 +5,6 @@ module.exports = {
rules: {
'@next/next/no-img-element': 'off',
'no-unreachable': 'error',
'react-hooks/exhaustive-deps': 'off',
},
settings: {
next: {
+5
View File
@@ -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. -->
+24
View File
@@ -0,0 +1,24 @@
name: Cache production build binaries
description: 'Cache or restore if necessary'
inputs:
node_version:
required: false
default: v20.x
runs:
using: 'composite'
steps:
- name: Cache production build
uses: actions/cache@v3
id: production-build-cache
with:
path: |
${{ github.workspace }}/apps/web/.next
${{ github.workspace }}/apps/marketing/.next
**/.turbo/**
**/dist/**
key: prod-build-${{ github.run_id }}-${{ hashFiles('package-lock.json') }}
restore-keys: prod-build-
- run: npm run build
shell: bash
+1 -1
View File
@@ -2,7 +2,7 @@ name: 'Setup node and cache node_modules'
inputs:
node_version:
required: false
default: v22.x
default: v20.x
runs:
using: 'composite'
+10
View File
@@ -11,6 +11,16 @@ updates:
- 'ci'
open-pull-requests-limit: 0
- package-ecosystem: 'npm'
directory: '/apps/marketing'
schedule:
interval: 'weekly'
target-branch: 'main'
labels:
- 'npm dependencies'
- 'frontend'
open-pull-requests-limit: 0
- package-ecosystem: 'npm'
directory: '/apps/web'
schedule:
+3
View File
@@ -1,3 +1,6 @@
'apps: marketing':
- apps/marketing/**
'apps: web':
- apps/web/**
+1 -2
View File
@@ -26,8 +26,7 @@ jobs:
- name: Copy env
run: cp .env.example .env
- name: Build app
run: npm run build
- uses: ./.github/actions/cache-build
build_docker:
name: Build Docker Image
+29
View File
@@ -0,0 +1,29 @@
name: cleanup caches by a branch
on:
pull_request:
types:
- closed
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Cleanup
run: |
gh extension install actions/gh-actions-cache
echo "Fetching list of cache key"
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
## Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR
do
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge
+1 -2
View File
@@ -30,8 +30,7 @@ jobs:
- uses: ./.github/actions/node-install
- name: Build app
run: npm run build
- uses: ./.github/actions/cache-build
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
+3 -9
View File
@@ -4,16 +4,11 @@ on:
branches: ['main']
pull_request:
branches: ['main']
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
e2e_tests:
name: 'E2E Tests'
timeout-minutes: 60
runs-on: warp-ubuntu-2204-x64-8x
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -33,8 +28,7 @@ jobs:
- name: Seed the database
run: npm run prisma:seed
- name: Install playwright browsers
run: npx playwright install --with-deps
- uses: ./.github/actions/cache-build
- name: Run Playwright tests
run: npm run ci
@@ -47,7 +41,7 @@ jobs:
with:
name: test-results
path: 'packages/app-tests/**/test-results/*'
retention-days: 7
retention-days: 30
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
+4 -123
View File
@@ -36,8 +36,6 @@ jobs:
- 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 }}
run: |
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
GIT_SHA="$(git rev-parse HEAD)"
@@ -45,8 +43,6 @@ jobs:
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" \
@@ -65,47 +61,6 @@ jobs:
env:
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
- name: Build the chromium docker image
env:
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
run: |
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
GIT_SHA="$(git rev-parse HEAD)"
docker build \
-f ./docker/Dockerfile.chromium \
--progress=plain \
--build-arg TAG="$GIT_SHA" \
-t "documenso/documenso-$BUILD_PLATFORM:latest-chromium" \
-t "documenso/documenso-$BUILD_PLATFORM:$GIT_SHA-chromium" \
-t "documenso/documenso-$BUILD_PLATFORM:$APP_VERSION-chromium" \
-t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:latest-chromium" \
-t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$GIT_SHA-chromium" \
-t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$APP_VERSION-chromium" \
.
- name: Push the chromium docker image to DockerHub
env:
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
run: |
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
GIT_SHA="$(git rev-parse HEAD)"
docker push "documenso/documenso-$BUILD_PLATFORM:latest-chromium"
docker push "documenso/documenso-$BUILD_PLATFORM:$GIT_SHA-chromium"
docker push "documenso/documenso-$BUILD_PLATFORM:$APP_VERSION-chromium" \
- name: Push the chromium docker image to GitHub Container Registry
env:
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
run: |
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
GIT_SHA="$(git rev-parse HEAD)"
docker push "ghcr.io/documenso/documenso-$BUILD_PLATFORM:latest-chromium"
docker push "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$GIT_SHA-chromium"
docker push "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$APP_VERSION-chromium"
create_and_publish_manifest:
name: Create and publish manifest
runs-on: ubuntu-latest
@@ -135,7 +90,7 @@ jobs:
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
if [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
docker manifest create \
documenso/documenso:latest \
--amend documenso/documenso-amd64:latest \
@@ -144,7 +99,7 @@ jobs:
docker manifest push documenso/documenso:latest
fi
if [[ "$APP_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
if [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
docker manifest create \
documenso/documenso:rc \
--amend documenso/documenso-amd64:rc \
@@ -166,50 +121,13 @@ jobs:
docker manifest push documenso/documenso:$GIT_SHA
docker manifest push documenso/documenso:$APP_VERSION
- name: Create and push DockerHub chromium manifest
run: |
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
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-chromium \
--amend documenso/documenso-amd64:latest-chromium \
--amend documenso/documenso-arm64:latest-chromium
docker manifest push documenso/documenso:latest-chromium
fi
if [[ "$APP_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
docker manifest create \
documenso/documenso:rc-chromium \
--amend documenso/documenso-amd64:rc-chromium \
--amend documenso/documenso-arm64:rc-chromium
docker manifest push documenso/documenso:rc-chromium
fi
docker manifest create \
documenso/documenso:$GIT_SHA-chromium \
--amend documenso/documenso-amd64:$GIT_SHA-chromium \
--amend documenso/documenso-arm64:$GIT_SHA-chromium
docker manifest create \
documenso/documenso:$APP_VERSION-chromium \
--amend documenso/documenso-amd64:$APP_VERSION-chromium \
--amend documenso/documenso-arm64:$APP_VERSION-chromium
docker manifest push documenso/documenso:$GIT_SHA-chromium
docker manifest push documenso/documenso:$APP_VERSION-chromium
- name: Create and push Github Container Registry manifest
run: |
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
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
if [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
docker manifest create \
ghcr.io/documenso/documenso:latest \
--amend ghcr.io/documenso/documenso-amd64:latest \
@@ -218,7 +136,7 @@ jobs:
docker manifest push ghcr.io/documenso/documenso:latest
fi
if [[ "$APP_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
if [[ "$APP_VERSION" =~ ^[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 \
@@ -239,40 +157,3 @@ jobs:
docker manifest push ghcr.io/documenso/documenso:$GIT_SHA
docker manifest push ghcr.io/documenso/documenso:$APP_VERSION
- name: Create and push Github Container Registry chromium manifest
run: |
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
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-chromium \
--amend ghcr.io/documenso/documenso-amd64:latest-chromium \
--amend ghcr.io/documenso/documenso-arm64:latest-chromium
docker manifest push ghcr.io/documenso/documenso:latest-chromium
fi
if [[ "$APP_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
docker manifest create \
ghcr.io/documenso/documenso:rc-chromium \
--amend ghcr.io/documenso/documenso-amd64:rc-chromium \
--amend ghcr.io/documenso/documenso-arm64:rc-chromium
docker manifest push ghcr.io/documenso/documenso:rc-chromium
fi
docker manifest create \
ghcr.io/documenso/documenso:$GIT_SHA-chromium \
--amend ghcr.io/documenso/documenso-amd64:$GIT_SHA-chromium \
--amend ghcr.io/documenso/documenso-arm64:$GIT_SHA-chromium
docker manifest create \
ghcr.io/documenso/documenso:$APP_VERSION-chromium \
--amend ghcr.io/documenso/documenso-amd64:$APP_VERSION-chromium \
--amend ghcr.io/documenso/documenso-arm64:$APP_VERSION-chromium
docker manifest push ghcr.io/documenso/documenso:$GIT_SHA-chromium
docker manifest push ghcr.io/documenso/documenso:$APP_VERSION-chromium
+4 -45
View File
@@ -17,64 +17,23 @@ jobs:
environment: Translations
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_PAT }}
- 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 }}
- name: Check and commit any files created
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
git diff --staged --quiet --exit-code || (git commit -m "chore: extract translations" && git push)
- name: Compile translations
id: compile_translations
-13
View File
@@ -50,16 +50,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*
+2
View File
@@ -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:
+3
View File
@@ -1 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run commitlint -- $1
+8 -1
View File
@@ -1,9 +1,16 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
SCRIPT_DIR="$(readlink -f "$(dirname "$0")")"
MONOREPO_ROOT="$(readlink -f "$SCRIPT_DIR/../")"
echo "Copying pdf.js"
npm run copy:pdfjs --workspace apps/**
echo "Copying .well-known/ contents"
node "$MONOREPO_ROOT/scripts/copy-wellknown.cjs"
git add "$MONOREPO_ROOT/apps/remix/public/"
git add "$MONOREPO_ROOT/apps/web/public/"
git add "$MONOREPO_ROOT/apps/marketing/public/"
npx lint-staged
+1 -2
View File
@@ -1,2 +1 @@
legacy-peer-deps = true
prefer-dedupe = true
auto-install-peers = true
-80
View File
@@ -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.
-112
View File
@@ -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.
-76
View File
@@ -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.
-75
View File
@@ -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.
-201
View File
@@ -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.
-100
View File
@@ -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.
-57
View File
@@ -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.
-56
View File
@@ -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.
-56
View File
@@ -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 -2
View File
@@ -17,6 +17,5 @@
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prisma.pinToPrisma6": true
}
}
-59
View File
@@ -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
View File
@@ -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
-50
View File
@@ -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.
+23 -14
View File
@@ -49,6 +49,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.
@@ -67,9 +69,9 @@ Contact us if you are interested in our Enterprise plan for large organizations
<a href="https://cal.com/timurercan/enterprise-customers?utm_source=banner&utm_campaign=oss"><img alt="Book us with Cal.com" src="https://cal.com/book-with-cal-dark.svg" /></a>
## 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://nextjs.org/"><img src="https://img.shields.io/badge/next.js-000000?style=flat-square&logo=nextdotjs&logoColor=white" alt="NextJS"></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>
@@ -79,17 +81,20 @@ Contact us if you are interested in our Enterprise plan for large organizations
<a href=""><img src="" alt=""></a>
</p>
- [Typescript](https://www.typescriptlang.org/) - Language
- [ReactRouter](https://reactrouter.com/) - Framework
- [Prisma](https://www.prisma.io/) - ORM
- [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)
- [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 +104,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 (v18 or above)
- Postgres SQL Database
- Docker (optional)
@@ -162,8 +167,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 +178,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
---
@@ -216,6 +221,8 @@ For detailed instructions on how to configure and run the Docker container, plea
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 +239,35 @@ cp .env.example .env
The following environment variables must be set:
- `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
cd apps/web
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 +282,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
@@ -308,7 +317,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 -16
View File
@@ -10,26 +10,15 @@ 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.
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/remix/resources/certificate.p12` (If the path does not exist, it needs to be created)
5. Place the certificate `/apps/web/resources/certificate.p12` (If the path does not exist, it needs to be created)
## Docker
-5
View File
@@ -34,8 +34,3 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
# next-sitemap output
/public/sitemap.xml
/public/robots.txt
/public/sitemap-*.xml
+36 -1
View File
@@ -1 +1,36 @@
# @documenso/documentation
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3002](http://localhost:3002) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
@@ -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
};
+17
View File
@@ -0,0 +1,17 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: [
'@documenso/assets',
'@documenso/lib',
'@documenso/tailwind-config',
'@documenso/trpc',
'@documenso/ui',
],
};
const withNextra = require('nextra')({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.tsx',
});
module.exports = withNextra(nextConfig);
-21
View File
@@ -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);
+9 -9
View File
@@ -7,7 +7,8 @@
"build": "next build",
"start": "next start -p 3002",
"lint:fix": "next lint --fix",
"clean": "rimraf .next && rimraf node_modules"
"clean": "rimraf .next && rimraf node_modules",
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs"
},
"dependencies": {
"@documenso/assets": "*",
@@ -15,18 +16,17 @@
"@documenso/tailwind-config": "*",
"@documenso/trpc": "*",
"@documenso/ui": "*",
"next": "15.5.9",
"next-plausible": "^3.12.5",
"nextra": "^3",
"nextra-theme-docs": "^3",
"next": "14.2.6",
"next-plausible": "^3.12.0",
"nextra": "^2.13.4",
"nextra-theme-docs": "^2.13.4",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "18.3.27",
"@types/react": "^18",
"@types/react-dom": "^18",
"pagefind": "^1.2.0",
"typescript": "5.6.2"
"typescript": "^5"
}
}
}
+10
View File
@@ -0,0 +1,10 @@
import { PlausibleProvider } from '../providers/plausible.tsx';
import '../styles.css';
export default function App({ Component, pageProps }) {
return (
<PlausibleProvider>
<Component {...pageProps} />;
</PlausibleProvider>
);
}
-18
View File
@@ -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>
);
}
-34
View File
@@ -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,
},
},
},
};
+34
View File
@@ -0,0 +1,34 @@
{
"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,18 +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: 'Embedding',
webhooks: 'Webhooks',
};
@@ -0,0 +1,17 @@
{
"index": "Introduction",
"-- Development & Deployment": {
"type": "separator",
"title": "Development & Deployment"
},
"local-development": "Local Development",
"self-hosting": "Self Hosting",
"contributing": "Contributing",
"-- API & Integration Guides": {
"type": "separator",
"title": "API & Integration Guides"
},
"public-api": "Public API",
"embedding": "Embedding",
"webhooks": "Webhooks"
}
@@ -1,4 +0,0 @@
export default {
index: 'Getting Started',
'contributing-translations': 'Contributing Translations',
};
@@ -0,0 +1,4 @@
{
"index": "Getting Started",
"contributing-translations": "Contributing Translations"
}
@@ -20,12 +20,16 @@ If you are looking for development notes on translations, you can find them [her
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).
The translation files are organized into folders represented by their respective language codes (`en` for English, `de` for German, etc). Each language folder contains three PO files:
1. `web.po`: Translations for the web application
2. `marketing.po`: Translations for the marketing application
3. `common.po`: Shared translations between web and marketing
Each PO file contains translations which look like this:
```po
#: apps/remix/app/(signing)/sign/[token]/no-longer-available.tsx:61
#: apps/web/src/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>"
```
@@ -42,10 +46,11 @@ msgstr "Möchten Sie auffällige Signatur-Links wie diesen senden? <0>Überprüf
### 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.
2. Navigate to the appropriate language folder.
3. Open the PO file you want to update (web.po, marketing.po, or common.po).
4. Make your changes, ensuring you follow the PO file format.
5. Commit your changes with a message such as `chore: update German translations`
6. Create a Pull Request.
### Adding a New Language
@@ -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
```
![Field Coordinates Legacy Editor](/developer-mode/field-coordinates-legacy-editor.webp)
```bash
# New editor
https://app.documenso.com/t/<team-url>/documents/<envelope-id>/edit?step=addFields&devmode=true
```
![Field Coordinates New Editor](/developer-mode/field-coordinates-new-editor.webp)
@@ -1,11 +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',
authoring: 'Authoring',
};
@@ -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,383 +0,0 @@
---
title: Authoring
description: Learn how to use embedded authoring to create documents and templates in your application
---
# 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.
## 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,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)
@@ -5,17 +5,13 @@ description: Learn how to use embedding to bring signing to your own website or
# Embedding
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.
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, or using generalized web components, this guide will help you get started with embedding Documenso.
## 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
In the future, we will roll out a **Platform Plan** that will offer additional enhancements for embedding, including the option to remove Documenso branding for a more customized experience.
## How Embedding Works
@@ -26,61 +22,17 @@ Embedding with Documenso allows you to handle document signing in two main ways:
_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) |
| 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) |
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.
@@ -128,7 +80,7 @@ This will show a dialog which will ask you to configure which recipient should b
## 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.
To embed the signing process for an ordinary document, youll 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
@@ -165,30 +117,15 @@ Once you've obtained the appropriate tokens, you can integrate the signing exper
- [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
## Stay Tuned for the Platform Plan
In addition to embedding signing experiences, Documenso now supports **embedded authoring**, allowing your users to create documents and templates directly within your application.
While embedding is already a powerful tool, we're working on a **Platform Plan** that will introduce even more functionality. This plan will offer:
With embedded authoring, you can:
- Additional customization options
- The ability to remove Documenso branding
- Additional controls for the signing experience
- 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)
More details will be shared as we approach the release.
@@ -44,9 +44,6 @@ const MyEmbeddingComponent = () => {
| 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 |
@@ -78,30 +75,3 @@ const MyEmbeddingComponent = () => {
| 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} />
);
};
```
@@ -44,9 +44,6 @@ const MyEmbeddingComponent = () => {
| 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 |
@@ -78,34 +75,3 @@ const MyEmbeddingComponent = () => {
| 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}
/>
);
};
```
@@ -44,9 +44,6 @@ const MyEmbeddingComponent = () => {
| 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 |
@@ -78,30 +75,3 @@ const MyEmbeddingComponent = () => {
| 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} />
);
};
```
@@ -46,9 +46,6 @@ If you have a direct link template, you can simply provide the token for the tem
| 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 |
@@ -80,28 +77,3 @@ const MyEmbeddingComponent = () => {
| 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}" />
```
@@ -46,9 +46,6 @@ If you have a direct link template, you can simply provide the token for the tem
| 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 |
@@ -80,35 +77,3 @@ const MyEmbeddingComponent = () => {
| 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>
```
@@ -3,16 +3,16 @@ 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';
import { Card, 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" />
<Card title="Set up dev environment" href="/developers/local-development" />
<Card title="Use the API" href="/developers/public-api" />
<Card title="Integrate webhooks" href="/developers/webhooks" />
<Card title="Contribute to the project" href="/developers/contributing" />
<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',
};
@@ -0,0 +1,8 @@
{
"index": "Get Started",
"quickstart": "Developer Quickstart",
"manual": "Manual Setup",
"gitpod": "Gitpod",
"signing-certificate": "Signing Certificate",
"translations": "Translations"
}
@@ -16,16 +16,18 @@ Pick the one that fits your needs the best.
## Tech Stack
- [Typescript](https://www.typescriptlang.org/) - Language
- [React Router](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
- [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
<div className="mt-16 flex items-center justify-center gap-4">
<a href="https://documen.so/discord">
@@ -32,8 +32,10 @@ Run `npm i` in the root directory to install the dependencies required for the p
Set up the following environment variables in the `.env` file:
```bash
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
@@ -61,6 +61,6 @@ 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.
- Port: 54320
- Connection: Use your favourite database client to connect to the database.
- S3 Storage Dashboard - http://localhost:9001
@@ -13,13 +13,35 @@ Documenso uses the following stack to handle translations:
Additional reading can be found in the [Lingui documentation](https://lingui.dev/introduction).
## Requirements
You **must** insert **`setupI18nSSR()`** when creating any of the following files:
- Server layout.tsx
- Server page.tsx
- Server loading.tsx
Server meaning it does not have `'use client'` in it.
```tsx
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
export default function SomePage() {
setupI18nSSR(); // Required if there are translations within the page, or nested in components.
// Rest of code...
}
```
Additional information can be found [here.](https://lingui.dev/tutorials/react-rsc#pages-layouts-and-lingui)
## 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**.
Wrap all text to translate in **`<Trans></Trans>`** tags exported from **@lingui/macro** (not @lingui/react).
```html
<h1>
@@ -42,9 +64,8 @@ For text that is broken into elements, but represent a whole sentence, you must
### Constants outside of react components
```tsx
import { msg } from '@lingui/core/macro';
import { Trans, msg } from '@lingui/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 = {
@@ -77,13 +98,31 @@ Lingui provides a Plural component to make it easy. See full documentation [here
Lingui provides a [DateTime instance](https://lingui.dev/ref/core#i18n.date) with the configured locale.
#### Server components
Note that the i18n instance is coming from **setupI18nSSR**.
```tsx
import { Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react';
export const SomeComponent = () => {
const { i18n } = useLingui();
const { i18n } = setupI18nSSR();
return <Trans>The current date is {i18n.date(new Date(), { dateStyle: 'short' })}</Trans>;
};
```
#### Client components
Note that the i18n instance is coming from the **import**.
```tsx
import { i18n } from '@lingui/core';
import { Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react';
export const SomeComponent = () => {
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',
};
@@ -0,0 +1,5 @@
{
"index": "Get Started",
"authentication": "Authentication",
"versioning": "Versioning"
}
@@ -9,11 +9,11 @@ Documenso uses API keys for authentication. An API key is a unique token that is
## 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.
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 "**[User settings](https://app.documenso.com/settings)**" from the dropdown menu.
![A screenshot of the Documenso's dashboard that shows the dropdown menu when you click on your user avatar](/public-api-images/documenso-user-dropdown-menu.webp)
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.
Once you're on the user settings page, navigate to the "**[API Tokens](https://app.documenso.com/settings/tokens)**" tab. The "API Token" page lists your existing keys and enables you to create new ones.
![A screenshot of the Documenso's user settings page that shows the API Tokens page](/public-api-images/api-tokens-page-documenso.webp)
@@ -37,17 +37,31 @@ You must include the API key in the `Authorization` request header to authentica
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" }'
curl --location 'https://app.documenso.com/api/v1/documents?page=1&perPage=1' \
--header 'Authorization: api_xxxxxxxxxxxxxxxx'
```
Here's a sample response from the API based on the above cURL request:
```json
{
"id": "envelope_xxxxxxxxxxxxx"
"documents": [
{
"id": 11,
"userId": 2,
"teamId": null,
"title": "documenso",
"status": "PENDING",
"documentDataId": "ab2ecm1npk11rt5sp398waf7h",
"createdAt": "2024-04-25T11:05:18.420Z",
"updatedAt": "2024-04-25T11:05:36.328Z",
"completedAt": null
}
],
"totalPages": 1
}
```
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.
![A screenshot of a cURL request to the Documenso public API with the API key in the Authorization header](/public-api-images/documenso-api-authorization.webp)
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 "API Tokens" page in your user settings.
@@ -3,38 +3,20 @@ 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
- 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
## Swagger Documentation
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
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.
📖 [Documentation](https://documen.so/api-v2-docs)
The [Swagger documentation](https://app.documenso.com/api/v1/openapi) also provides information 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.
The API is available to individual users and teams.
@@ -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 |
@@ -7,359 +7,501 @@ 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.
The Swagger UI for the API is available at [/api/v1/openapi](https://app.documenso.com/api/v1/openapi). This page provides detailed information about the API endpoints, request and response formats, and authentication requirements.
<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>
## Upload a Document
## Get Envelope
Uploading a document to your Documenso account requires a two-step process.
For the vast majority of use cases, you will need to retrieve the envelope to get details for further operations.
<Steps>
The main details you generally want to extract are the following:
### Create Document
- **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:
First, you need to make a `POST` request to the `/api/v1/documents` endpoint, which takes a JSON payload with the following fields:
```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",
"title": "string",
"externalId": "string",
"recipients": [
{
"envelopeId": "envelope_xxxxxxxxxxxxxxxx",
"name": "string",
"email": "user@example.com",
"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
"signingOrder": 0
}
],
// Other attributes
"meta": {
"subject": "string",
"message": "string",
"timezone": "Etc/UTC",
"dateFormat": "yyyy-MM-dd hh:mm a",
"redirectUrl": "string",
"signingOrder": "PARALLEL"
},
"authOptions": {
"globalAccessAuth": "ACCOUNT",
"globalActionAuth": "ACCOUNT"
},
"formValues": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
}
}
```
## 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"
```
- `title` _(required)_ - This represents the document's title.
- `externalId` - This is an optional field that you can use to store an external identifier for the document. This can be useful for tracking the document in your system.
- `recipients` _(required)_ - This is an array of recipient objects. Each recipient object has the following fields:
- `name` - The name of the recipient.
- `email` - The email address of the recipient.
- `role` - The role of the recipient. See the [available roles](/users/signing-documents#roles).
- `signingOrder` - The order in which the recipient should sign the document. This is an integer value starting from 0.
- `meta` - This object contains additional metadata for the document. It has the following fields:
- `subject` - The subject of the email that will be sent to the recipients.
- `message` - The message of the email that will be sent to the recipients.
- `timezone` - The timezone in which the document should be signed.
- `dateFormat` - The date format that should be used in the document.
- `redirectUrl` - The URL to which the user should be redirected after signing the document.
- `signingOrder` - The signing order for the document. This can be either `SEQUENTIAL` or `PARALLEL`.
- `authOptions` - This object contains authentication options for the document. It has the following fields:
- `globalAccessAuth` - The authentication level required to access the document. This can be either `ACCOUNT` or `null`.
- If the document is set to `ACCOUNT`, all recipients must authenticate with their Documenso account to access it.
- The document can be accessed without a Documenso account if it's set to `null`.
- `globalActionAuth` - The authentication level required to perform actions on the document. This can be `ACCOUNT`, `PASSKEY`, `TWO_FACTOR_AUTH`, or `null`.
- If the document is set to `ACCOUNT`, all recipients must authenticate with their Documenso account to perform actions on the document.
- If it's set to `PASSKEY`, all recipients must have the passkey active to perform actions on the document.
- If it's set to `TWO_FACTOR_AUTH`, all recipients must have the two-factor authentication active to perform actions on the document.
- If it's set to `null`, all the recipients can perform actions on the document without any authentication.
- `formValues` - This object contains additional form values for the document. This property only works with native PDF fields and accepts three types: number, text and boolean.
<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)
The `globalActionAuth` property is only available for Enterprise accounts.
</Callout>
## Use Template
Here's an example of the JSON payload for uploading a document:
```json
{
"title": "my-document.pdf",
"externalId": "12345",
"recipients": [
{
"name": "Alex Blake",
"email": "alexblake@email.com",
"role": "SIGNER",
"signingOrder": 1
},
{
"name": "Ash Drew",
"email": "ashdrew@email.com",
"role": "SIGNER",
"signingOrder": 0
}
],
"meta": {
"subject": "Sign the document",
"message": "Hey there, please sign this document.",
"timezone": "Europe/London",
"dateFormat": "Day, Month Year",
"redirectUrl": "https://mysite.com/welcome",
"signingOrder": "SEQUENTIAL"
},
"authOptions": {
"globalAccessAuth": "ACCOUNT",
"globalActionAuth": "PASSKEY"
}
}
```
### Upload to S3
A successful API call to the `/api/v1/documents` endpoint returns a JSON response containing the upload URL, document ID, and recipient information.
The upload URL is a pre-signed S3 URL that you can use to upload the document to the Documenso (or your) S3 bucket. You need to make a `PUT` request to this URL to upload the document.
```json
{
"uploadUrl": "https://<url>/<bucket-name>/<id>/my-document.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=<credentials>&X-Amz-Date=<date>&X-Amz-Expires=3600&X-Amz-Signature=<signature>&X-Amz-SignedHeaders=host&x-id=PutObject",
"documentId": 51,
"recipients": [
{
"recipientId": 11,
"name": "Alex Blake",
"email": "alexblake@email.com",
"token": "<unique-signer-token>",
"role": "SIGNER",
"signingOrder": 1,
"signingUrl": "https://app.documenso.com/sign/<unique-signer-token>"
},
{
"recipientId": 12,
"name": "Ash Drew",
"email": "ashdrew@email.com",
"token": "<unique-signer-token>",
"role": "SIGNER",
"signingOrder": 0,
"signingUrl": "https://app.documenso.com/sign/<unique-signer-token>"
}
]
}
```
When you make the `PUT` request to the pre-signed URL, you need to include the document file you want to upload. The image below shows how to upload a document to the S3 bucket via Postman.
![Upload document to S3](/api-reference/upload-document-to-s3.webp)
Here's an example of how to upload a document using cURL:
```bash
curl --location --request PUT 'https://<url>/<bucket-name>/<id>/my-document.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=<credentials>&X-Amz-Date=<date>&X-Amz-Expires=3600&X-Amz-Signature=<signature>&X-Amz-SignedHeaders=host&x-id=PutObject' \
--form '=@"/Users/my-user/Documents/documenso.pdf"'
```
Once the document is successfully uploaded, you can access it in your Documenso account dashboard. The screenshot below shows the document that was uploaded via the API.
![Uploaded Document](/api-reference/document-uploaded-to-documenso-via-api.webp)
</Steps>
## Generate Document From 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:
The API endpoint for generating a document from a template is `/api/v1/templates/{templateId}/generate-document`, 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>
```json
{
"title": "string",
"externalId": "string",
"recipients": [
{
"id": 0,
"name": "string",
"email": "user@example.com",
"signingOrder": 0
}
],
"meta": {
"subject": "string",
"message": "string",
"timezone": "string",
"dateFormat": "string",
"redirectUrl": "string",
"signingOrder": "PARALLEL"
},
"authOptions": {
"globalAccessAuth": "ACCOUNT",
"globalActionAuth": "ACCOUNT"
},
"formValues": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
}
}
```
The JSON payload is identical to the payload for uploading a document, so you can read more about the fields in the [Create Document](/developers/public-api/reference#create-document) step. For this API endpoint, the `recipients` property is required.
<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.
The first step is to retrieve the template ID from the Documenso dashboard. You can find the template ID in the URL by navigating to the template details page.
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"
![Template ID](/api-reference/documenso-template-id.webp)
In this case, the template ID is "99999".
### 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.
Once you have the template ID, the next step involves retrieving the ID(s) of the recipient(s) from the template. You can do this by making a GET request to `/api/v1/templates/{template-id}`.
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.
A successful response looks as follows:
```json
{
"prefillFields": [
"id": 0,
"externalId": "string",
"type": "PUBLIC",
"title": "string",
"userId": 0,
"teamId": 0,
"templateDocumentDataId": "string",
"createdAt": "2024-10-11T08:46:58.247Z",
"updatedAt": "2024-10-11T08:46:58.247Z",
"templateMeta": {
"id": "string",
"subject": "string",
"message": "string",
"timezone": "string",
"dateFormat": "string",
"templateId": 0,
"redirectUrl": "string",
"signingOrder": "PARALLEL"
},
"directLink": {
"token": "string",
"enabled": true
},
"templateDocumentData": {
"id": "string",
"type": "S3_PATH",
"data": "string"
},
"Field": [
{
"id": 21,
"type": "text",
"label": "my-label",
"placeholder": "my-placeholder",
"value": "my-value"
},
"id": 0,
"recipientId": 0,
"type": "SIGNATURE",
"page": 0,
"positionX": "string",
"positionY": "string",
"width": "string",
"height": "string"
}
],
"Recipient": [
{
"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"]
"id": 0,
"email": "user@example.com",
"name": "string",
"signingOrder": 0,
"authOptions": "string",
"role": "CC"
}
]
// Other payload properties...
}
```
You'll need the recipient(s) ID(s) for the next step.
### Generate the Document
To generate a document from the template, you need to make a POST request to the `/api/v1/templates/{template-id}/generate-document` endpoint.
At the minimum, you must provide the `recipients` array in the JSON payload. Here's an example of the JSON payload:
```json
{
"recipients": [
{
"id": 0,
"name": "Ash Drew",
"email": "ashdrew@email.com",
"signingOrder": 0
}
]
}
```
Filling the `recipients` array with the corresponding recipient for each template placeholder recipient is recommended. For example, if the template has two placeholders, you should provide at least two recipients in the `recipients` array. Otherwise, the document will be sent to inexistent recipients such as `<recipient.1@documenso.com>`. However, the recipients can always be edited via the API or the web app.
A successful response will contain the document ID and recipient(s) information.
```json
{
"documentId": 999,
"recipients": [
{
"recipientId": 0,
"name": "Ash Drew",
"email": "ashdrew@email.com",
"token": "<signing-token>",
"role": "SIGNER",
"signingOrder": null,
"signingUrl": "https://app.documenso.com/sign/<signing-token>"
}
]
}
```
You can now access the document in your Documenso account dashboard. The screenshot below shows the document that was generated from the template.
![Generated Document](/api-reference/document-generated-from-template.webp)
</Steps>
## Add Recipients
## Add Fields to Document
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 API allows you to add fields to a document via the `/api/v1/documents/{documentId}/fields` endpoint. This is useful when you want to add fields to a document before sending it to recipients.
The following is an example of a request which creates 2 new recipients on the envelope.
To add fields to a document, you need to make a `POST` request with a JSON payload containing the field(s) information.
```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"
}
]
}'
```json
{
"recipientId": 0,
"type": "SIGNATURE",
"pageNumber": 0,
"pageX": 0,
"pageY": 0,
"pageWidth": 0,
"pageHeight": 0,
"fieldMeta": {
"label": "string",
"placeholder": "string",
"required": true,
"readOnly": true,
"type": "text",
"text": "string",
"characterLimit": 0
}
}
// or
[
{
"recipientId": 0,
"type": "SIGNATURE",
"pageNumber": 0,
"pageX": 0,
"pageY": 0,
"pageWidth": 0,
"pageHeight": 0
},
{
"recipientId": 0,
"type": "TEXT",
"pageNumber": 0,
"pageX": 0,
"pageY": 0,
"pageWidth": 0,
"pageHeight": 0,
"fieldMeta": {
"label": "string",
"placeholder": "string",
"required": true,
"readOnly": true,
"type": "text",
"text": "string",
"characterLimit": 0
}
}
]
```
## Add Fields
<Callout type="info">This endpoint accepts either one field or an array of fields.</Callout>
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 a document, you need each recipient's ID. If the document already has recipients, you can query the document to retrieve the recipient's details. If the document has no recipients, you need to add a recipient via the UI or API before adding a field.
Before adding fields to an envelope, you will need the following details:
<Steps>
- **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.
### Retrieve the Recipient(s) ID(s)
See the [Get Envelope](#get-envelope) section for more details on how to retrieve these details.
Perform a `GET` request to the `/api/v1/documents/{id}` to retrieve the details of a specific document, including the recipient's information.
The following is an example of a request which creates 2 new fields on the first page of the envelope.
An example response would look like this:
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"
}
}
]
}'
```json
{
"id": 137,
"externalId": null,
"userId": 3,
"teamId": null,
"title": "documenso.pdf",
"status": "DRAFT",
"documentDataId": "<document-data-id>",
"createdAt": "2024-10-11T12:29:12.725Z",
"updatedAt": "2024-10-11T12:29:12.725Z",
"completedAt": null,
"recipients": [
{
"id": 55,
"documentId": 137,
"email": "ashdrew@email.com",
"name": "Ash Drew",
"role": "SIGNER",
"signingOrder": null,
"token": "<signing-token>",
"signedAt": null,
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "NOT_SENT",
"signingUrl": "https://app.documenso.com/sign/<signing-token>"
}
]
}
```
Field meta allows you to further configure fields, for example it will allow you to add multiple items for checkboxes or radios.
From this response, you'll only need the recipient ID, which is `55` in this case.
A successful request will return a JSON response with the newly added fields.
### (OR) Add a Recipient
If the document doesn't already have recipient(s), you can add recipient(s) via the API. Make a `POST` request to the `/api/v1/documents/{documentId}/recipients` endpoint with the recipient information. This endpoint takes the following JSON payload:
```json
{
"name": "string",
"email": "user@example.com",
"role": "SIGNER",
"signingOrder": 0,
"authOptions": {
"actionAuth": "ACCOUNT"
}
}
```
<Callout type="info">The `authOptions` property is only available for Enterprise accounts.</Callout>
Here's an example of the JSON payload for adding a recipient:
```json
{
"name": "Ash Drew",
"email": "ashdrew@email.com",
"role": "SIGNER",
"signingOrder": 0
}
```
A successful request will return a JSON response with the newly added recipient. You can now use the recipient ID to add fields to the document.
### Add Field(s)
Now you can make a `POST` request to the `/api/v1/documents/{documentId}/fields` endpoint with the field(s) information. Here's an example:
```json
[
{
"recipientId": 55,
"type": "SIGNATURE",
"pageNumber": 1,
"pageX": 50,
"pageY": 20,
"pageWidth": 25,
"pageHeight": 5
},
{
"recipientId": 55,
"type": "TEXT",
"pageNumber": 1,
"pageX": 20,
"pageY": 50,
"pageWidth": 30,
"pageHeight": 7.5,
"fieldMeta": {
"label": "Address",
"placeholder": "32 New York Street, 41241",
"required": true,
"readOnly": false,
"type": "text",
"text": "32 New York Street, 41241",
"characterLimit": 40
}
}
]
```
<Callout type="info">
The `text` field represents the default value of the field. If the user doesn't provide any other
value, this is the value that will be used to sign the field.
</Callout>
A successful request will return a JSON response with the newly added fields. The image below illustrates the fields added to the document via the API.
![A screenshot of the document in the Documenso editor](/api-reference/fields-added-via-api.webp)
</Steps>
@@ -9,9 +9,9 @@ import { Callout } from 'nextra/components';
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>
<Callout type="info">The current version of the API is `v1`.</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`.
The API version is specified in the URL. For example, the base URL for the `v1` API is `https://app.documenso.com/api/v1`.
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.
@@ -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',
};
@@ -0,0 +1,6 @@
{
"index": "Getting Started",
"signing-certificate": "Signing Certificate",
"how-to": "How To",
"setting-up-oauth-providers": "Setting up OAuth Providers"
}
@@ -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.
@@ -5,7 +5,7 @@ description: Learn how to self-host Documenso on your server or cloud infrastruc
import { Callout, Steps } from 'nextra/components';
import { CallToAction } from '../../../components/call-to-action';
import { CallToAction } from '@documenso/ui/components/call-to-action';
# Self Hosting
@@ -35,8 +35,10 @@ cp .env.example .env
Open the `.env` file and fill in the following variables:
```bash
- 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
@@ -44,8 +46,8 @@ Open the `.env` file and fill in the following variables:
```
<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!
If you use a reverse proxy in front of Documenso, don't forget to provide the public URL for both
the `NEXTAUTH_URL` and `NEXT_PUBLIC_WEBAPP_URL` variables!
</Callout>
### Install the Dependencies
@@ -54,7 +56,7 @@ Install the project dependencies as follows:
```bash
npm i
npm run build
npm run build:web
npm run prisma:migrate-deploy
```
@@ -69,7 +71,7 @@ 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.
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.
</Callout>
</Steps>
@@ -119,92 +121,16 @@ 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.
### Update the Volume Binding
### Set Up Your Signing Certificate
The `cert.p12` file is required to sign and encrypt documents, so you must provide your key file. Update the volume binding in the `compose.yml` file to point to your key file:
<Callout type="warning">
This is the most common source of issues for self-hosters. Please follow these steps carefully.
</Callout>
```yaml
volumes:
- /path/to/your/keyfile.p12:/opt/documenso/cert.p12
```
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:
After updating the volume binding, save the `compose.yml` file and run the following command to start the containers:
```bash
docker-compose --env-file ./.env up -d
@@ -245,6 +171,7 @@ Run the Docker container with the required environment variables:
```bash
docker run -d \
-p 3000:3000 \
-e NEXTAUTH_URL="<your-nextauth-url>"
-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>"
@@ -270,95 +197,48 @@ You can access the Documenso application by visiting the URL you provided for th
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.
| Variable | Description |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `PORT` | The port on which the Documenso application runs. It defaults to `3000`. |
| `NEXTAUTH_URL` | The URL for the NextAuth.js authentication service. |
| `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_PUBLIC_WEBAPP_URL` | The URL for the web application. |
| `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) |
| `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_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_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. |
## Run as a Service
@@ -373,7 +253,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
@@ -3,7 +3,7 @@ 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';
import { CallToAction } from '@documenso/ui/components/call-to-action';
# Getting Started with Self-Hosting
@@ -27,33 +27,3 @@ NEXT_PRIVATE_GOOGLE_CLIENT_SECRET=<your-client-secret>
```
Finally verify the signing in with Google works by signing in with your Google account and checking the email address in your profile.
## Microsoft OAuth (Azure AD)
To use Microsoft OAuth, you will need to create an Azure AD application registration in the Microsoft Azure portal. This will allow users to sign in with their Microsoft accounts.
### Create and configure a new Azure AD application
1. Go to the [Azure Portal](https://portal.azure.com/)
2. Navigate to **Azure Active Directory** (or **Microsoft Entra ID** in newer Azure portals)
3. In the left sidebar, click **App registrations**
4. Click **New registration**
5. Enter a name for your application (e.g., "Documenso")
6. Under **Supported account types**, select **Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)** to allow any Microsoft account to sign in
7. Under **Redirect URI**, select **Web** and enter: `https://<documenso-domain>/api/auth/callback/microsoft`
8. Click **Register**
### Configure the application
1. After registration, you'll be taken to the app's overview page
2. Copy the **Application (client) ID** - this will be your `NEXT_PRIVATE_MICROSOFT_CLIENT_ID`
3. In the left sidebar, click **Certificates & secrets**
4. Under **Client secrets**, click **New client secret**
5. Add a description and select an expiration period
6. Click **Add** and copy the **Value** (not the Secret ID) - this will be your `NEXT_PRIVATE_MICROSOFT_CLIENT_SECRET`
7. In the Documenso environment variables, set the following:
```
NEXT_PRIVATE_MICROSOFT_CLIENT_ID=<your-application-client-id>
NEXT_PRIVATE_MICROSOFT_CLIENT_SECRET=<your-client-secret-value>
```
@@ -53,21 +53,15 @@ Have the Certificate Authority sign the Certificate Signing Request.
Configure your instance to use the new certificate by configuring the following environment variables in your `.env` file:
| Environment Variable | Description |
| :------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
| `NEXT_PRIVATE_SIGNING_TRANSPORT` | The transport used for document signing. Available options: local (default), gcloud-hsm |
| `NEXT_PRIVATE_SIGNING_PASSPHRASE` | The passphrase for the local file-based signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH` | The local file path to the .p12 file to use for the local signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS` | The base64-encoded contents of the .p12 file to use for the local signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH` | The Google Cloud HSM key path for the gcloud-hsm signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH` | The path to the Google Cloud HSM public certificate file to use for the gcloud-hsm signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS` | The base64-encoded contents of the Google Cloud HSM public certificate file for the gcloud-hsm signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS` | The base64-encoded Google Cloud Credentials for the gcloud-hsm signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_FILE_PATH` | The path to the certificate chain file for the gcloud-hsm signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_CONTENTS` | The base64-encoded contents of the certificate chain for the gcloud-hsm signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_GCLOUD_HSM_SECRET_MANAGER_CERT_PATH` | The Google Secret Manager path to retrieve the certificate for the gcloud-hsm signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY` | Comma-separated list of timestamp authority URLs for PDF signing. Enables LTV and archival timestamps. This field is optional. |
| `NEXT_PUBLIC_SIGNING_CONTACT_INFO` | Contact info to embed in PDF signatures. Defaults to the webapp URL. This field is optional. |
| `NEXT_PRIVATE_USE_LEGACY_SIGNING_SUBFILTER` | Set to "true" to use the legacy adbe.pkcs7.detached subfilter instead of ETSI.CAdES.detached. This field is optional. |
| Environment Variable | Description |
| :-------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
| `NEXT_PRIVATE_SIGNING_TRANSPORT` | The transport used for document signing. Available options: local (default), gcloud-hsm |
| `NEXT_PRIVATE_SIGNING_PASSPHRASE` | The passphrase for the local file-based signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH` | The local file path to the .p12 file to use for the local signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS` | The base64-encoded contents of the .p12 file to use for the local signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH` | The Google Cloud HSM key path for the gcloud-hsm signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_GCLOUD_HSM _PUBLIC_CRT_FILE_PATH` | The path to the Google Cloud HSM public certificate file to use for the gcloud-hsm signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_GCLOUD_HSM _PUBLIC_CRT_FILE_CONTENTS` | The base64-encoded contents of the Google Cloud HSM public certificate file for the gcloud-hsm signing transport. This field is optional. |
| `NEXT_PRIVATE_SIGNING_GCLOUD_ APPLICATION_CREDENTIALS_CONTENTS` | The Google Cloud Credentials file path for the gcloud-hsm signing transport. This field is optional. |
</Steps>
@@ -1,90 +0,0 @@
---
title: Telemetry
description: Learn about the telemetry data that Documenso collects from self-hosted instances.
---
# Telemetry
Documenso collects anonymous telemetry data from self-hosted instances to help us understand how the software is being used and make improvements to the product. This telemetry is enabled by default, but you can easily disable it if you prefer.
## What We Collect
We collect minimal, privacy-preserving information that helps us understand the health and adoption of self-hosted installations:
- **App Version**: The version of Documenso you are running. This helps us understand which versions are in use and prioritize support for older versions.
- **Installation ID**: A unique identifier for your installation. This is stored in your database and helps us count distinct installations without knowing who you are.
- **Node ID**: A unique identifier for each server or container instance. This is stored in your operating system's temporary directory and helps us understand deployment patterns (for example, how many instances are running in a cluster).
### What We Don't Collect
We do **not** collect any of the following:
- Personal information about you or your users
- Document contents or file names
- User email addresses or names
- Usage patterns or feature usage statistics
- Server logs or error messages
- Any data that could identify your organization or users
## Why We Collect Telemetry
The telemetry data we collect serves several important purposes:
1. **Product Improvement**: Understanding which versions are in use helps us prioritize bug fixes and security updates for the versions that matter most.
2. **Support Planning**: Knowing how many installations exist and their deployment patterns helps us plan support resources and documentation.
3. **Feature Development**: Understanding deployment patterns (like cluster sizes) helps us make better architectural decisions for future features.
4. **Community Health**: Tracking adoption helps us understand the growth of the self-hosted community and allocate resources accordingly.
All of this is done anonymously and in aggregate. We cannot identify you, your organization, or your users from the telemetry data we collect.
## Events We Track
We track two simple events:
- **Server Startup**: Captured once when your server starts. This tells us when installations are first set up or restarted.
- **Server Heartbeat**: Captured every hour while your server is running. This helps us understand how many active installations exist and their uptime patterns.
## How to Disable Telemetry
If you prefer not to send telemetry data, you can disable it by setting an environment variable.
### Using Environment Variables
Add the following to your environment configuration:
```bash
DOCUMENSO_DISABLE_TELEMETRY=true
```
### Docker
If you're using Docker, you can set this in your `docker-compose.yml`:
```yaml
services:
app:
environment:
- DOCUMENSO_DISABLE_TELEMETRY=true
```
Or pass it when running a container:
```bash
docker run -e DOCUMENSO_DISABLE_TELEMETRY=true ...
```
### After Disabling
Once you set `DOCUMENSO_DISABLE_TELEMETRY=true` and restart your server, no telemetry data will be sent. The telemetry client will not initialize, and no network requests will be made to our telemetry servers.
Note: If you previously had telemetry enabled, the installation ID stored in your database will remain, but it will no longer be used or sent anywhere.
## Questions or Concerns
If you have questions about our telemetry practices or concerns about privacy, please reach out to us. We're committed to transparency and respect your choice to disable telemetry if you prefer.
+73 -382
View File
@@ -5,7 +5,7 @@ description: Learn how to use webhooks to receive real-time notifications about
# Webhooks
Webhooks are HTTP callbacks triggered by specific events. When you subscribe to a specific event and that event occurs, the webhook makes an HTTP request to the URL you provide. The request can be a simple notification or carry a payload with more information about the event.
Webhooks are HTTP callbacks triggered by specific events. When the user subscribes to a specific event, and that event occurs, the webhook makes an HTTP request to the URL provided by the user. The request can be a simple notification or carry a payload with more information about the event.
Some of the common use cases for webhooks include:
@@ -20,28 +20,26 @@ Documenso supports Webhooks and allows you to subscribe to the following events:
- `document.opened`
- `document.signed`
- `document.completed`
- `document.rejected`
- `document.cancelled`
## Create a webhook subscription
You can create a webhook subscription from the team settings page. Click your avatar in the top right corner of the dashboard and select "Team settings" from the dropdown menu.
You can create a webhook subscription from the user settings page. Click on your avatar in the top right corner of the dashboard and select "**[User settings](https://app.documenso.com/settings)**" from the dropdown menu.
![A screenshot of the Documenso's dashboard that shows the dropdown menu when you click on your user avatar](/webhook-images/documenso-main-page.webp)
![A screenshot of the Documenso's dashboard that shows the dropdown menu when you click on your user avatar](/webhook-images/dashboard-user-dropdown-menu.webp)
Then, navigate to the "Webhooks" tab, which takes you to the webhooks main page.
Then, navigate to the "**[Webhooks](https://app.documenso.com/settings/webhooks)**" tab, where you can see a list of your existing webhooks and create new ones.
![A screenshot of the Documenso's team settings page that shows the Webhooks tab and the Create Webhook button](/webhook-images/webhooks-main-page.webp)
![A screenshot of the Documenso's user settings page that shows the Webhooks tab and the Create Webhook button](/webhook-images/webhooks-settings-page.webp)
Clicking on the "**Create Webhook**" button opens a modal to create a new webhook subscription.
To create a new webhook subscription, you need to provide the following information:
- Enter the webhook URL that will receive the event payload.
- Select the event(s) you want to subscribe to: `document.created`, `document.sent`, `document.opened`, `document.signed`, `document.completed`, `document.rejected`, `document.cancelled`.
- Select the event(s) you want to subscribe to: `document.created`, `document.sent`, `document.opened`, `document.signed`, `document.completed`.
- Optionally, you can provide a secret key that will be used to sign the payload. This key will be included in the `X-Documenso-Secret` header of the request.
![A screenshot of the Create Webhook modal that shows the URL input field and the event checkboxes](/webhook-images/create-webhook-dialog.webp)
![A screenshot of the Create Webhook modal that shows the URL input field and the event checkboxes](/webhook-images/webhooks-page-create-webhook-modal.webp)
After you have filled in the required information, click on the "**Create Webhook**" button to save your subscription.
@@ -49,76 +47,51 @@ The screenshot below illustrates a newly created webhook subscription.
![A screenshot of the Documenso's user settings page that shows the newly created webhook subscription](/webhook-images/webhooks-page.webp)
You can edit, view the logs, or delete your webhook subscriptions by clicking the three dots (...) under the "Action" column. You can also access the webhook logs by clicking on the webhook subscription directly.
![A screenshot of the Documenso's team settings page that shows the webhook logs](/webhook-images/webhook-detail-page.webp)
You can go even further and check the execution details of each call by clicking on a specific webhook call.
![A screenshot of the Documenso's team settings page that shows the webhook call details](/webhook-images/webhook-run-page.webp)
This page shows the details of the webhook call such as:
- status
- event
- date when the webhook was sent
- response code
- request body
- response body and headers
You can edit or delete your webhook subscriptions by clicking the "**Edit**" or "**Delete**" buttons next to the webhook.
## Webhook fields
The payload sent to the webhook URL contains the following fields:
| Field | Type | Description |
| -------------------------------------------- | --------- | ----------------------------------------------------- |
| `event` | string | The type of event that triggered the webhook. |
| `payload.id` | number | The id of the document. |
| `payload.externalId` | string? | External identifier for the document. |
| `payload.userId` | number | The id of the user who owns the document. |
| `payload.authOptions` | json? | Authentication options for the document. |
| `payload.formValues` | json? | Form values for the document. |
| `payload.visibility` | string | Document visibility (e.g., EVERYONE). |
| `payload.title` | string | The title of the document. |
| `payload.status` | string | The current status of the document. |
| `payload.documentDataId` | string | The identifier for the document data. |
| `payload.createdAt` | datetime | The creation date and time of the document. |
| `payload.updatedAt` | datetime | The last update date and time of the document. |
| `payload.completedAt` | datetime? | The completion date and time of the document. |
| `payload.deletedAt` | datetime? | The deletion date and time of the document. |
| `payload.teamId` | number? | The id of the team if document belongs to a team. |
| `payload.templateId` | number? | The id of the template if created from template. |
| `payload.source` | string | The source of the document (e.g., DOCUMENT, TEMPLATE) |
| `payload.documentMeta.id` | string | The id of the document metadata. |
| `payload.documentMeta.subject` | string? | The subject of the document. |
| `payload.documentMeta.message` | string? | The message associated with the document. |
| `payload.documentMeta.timezone` | string | The timezone setting for the document. |
| `payload.documentMeta.password` | string? | The password protection if set. |
| `payload.documentMeta.dateFormat` | string | The date format used in the document. |
| `payload.documentMeta.redirectUrl` | string? | The URL to redirect after signing. |
| `payload.documentMeta.signingOrder` | string | The signing order (e.g., PARALLEL, SEQUENTIAL). |
| `payload.documentMeta.typedSignatureEnabled` | boolean | Whether typed signatures are enabled. |
| `payload.documentMeta.language` | string | The language of the document. |
| `payload.documentMeta.distributionMethod` | string | The method of distributing the document. |
| `payload.documentMeta.emailSettings` | json? | Email notification settings. |
| `payload.Recipient[].id` | number | The id of the recipient. |
| `payload.Recipient[].documentId` | number? | The id of the document for this recipient. |
| `payload.Recipient[].templateId` | number? | The template id if from a template. |
| `payload.Recipient[].email` | string | The email address of the recipient. |
| `payload.Recipient[].name` | string | The name of the recipient. |
| `payload.Recipient[].token` | string | The unique token for this recipient. |
| `payload.Recipient[].documentDeletedAt` | datetime? | When the document was deleted for this recipient. |
| `payload.Recipient[].expired` | datetime? | When the recipient's access expired. |
| `payload.Recipient[].signedAt` | datetime? | When the recipient signed the document. |
| `payload.Recipient[].authOptions` | json? | Authentication options for this recipient. |
| `payload.Recipient[].signingOrder` | number? | The order in which this recipient should sign. |
| `payload.Recipient[].rejectionReason` | string? | The reason if the recipient rejected the document. |
| `payload.Recipient[].role` | string | The role of the recipient (e.g., SIGNER, VIEWER). |
| `payload.Recipient[].readStatus` | string | Whether the recipient has read the document. |
| `payload.Recipient[].signingStatus` | string | The signing status of this recipient. |
| `payload.Recipient[].sendStatus` | string | The sending status for this recipient. |
| `createdAt` | datetime | The creation date and time of the webhook event. |
| `webhookEndpoint` | string | The endpoint URL where the webhook is sent. |
| Field | Type | Description |
| -------------------------------------------- | --------- | ---------------------------------------------------- |
| `event` | string | The type of event that triggered the webhook. |
| `payload.id` | number | The id of the document. |
| `payload.userId` | number | The id of the user who owns the document. |
| `payload.authOptions` | json? | Authentication options for the document. |
| `payload.formValues` | json? | Form values for the document. |
| `payload.title` | string | The name of the document. |
| `payload.status` | string | The current status of the document. |
| `payload.documentDataId` | string | The identifier for the document data. |
| `payload.createdAt` | datetime | The creation date and time of the document. |
| `payload.updatedAt` | datetime | The last update date and time of the document. |
| `payload.completedAt` | datetime? | The completion date and time of the document. |
| `payload.deletedAt` | datetime? | The deletion date and time of the document. |
| `payload.teamId` | number? | The id of the team. |
| `payload.documentData.id` | string | The id of the document data. |
| `payload.documentData.type` | string | The type of the document data. |
| `payload.documentData.data` | string | The data of the document. |
| `payload.documentData.initialData` | string | The initial data of the document. |
| `payload.Recipient[].id` | number | The id of the recipient. |
| `payload.Recipient[].documentId` | number? | The id the document associated with the recipient. |
| `payload.Recipient[].templateId` | number? | The template identifier for the recipient. |
| `payload.Recipient[].email` | string | The email address of the recipient. |
| `payload.Recipient[].name` | string | The name of the recipient. |
| `payload.Recipient[].token` | string | The token associated with the recipient. |
| `payload.Recipient[].expired` | datetime? | The expiration status of the recipient. |
| `payload.Recipient[].signedAt` | datetime? | The date and time the recipient signed the document. |
| `payload.Recipient[].authOptions.accessAuth` | json? | Access authentication options. |
| `payload.Recipient[].authOptions.actionAuth` | json? | Action authentication options. |
| `payload.Recipient[].role` | string | The role of the recipient. |
| `payload.Recipient[].readStatus` | string | The read status of the document by the recipient. |
| `payload.Recipient[].signingStatus` | string | The signing status of the recipient. |
| `payload.Recipient[].sendStatus` | string | The send status of the document to the recipient. |
| `createdAt` | datetime | The creation date and time of the webhook event. |
| `webhookEndpoint` | string | The endpoint URL where the webhook is sent. |
## Webhook event payload example
When an event that you have subscribed to occurs, Documenso will send a POST request to the specified webhook URL with a payload containing information about the event.
## Example payloads
@@ -131,11 +104,9 @@ Example payload for the `document.created` event:
"event": "DOCUMENT_CREATED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "DRAFT",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
@@ -143,43 +114,7 @@ Example payload for the `document.created` event:
"updatedAt": "2024-04-22T11:44:43.341Z",
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 52,
"documentId": 10,
"templateId": null,
"email": "signer@documenso.com",
"name": "John Doe",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": null,
"signingOrder": 1,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "NOT_SENT"
}
]
"teamId": null
},
"createdAt": "2024-04-22T11:44:44.779Z",
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
@@ -193,11 +128,9 @@ Example payload for the `document.sent` event:
"event": "DOCUMENT_SENT",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "PENDING",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
@@ -206,22 +139,6 @@ Example payload for the `document.sent` event:
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 52,
@@ -230,12 +147,12 @@ Example payload for the `document.sent` event:
"email": "signer2@documenso.com",
"name": "Signer 2",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": null,
"signingOrder": 1,
"rejectionReason": null,
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "VIEWER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
@@ -248,12 +165,12 @@ Example payload for the `document.sent` event:
"email": "signer1@documenso.com",
"name": "Signer 1",
"token": "HkrptwS42ZBXdRKj1TyUo",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": null,
"signingOrder": 2,
"rejectionReason": null,
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "SIGNER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
@@ -273,11 +190,9 @@ Example payload for the `document.opened` event:
"event": "DOCUMENT_OPENED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "PENDING",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
@@ -286,22 +201,6 @@ Example payload for the `document.opened` event:
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 52,
@@ -310,18 +209,24 @@ Example payload for the `document.opened` event:
"email": "signer2@documenso.com",
"name": "Signer 2",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": null,
"signingOrder": 1,
"rejectionReason": null,
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "VIEWER",
"readStatus": "OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "SENT"
}
]
],
"documentData": {
"id": "hs8qz1ktr9204jn7mg6c5dxy0",
"type": "S3_PATH",
"data": "9753/xzqrshtlpokm/documenso.pdf",
"initialData": "9753/xzqrshtlpokm/documenso.pdf"
}
},
"createdAt": "2024-04-22T11:50:26.174Z",
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
@@ -335,11 +240,9 @@ Example payload for the `document.signed` event:
"event": "DOCUMENT_SIGNED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "COMPLETED",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
@@ -348,22 +251,6 @@ Example payload for the `document.signed` event:
"completedAt": "2024-04-22T11:52:05.707Z",
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 51,
@@ -372,15 +259,12 @@ Example payload for the `document.signed` event:
"email": "signer1@documenso.com",
"name": "Signer 1",
"token": "HkrptwS42ZBXdRKj1TyUo",
"documentDeletedAt": null,
"expired": null,
"signedAt": "2024-04-22T11:52:05.688Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"signingOrder": 1,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
@@ -400,11 +284,9 @@ Example payload for the `document.completed` event:
"event": "DOCUMENT_COMPLETED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "COMPLETED",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
@@ -413,21 +295,11 @@ Example payload for the `document.completed` event:
"completedAt": "2024-04-22T11:52:05.707Z",
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
"documentData": {
"id": "hs8qz1ktr9204jn7mg6c5dxy0",
"type": "S3_PATH",
"data": "bk9p1h7x0s3m/documenso-signed.pdf",
"initialData": "9753/xzqrshtlpokm/documenso.pdf"
},
"Recipient": [
{
@@ -437,15 +309,12 @@ Example payload for the `document.completed` event:
"email": "signer2@documenso.com",
"name": "Signer 2",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": "2024-04-22T11:51:10.055Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"signingOrder": 1,
"rejectionReason": null,
"role": "VIEWER",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
@@ -458,15 +327,12 @@ Example payload for the `document.completed` event:
"email": "signer1@documenso.com",
"name": "Signer 1",
"token": "HkrptwS42ZBXdRKj1TyUo",
"documentDeletedAt": null,
"expired": null,
"signedAt": "2024-04-22T11:52:05.688Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"signingOrder": 2,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
@@ -479,181 +345,6 @@ Example payload for the `document.completed` event:
}
```
Example payload for the `document.rejected` event:
```json
{
"event": "DOCUMENT_REJECTED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "PENDING",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:48:07.569Z",
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 52,
"documentId": 10,
"templateId": null,
"email": "signer@documenso.com",
"name": "Signer",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": "2024-04-22T11:48:07.569Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"signingOrder": 1,
"rejectionReason": "I do not agree with the terms",
"role": "SIGNER",
"readStatus": "OPENED",
"signingStatus": "REJECTED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2024-04-22T11:48:07.945Z",
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
}
```
Example payload for the `document.rejected` event:
```json
{
"event": "DOCUMENT_CANCELLED",
"payload": {
"id": 7,
"externalId": null,
"userId": 3,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "PENDING",
"documentDataId": "cm6exvn93006hi02ru90a265a",
"createdAt": "2025-01-27T11:02:14.393Z",
"updatedAt": "2025-01-27T11:03:16.387Z",
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "cm6exvn96006ji02rqvzjvwoy",
"subject": "",
"message": "",
"timezone": "Etc/UTC",
"password": null,
"dateFormat": "yyyy-MM-dd hh:mm a",
"redirectUrl": "",
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": {
"documentDeleted": true,
"documentPending": true,
"recipientSigned": true,
"recipientRemoved": true,
"documentCompleted": true,
"ownerDocumentCompleted": true,
"recipientSigningRequest": true
}
},
"recipients": [
{
"id": 7,
"documentId": 7,
"templateId": null,
"email": "mybirihix@mailinator.com",
"name": "Zorita Baird",
"token": "XkKx1HCs6Znm2UBJA2j6o",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": { "accessAuth": null, "actionAuth": null },
"signingOrder": 1,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "SENT"
}
],
"Recipient": [
{
"id": 7,
"documentId": 7,
"templateId": null,
"email": "signer@documenso.com",
"name": "Signer",
"token": "XkKx1HCs6Znm2UBJA2j6o",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": { "accessAuth": null, "actionAuth": null },
"signingOrder": 1,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2025-01-27T11:03:27.730Z",
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
}
```
## Webhook events testing
You can trigger test webhook events to test the webhook functionality. To do so, navigate to the webhook subscription details page and click the "Test" button.
![A screenshot of the Documenso's team settings page that shows the webhook logs](/webhook-images/webhook-detail-page.webp)
This opens a dialog where you can select the event type to test.
![Documenso's individual webhook page](/webhook-images/webhook-test-trigger.webp)
Choose the event you want to test and click "Send". Youll then receive a test payload from Documenso with sample data.
## Webhook events resending
To resend a webhook call, you need to navigate to the webhook call page and click the "Resend" button.
![A screenshot of the Documenso's team settings page that shows the webhook call details](/webhook-images/webhook-run-page.webp)
This will send the webhook event to the webhook URL again.
## Availability
Webhooks are available to teams only.
Webhooks are available to individual users and teams.
-23
View File
@@ -1,23 +0,0 @@
export default {
index: 'Introduction',
support: 'Support',
'-- How To Use': {
type: 'separator',
title: 'How To Use',
},
'get-started': 'Get Started',
profile: 'Public Profile',
organisations: 'Organisations',
documents: 'Documents',
templates: 'Templates',
branding: 'Branding',
'email-domains': 'Email Domains',
'direct-links': 'Direct Signing Links',
'-- Legal Overview': {
type: 'separator',
title: 'Legal Overview',
},
'fair-use': 'Fair Use Policy',
licenses: 'Licenses',
compliance: 'Compliance',
};
+22
View File
@@ -0,0 +1,22 @@
{
"index": "Introduction",
"support": "Support",
"-- How To Use": {
"type": "separator",
"title": "How To Use"
},
"get-started": "Get Started",
"profile": "User Profile",
"signing-documents": "Signing Documents",
"templates": "Templates",
"direct-links": "Direct Signing Links",
"document-visibility": "Document Visibility",
"teams": "Teams",
"-- Legal Overview": {
"type": "separator",
"title": "Legal Overview"
},
"fair-use": "Fair Use Policy",
"licenses": "Licenses",
"compliance": "Compliance"
}
@@ -1,28 +0,0 @@
---
title: Branding Preferences
description: Learn how to set the branding preferences for your team account.
---
import Image from 'next/image';
import { Callout, Steps } from 'nextra/components';
# Branding Preferences
Branding preferences allow you to set the default settings when emailing documents to your recipients.
## Preferences
Branding preferences can be set on either the organisation or team level.
By default, teams inherit the preferences from the organisation. You can override these preferences on the team level at any time.
To access the preferences, navigate to either the organisation or teams settings page and click the **Branding** tab under the **Preferences** section.
![A screenshot of the organisation's document preferences page](/organisations/organisation-branding.webp)
On this page, you can:
- **Upload a Logo** - Upload your team's logo to be displayed instead of the default Documenso logo.
- **Set the Brand Website** - Enter the URL of your team's website to be displayed in the email communications sent by the team.
- **Add Additional Brand Details** - You can add additional information to display at the bottom of the emails sent by the team. This can include contact information, social media links, and other relevant details.
@@ -1,4 +0,0 @@
export default {
'signature-levels': 'Signature Levels',
'standards-and-regulations': 'Standards and Regulations',
};
@@ -0,0 +1,4 @@
{
"signature-levels": "Signature Levels",
"standards-and-regulations": "Standards and Regulations"
}
@@ -1,8 +1,3 @@
---
title: Signature Levels
description: Learn about the different signature levels for Documenso.
---
import { Callout } from 'nextra/components';
# Signature Levels
@@ -31,20 +26,20 @@ ensures the legal validity and enforceability of electronic signatures and recor
### Main Requirements
- [x] **Intent to Sign**: "Parties must demonstrate their intent to sign [..]"
- [x] **Consent**: "The ESIGN Act requires that all parties involved in a transaction consent to the use of electronic signatures and records [..]"
- [x] **Consumer Disclosures**: Before obtaining their consent, financial institutions must provide the consumer a clear and conspicuous statement informing the consumer [..]
- [x] **Record Retention**: Electronic Records must be maintained for later access by signers.
- [x] **Security**: The ESIGN Act does not mandate specific security measures, but it does require that parties take reasonable steps to ensure the security and integrity of electronic signatures and records. This may include implementing encryption, access controls, and authentication measures.
- [x] Intent to Sign: "Parties must demonstrate their intent to sign [..]"
- [x] Consent: "The ESIGN Act requires that all parties involved in a transaction consent to the use of electronic signatures and records [..]"
- [x] Consumer Disclosures: Before obtaining their consent, financial institutions must provide the consumer a clear and conspicuous statement informing the consumer [..]
- [x] Record Retention: Electronic Records must be maintained for later access by signers.
- [x] Security: The ESIGN Act does not mandate specific security measures, but it does require that parties take reasonable steps to ensure the security and integrity of electronic signatures and records. This may include implementing encryption, access controls, and authentication measures.
## UETA (Uniform Electronic Transactions Act)
<Callout type="info" emoji="✅">
Status: Compliant
</Callout>
The Uniform Electronic Transactions Act is a law that provides a legal framework for the use of
electronic signatures and records in electronic transactions, ensuring they have the same validity
and enforceability as paper documents and handwritten signatures.
The Uniform Electronic Transactions Act is a law that provides a legal framework for the use of electronic
signatures and records in electronic transactions, ensuring they have the same validity and enforceability
as paper documents and handwritten signatures.
### Main Requirements
@@ -55,9 +50,9 @@ _See [ESIGN](/users/compliance/signature-levels#-esign-electronic-signatures-in-
<Callout type="info" emoji="✅">
Status: Compliant for Level 1 - SES (Simple Electronic Signatures)
</Callout>
eIDAS (Electronic Identification, Authentication and Trust Services) is an EU regulation that
standardizes electronic identification and trust services for secure and seamless electronic
transactions across European member states.
eIDAS (Electronic Identification, Authentication and Trust Services) is an EU regulation that standardizes
electronic identification and trust services for secure and seamless electronic transactions across European
member states.
### Level 1 - SES (Simple Electronic Signatures)
@@ -74,8 +69,8 @@ eIDAS SES (Simple Electronic Signature) is a basic electronic signature with min
Status: [Planned](https://github.com/documenso/backlog/issues/9) via third party until [Let's
Sign](https://github.com/documenso/backlog/issues/21) is realized.
</Callout>
eIDAS AES (Advanced Electronic Signature) provides a higher level of security with unique
identification of the signer and data integrity.
eIDAS AES (Advanced Electronic Signature) provides a higher level of security with unique identification
of the signer and data integrity.
### Main Requirements
@@ -90,8 +85,8 @@ identification of the signer and data integrity.
Status: [Planned](https://github.com/documenso/backlog/issues/32) via third party until [Let's
Sign](https://github.com/documenso/backlog/issues/21) is realized.
</Callout>
eIDAS QES (Qualified Electronic Signature) is the highest security level, legally equivalent to a
handwritten signature within the EU.
eIDAS QES (Qualified Electronic Signature) is the highest security level, legally equivalent to a handwritten
signature within the EU.
### Main Requirements
@@ -1,8 +1,3 @@
---
title: Standards and Regulations
description: Learn about the different standards and regulations for Documenso.
---
import { Callout } from 'nextra/components';
## 21 CFR Part 11
@@ -24,13 +19,13 @@ device, and other FDA-regulated industries.
- [x] User Access Management
- [x] Quality Assurance Documentation
## SOC 2
## SOC/ SOC II
<Callout type="info" emoji="">
Status: [Compliant](https://documen.so/trust)
<Callout type="warning" emoji="">
Status: [Planned](https://github.com/documenso/backlog/issues/24)
</Callout>
SOC 2 is a framework for managing and auditing the security, availability, processing integrity, confidentiality,
SOC II is a framework for managing and auditing the security, availability, processing integrity, confidentiality,
and data privacy in cloud and IT service organizations, established by the American Institute of Certified
Public Accountants (AICPA).
@@ -39,9 +34,9 @@ Public Accountants (AICPA).
<Callout type="warning" emoji="⏳">
Status: [Planned](https://github.com/documenso/backlog/issues/26)
</Callout>
ISO 27001 is an international standard for managing information security, specifying requirements
for establishing, implementing, maintaining, and continually improving an information security
management system (ISMS).
ISO 27001 is an international standard for managing information security, specifying requirements for
establishing, implementing, maintaining, and continually improving an information security management
system (ISMS).
### HIPAA
@@ -1,9 +0,0 @@
export default {
'sending-documents': 'Sending Documents',
'document-preferences': 'Document Preferences',
'document-visibility': 'Document Visibility',
fields: 'Document Fields',
'email-preferences': 'Email Preferences',
'ai-detection': 'AI Recipient & Field Detection',
'default-recipients': 'Default Recipients',
};

Some files were not shown because too many files have changed in this diff Show More