mirror of
https://github.com/documenso/documenso.git
synced 2026-06-23 04:42:09 +10:00
Compare commits
577 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d08049ed3b | |||
| 7a583aa7af | |||
| b590076d85 | |||
| 65e30b88be | |||
| 9c6ee88cc4 | |||
| 6028ad9158 | |||
| 7fc6f5bb6e | |||
| 17b261df1f | |||
| c732c85082 | |||
| 7d38e18f93 | |||
| 0a3e0b8727 | |||
| b538580a1e | |||
| 42d6e1cbbd | |||
| 67da488f63 | |||
| fd3ebc08ec | |||
| a7963b385a | |||
| 9035240b4d | |||
| ed7a0011c7 | |||
| 158b36a9b7 | |||
| fabd69bd62 | |||
| c976e747e3 | |||
| 34f512bd55 | |||
| db913e95b6 | |||
| bb3e9583e4 | |||
| 5bc73a7471 | |||
| 06d7849146 | |||
| cef7987a72 | |||
| cf6f6bcea0 | |||
| 2f27304750 | |||
| 912530ca17 | |||
| a995961c4e | |||
| 6b041c23b4 | |||
| 7b6e948aa2 | |||
| f6d81b22bd | |||
| c861dd2ee2 | |||
| 7eabae4b4b | |||
| ae4272a6b6 | |||
| fd672943d1 | |||
| c2ea5e5859 | |||
| c1217c5a58 | |||
| 27eb2d65d4 | |||
| ef407cb0b4 | |||
| 1e20561e91 | |||
| a2ec5f0fa1 | |||
| de8d13a4c1 | |||
| 495d61a11d | |||
| 90fdba8000 | |||
| aa1cada79b | |||
| 790b385849 | |||
| baa2c51123 | |||
| 1e585e06e6 | |||
| 5624484631 | |||
| 810e00da03 | |||
| eeeee2fa0e | |||
| c50a31a503 | |||
| 7360709795 | |||
| df678d7d69 | |||
| 6739242554 | |||
| a5e5eecf8b | |||
| b0248c20eb | |||
| f129968968 | |||
| c5c87e3fd1 | |||
| 24a74c7b57 | |||
| f0a5a7e816 | |||
| 8462cd13fd | |||
| 576846de32 | |||
| 06071ea035 | |||
| b45a2691ba | |||
| f31cc575d0 | |||
| 05d7015ef0 | |||
| 2ca5d6cfaa | |||
| 04814ca14e | |||
| dd1dccdb6a | |||
| df4316ac5c | |||
| 02f1264eea | |||
| 928edb8645 | |||
| 54b0e4964e | |||
| 68e6ccdd19 | |||
| 09ab7e9a09 | |||
| 3bb0777914 | |||
| 4d6389e901 | |||
| 51e3d5030d | |||
| 0cebdec637 | |||
| 43486d8448 | |||
| 4d3d1b8d14 | |||
| 0387f3c20a | |||
| c5032d0c43 | |||
| 3bd34964cd | |||
| fe93b11a2c | |||
| 7638faf27b | |||
| 8fca029d96 | |||
| bac2bf11f4 | |||
| d93b2a70a7 | |||
| 5da915da38 | |||
| dcaecf1fc5 | |||
| f70b76d8b8 | |||
| 93137c6396 | |||
| d058b7c705 | |||
| b51f562224 | |||
| f80aa4bf72 | |||
| 9238f759a6 | |||
| 74ad6af47d | |||
| 18902ed59d | |||
| 3f70082146 | |||
| 31ba6d5f00 | |||
| c4f89a87a2 | |||
| 89d6dd5b0e | |||
| 08a9ab3aaf | |||
| e66bd422e3 | |||
| 0f5814ff89 | |||
| 1275a15571 | |||
| 22d99c7410 | |||
| 26a36487d4 | |||
| 2ee6b90c99 | |||
| f70e6ac50a | |||
| 7a94ee3b83 | |||
| e39924714a | |||
| c9604fee64 | |||
| 90f8340af4 | |||
| 28b8d2d415 | |||
| 978a2047d4 | |||
| 0dfa953f54 | |||
| 4774324e07 | |||
| bc19699a58 | |||
| 55480826de | |||
| 327b0eaf86 | |||
| 2de5c1992f | |||
| df0c03816e | |||
| a610a06372 | |||
| d5e085d7ee | |||
| c322356654 | |||
| b16862b480 | |||
| 7065b0dd88 | |||
| dff9cfec05 | |||
| d84cf0e58d | |||
| 5d8b147199 | |||
| 7d28295d42 | |||
| 94646cd48a | |||
| 14db9b8203 | |||
| 6ae672c16b | |||
| e9a9d65937 | |||
| d857dfdb38 | |||
| 11a56f3228 | |||
| 91642ddf0b | |||
| e364b08b6a | |||
| 5df3932958 | |||
| ae31860b16 | |||
| 16ee6b7a6d | |||
| 921c3d1ff3 | |||
| 2d7a4d0dde | |||
| d2176627ca | |||
| 17c6098638 | |||
| e5bde53ee4 | |||
| 0663605ffd | |||
| 1bbe561162 | |||
| fbc156722a | |||
| f5d63fb76c | |||
| 374477e692 | |||
| 11d9bde8f8 | |||
| fa1680aaf1 | |||
| 798b6bd750 | |||
| 8fbace0f61 | |||
| 1bbd04be9b | |||
| 6aa56fe7e0 | |||
| a07f3090cf | |||
| de3e6d2115 | |||
| dabd2564cd | |||
| 5811716d12 | |||
| 56526f9448 | |||
| 6ec1c3a3fb | |||
| 47466a2db9 | |||
| e4e04cdddc | |||
| 74a03077b7 | |||
| 58bff33275 | |||
| 0cb23be27a | |||
| 4304fc1d35 | |||
| ce53bcea8c | |||
| 5a3d5b8b4a | |||
| 3d1fe85d62 | |||
| 1772c3ee36 | |||
| f55b902a01 | |||
| 50db4e39be | |||
| 2802813c76 | |||
| 29d40f1cca | |||
| d67f32eae2 | |||
| a33233443b | |||
| 68a3608aee | |||
| 378dd605b9 | |||
| 211ae6c9e9 | |||
| f931885a95 | |||
| 4ade408001 | |||
| 3d0e3c6e8e | |||
| 936d8d90b3 | |||
| c6b08d8594 | |||
| 575634e326 | |||
| c66eda4aae | |||
| ef52b35f79 | |||
| 95a647034a | |||
| 34dba0b6ff | |||
| fccd97e124 | |||
| 3dbbcefddf | |||
| 2aea3c4de0 | |||
| ff44ffbc03 | |||
| 441842d2bd | |||
| ca0b83579f | |||
| 6c0d1da91e | |||
| 805982f3e8 | |||
| e2f5e570cf | |||
| 9fd9613076 | |||
| 0977c16e33 | |||
| 88d5a636c3 | |||
| 1e6292b1d9 | |||
| d65866156d | |||
| fe8915162f | |||
| 37a2634aca | |||
| eff7d90f43 | |||
| db5524f8ce | |||
| 3d539b20ad | |||
| 48626b9169 | |||
| 88371b665a | |||
| 1650c55b19 | |||
| 60d73e0921 | |||
| 4a779ec81e | |||
| 7f19ec1265 | |||
| d6a2f5a4c9 | |||
| d05bfa9fed | |||
| d2a009d52e | |||
| 9350c53c7d | |||
| ffce7a2c81 | |||
| 353bdce86b | |||
| e13b9f7c84 | |||
| 9908580bf1 | |||
| b0b07106b4 | |||
| 35250fa308 | |||
| 5cdd7f8623 | |||
| 47bdcd833f | |||
| 03eb6af69a | |||
| 88836404d1 | |||
| 2eebc0e439 | |||
| 4a3859ec60 | |||
| 49b792503f | |||
| c3dc76b1b4 | |||
| daab8461c7 | |||
| 1ffc4bd703 | |||
| f15c0778b5 | |||
| 06cb8b1f23 | |||
| 7f09ba72f4 | |||
| 7b17156e56 | |||
| 86e89e137e | |||
| 26f65dbdd7 | |||
| a902bec96d | |||
| 399f91de73 | |||
| 995bc9c362 | |||
| 3467317271 | |||
| a5eaa8ad47 | |||
| 577691214b | |||
| c7d21c6587 | |||
| 2aa391f917 | |||
| 681540b501 | |||
| f3305ac306 | |||
| 68b4305b6a | |||
| 3de1ea0a02 | |||
| b8fc47b719 | |||
| cfceebd78f | |||
| b9b3ddfb98 | |||
| 8590502338 | |||
| 53f29daf50 | |||
| 197d17ed7b | |||
| 3c646d9475 | |||
| ed4dfc9b55 | |||
| 32ce573de4 | |||
| 2ecfdbdde5 | |||
| a3005f8616 | |||
| 2c0d4f8789 | |||
| 7c8e93b53e | |||
| 93a3809f6a | |||
| 4550bca3d3 | |||
| 9ac7b94d9a | |||
| 374f2c45b4 | |||
| bb5c2edefd | |||
| 19565c1821 | |||
| 2603ae8b90 | |||
| 7d257236a6 | |||
| 31c1a9a783 | |||
| 657db3bc84 | |||
| 184ebdedf1 | |||
| 4012022f55 | |||
| 44f5da95b3 | |||
| 7eb882aea8 | |||
| dbf10e5b7b | |||
| fe4d3ed1fd | |||
| b8d07fd1a6 | |||
| 49fabeb0ec | |||
| 5a5bfe6e34 | |||
| d7e5a9eec7 | |||
| adefac81e2 | |||
| 67501b45cf | |||
| 17b36ac8e4 | |||
| 80e452afa2 | |||
| 1cb9de8083 | |||
| 231ef9c27e | |||
| 6f35342a83 | |||
| a51110d276 | |||
| 7f81231467 | |||
| 439262fd02 | |||
| 93a184355b | |||
| 1dea0b8fab | |||
| ea7a2c2712 | |||
| deb3a63fb8 | |||
| cc05af2062 | |||
| 9026aabe3b | |||
| b844e166a9 | |||
| 950951de75 | |||
| c37e10faab | |||
| fdf6efe94e | |||
| 4c1eb8f874 | |||
| e547b0b410 | |||
| 803edf5b16 | |||
| 86c133ae84 | |||
| c28c5ab91d | |||
| d1eb14ac16 | |||
| f24b71f559 | |||
| 2ee0d77870 | |||
| 9b01a2318f | |||
| 5689cd1538 | |||
| 9d5b573dda | |||
| c48486472a | |||
| 1e2388519c | |||
| 20198b5b6c | |||
| 7cbf527eb3 | |||
| 767b66672e | |||
| 109a49826c | |||
| 3409aae411 | |||
| 07119f0e8d | |||
| 7a5a9eefe8 | |||
| 5570690b3b | |||
| 9ea56a77ff | |||
| 32c94118ce | |||
| 512e3555b4 | |||
| c47dc8749a | |||
| 32a5d33a16 | |||
| e5aaa17545 | |||
| f9d7fd7d9a | |||
| 5083ecb4b8 | |||
| 168648164b | |||
| 202e9fedb9 | |||
| 939bbcdb33 | |||
| 70f6036525 | |||
| 122e25b491 | |||
| ca9a70ced5 | |||
| 55abecc526 | |||
| 49c70fc8a8 | |||
| 4195a871ce | |||
| 37ed5ad222 | |||
| d6c11bd195 | |||
| cb73d21e05 | |||
| 106f796fea | |||
| 9917def0ca | |||
| cdb9b9ee03 | |||
| 8d1d098e3a | |||
| b682d2785f | |||
| 1a1a30791e | |||
| ea1cf481eb | |||
| eda0d5eeb6 | |||
| 8da4ab533f | |||
| 8695ef766e | |||
| 7487399123 | |||
| 0cc729e9bd | |||
| 58d97518c8 | |||
| 20c8969272 | |||
| 85ac65e405 | |||
| e07a497b69 | |||
| 21dc4eee62 | |||
| dc2042a1ee | |||
| bb9ba80edb | |||
| bfe8c674f2 | |||
| ebe1baf0a0 | |||
| 2345de679b | |||
| 1be0e2842c | |||
| 29a03d4ec7 | |||
| 039cd7d449 | |||
| 484f6c8b85 | |||
| 4fd8a767b2 | |||
| b8e08e88ac | |||
| 031a7b9e36 | |||
| 12fe045195 | |||
| 614106a5e4 | |||
| 8be7137b59 | |||
| 31e2a6443e | |||
| 400d2a2b1a | |||
| e3ce7f94e6 | |||
| cad04f26e7 | |||
| d27f0ee0ef | |||
| fd2b413ed9 | |||
| d11ec8fa2a | |||
| b1127b4f0d | |||
| be4244fb62 | |||
| 504a0893ab | |||
| 22a37409c1 | |||
| 50605d5912 | |||
| 4609fc852d | |||
| e6dc237ad2 | |||
| 0b37f19641 | |||
| 64c6a51e04 | |||
| d1eddb02c4 | |||
| 60a623fafd | |||
| 6059b79a8e | |||
| c73d61955b | |||
| 7c3ca72359 | |||
| 55c8632620 | |||
| ce66da0055 | |||
| 695ed418e2 | |||
| 93aece9644 | |||
| abd4fddf31 | |||
| 44bc769e60 | |||
| c8f80f7be0 | |||
| 8540f24de0 | |||
| 67203d4bd7 | |||
| 9d1e638f0f | |||
| bd64ad9fef | |||
| 99b0ad574e | |||
| 9594e1fee8 | |||
| 5e3a2b8f76 | |||
| f928503a33 | |||
| c389670785 | |||
| 99ad2eb645 | |||
| 2f48679b0b | |||
| e40c5d9d24 | |||
| ab323f149f | |||
| bf1c1ff9dc | |||
| 516e237966 | |||
| ac7d24eb12 | |||
| 0931c472a7 | |||
| 8c9dd5e372 | |||
| e108da546d | |||
| 17370749b4 | |||
| 12ada567f5 | |||
| bdb0b0ea88 | |||
| 6a41a37bd4 | |||
| d78cfec00e | |||
| f0dcf7e9bf | |||
| 6540291055 | |||
| 193325717d | |||
| b94645a451 | |||
| 7e6704faae | |||
| cf17fc61bc | |||
| 6df8b3aac8 | |||
| fdb31772db | |||
| a3dfd81870 | |||
| 755ef697ba | |||
| 37cc41d713 | |||
| dd2ef3a657 | |||
| 435b3ca4f8 | |||
| 278cd8a9de | |||
| f1526315f5 | |||
| 353a7e8e0d | |||
| 34b2504268 | |||
| 566abda36b | |||
| 9121a062b3 | |||
| e613e0e347 | |||
| 95aae52fa4 | |||
| 5958f38719 | |||
| 419bc02171 | |||
| 5e4956f3a2 | |||
| da71613c9f | |||
| 4d6efe091e | |||
| 7e6ac4db40 | |||
| a87af910c7 | |||
| e37b005d7f | |||
| 73f8518b47 | |||
| ac3deb113e | |||
| c82388c40a | |||
| 31be548939 | |||
| 063fd32f18 | |||
| 231f51bd1f | |||
| a8de8368a2 | |||
| 7dd331addf | |||
| c6743a7cec | |||
| efbc097191 | |||
| f1525991dc | |||
| fb173e4d0e | |||
| d422ffa873 | |||
| 55b7697316 | |||
| 67bbb6c6f4 | |||
| 63a4bab0fe | |||
| 9f17c1e48e | |||
| 91ae818213 | |||
| a0ace803cf | |||
| b3db3be8e9 | |||
| 44cdbeecb4 | |||
| 7214965c0c | |||
| 8d6bf91d12 | |||
| fec078081b | |||
| c646afcd97 | |||
| 63d990ce8d | |||
| aa7d6b28a4 | |||
| b990532633 | |||
| 65be37514f | |||
| 0df29fce36 | |||
| ba5b7ce480 | |||
| 422770a8c7 | |||
| 083a706373 | |||
| db326cb4a9 | |||
| d664f571d6 | |||
| 7c38970ee8 | |||
| e08d62c844 | |||
| 25bb6ffe77 | |||
| e79d762710 | |||
| d970976299 | |||
| 3dce814ab2 | |||
| ad520bb032 | |||
| 596d30e2e5 | |||
| 6474b4a524 | |||
| 5b4db51051 | |||
| cf58c80e31 | |||
| 11dbb8873e | |||
| bc7907271b | |||
| 9b376d34cf | |||
| deea99d865 | |||
| 3328074f51 | |||
| 5e69665e00 | |||
| c1c7cfaf8b | |||
| 7e8955b89c | |||
| cedd5e87b1 | |||
| 5255e8671f | |||
| d4c1bad407 | |||
| 01dccb7916 | |||
| 483d7caef7 | |||
| 139bc265c7 | |||
| 991ce5ff46 | |||
| 7728c8641c | |||
| 50a41d0799 | |||
| 250381fec8 | |||
| d2f3d24542 | |||
| ec07092bf6 | |||
| 63e2ef0abf | |||
| 90ce52164c | |||
| ac30654913 | |||
| 24f3ecd94f | |||
| a319ea0f5e | |||
| 5ce2bae39d | |||
| 5d86e84217 | |||
| 79e26a9a46 | |||
| dd602a7e1c | |||
| fb16214dc5 | |||
| 5fc724b247 | |||
| 1ed1cb0773 | |||
| 8d5fafec27 | |||
| 0f6f236e0c | |||
| e518985833 | |||
| 595e901bc2 | |||
| df8ea09021 | |||
| 180656978b | |||
| 28f5177064 | |||
| 31de86e425 | |||
| 113ab293bb | |||
| 1c4878e526 | |||
| 92db4d68db | |||
| 7379391f92 | |||
| ebc2b00067 | |||
| 87dcdd44cd | |||
| b205f7e5f3 | |||
| c5d5355cf7 | |||
| 5fac29a07f | |||
| 1aaacab6ca | |||
| 06076c1809 | |||
| c0ae68c28b | |||
| 3e106c1a2d | |||
| 741639ee78 | |||
| 0b3638c42c | |||
| 0f50110853 | |||
| b0f8c83134 | |||
| c9e8a32471 | |||
| 84b193d99c | |||
| b03c5ab1a7 | |||
| 9db42accf3 | |||
| 383b5f78f0 |
@@ -0,0 +1,76 @@
|
||||
---
|
||||
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
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
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
|
||||
+43
-16
@@ -1,4 +1,7 @@
|
||||
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
|
||||
@@ -6,20 +9,25 @@ 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 interfaces over types
|
||||
- Avoid enums; use maps instead
|
||||
|
||||
- Use TypeScript for all code; prefer types over interfaces
|
||||
- Use functional components with TypeScript interfaces
|
||||
|
||||
Syntax and Formatting:
|
||||
- Use the "function" keyword for pure functions
|
||||
|
||||
- Create functions using `const fn = () => {}`
|
||||
- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
|
||||
- Use declarative JSX
|
||||
- Never use 'use client'
|
||||
- Never use 1 line if statements
|
||||
|
||||
Error Handling and Validation:
|
||||
|
||||
- Prioritize error handling: handle errors and edge cases early
|
||||
- Use early returns and guard clauses
|
||||
- Implement proper error logging and user-friendly messages
|
||||
@@ -28,21 +36,40 @@ 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
|
||||
|
||||
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
|
||||
React forms
|
||||
|
||||
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
|
||||
- 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
|
||||
|
||||
Follow Next.js docs for Data Fetching, Rendering, and Routing
|
||||
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
|
||||
|
||||
+52
-15
@@ -1,5 +1,4 @@
|
||||
# [[AUTH]]
|
||||
NEXTAUTH_URL="http://localhost:3000"
|
||||
NEXTAUTH_SECRET="secret"
|
||||
|
||||
# [[CRYPTO]]
|
||||
@@ -14,22 +13,30 @@ 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.
|
||||
@@ -52,6 +59,18 @@ 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
|
||||
@@ -110,16 +129,18 @@ 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]]
|
||||
@@ -129,16 +150,32 @@ 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"
|
||||
|
||||
# This is only required for the marketing site
|
||||
# [[REDIS]]
|
||||
NEXT_PRIVATE_REDIS_URL=
|
||||
NEXT_PRIVATE_REDIS_TOKEN=
|
||||
|
||||
# [[LOGGER]]
|
||||
NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY=
|
||||
# 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=
|
||||
|
||||
@@ -5,6 +5,7 @@ module.exports = {
|
||||
rules: {
|
||||
'@next/next/no-img-element': 'off',
|
||||
'no-unreachable': 'error',
|
||||
'react-hooks/exhaustive-deps': 'off',
|
||||
},
|
||||
settings: {
|
||||
next: {
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
name: Pull Request
|
||||
about: Submit changes to the project for review and inclusion
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!--- Describe the changes introduced by this pull request. -->
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
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
|
||||
**/.turbo/**
|
||||
**/dist/**
|
||||
|
||||
key: prod-build-${{ github.run_id }}-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: prod-build-
|
||||
|
||||
- run: npm run build
|
||||
shell: bash
|
||||
@@ -2,7 +2,7 @@ name: 'Setup node and cache node_modules'
|
||||
inputs:
|
||||
node_version:
|
||||
required: false
|
||||
default: v20.x
|
||||
default: v22.x
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
|
||||
@@ -26,7 +26,8 @@ jobs:
|
||||
- name: Copy env
|
||||
run: cp .env.example .env
|
||||
|
||||
- uses: ./.github/actions/cache-build
|
||||
- name: Build app
|
||||
run: npm run build
|
||||
|
||||
build_docker:
|
||||
name: Build Docker Image
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
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
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
@@ -30,7 +30,8 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/node-install
|
||||
|
||||
- uses: ./.github/actions/cache-build
|
||||
- name: Build app
|
||||
run: npm run build
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
|
||||
@@ -4,11 +4,16 @@ 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-16x
|
||||
runs-on: warp-ubuntu-2204-x64-8x
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -28,7 +33,8 @@ jobs:
|
||||
- name: Seed the database
|
||||
run: npm run prisma:seed
|
||||
|
||||
- uses: ./.github/actions/cache-build
|
||||
- name: Install playwright browsers
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: npm run ci
|
||||
@@ -41,7 +47,7 @@ jobs:
|
||||
with:
|
||||
name: test-results
|
||||
path: 'packages/app-tests/**/test-results/*'
|
||||
retention-days: 30
|
||||
retention-days: 7
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
|
||||
@@ -3,6 +3,12 @@ name: Publish Docker
|
||||
on:
|
||||
push:
|
||||
branches: ['release']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git tag to build and publish (e.g., v1.0.0)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build_and_publish_platform_containers:
|
||||
@@ -18,6 +24,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag || github.ref }}
|
||||
fetch-tags: true
|
||||
|
||||
- name: Login to DockerHub
|
||||
@@ -36,13 +43,20 @@ 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 }}
|
||||
APP_VERSION: ${{ inputs.tag || '' }}
|
||||
run: |
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
if [ -z "$APP_VERSION" ]; then
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
fi
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
|
||||
docker build \
|
||||
-f ./docker/Dockerfile \
|
||||
--progress=plain \
|
||||
--build-arg NEXT_PRIVATE_TELEMETRY_KEY="${NEXT_PRIVATE_TELEMETRY_KEY:-}" \
|
||||
--build-arg NEXT_PRIVATE_TELEMETRY_HOST="${NEXT_PRIVATE_TELEMETRY_HOST:-}" \
|
||||
-t "documenso/documenso-$BUILD_PLATFORM:latest" \
|
||||
-t "documenso/documenso-$BUILD_PLATFORM:$GIT_SHA" \
|
||||
-t "documenso/documenso-$BUILD_PLATFORM:$APP_VERSION" \
|
||||
@@ -69,6 +83,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag || github.ref }}
|
||||
fetch-tags: true
|
||||
|
||||
- name: Login to DockerHub
|
||||
@@ -85,8 +100,12 @@ jobs:
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Create and push DockerHub manifest
|
||||
env:
|
||||
APP_VERSION: ${{ inputs.tag || '' }}
|
||||
run: |
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
if [ -z "$APP_VERSION" ]; then
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
fi
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
|
||||
# Check if the version is stable (no rc or beta in the version)
|
||||
@@ -122,8 +141,12 @@ jobs:
|
||||
docker manifest push documenso/documenso:$APP_VERSION
|
||||
|
||||
- name: Create and push Github Container Registry manifest
|
||||
env:
|
||||
APP_VERSION: ${{ inputs.tag || '' }}
|
||||
run: |
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
if [ -z "$APP_VERSION" ]; then
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
fi
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
|
||||
# Check if the version is stable (no rc or beta in the version)
|
||||
|
||||
@@ -17,23 +17,64 @@ 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: Check and commit any files created
|
||||
- name: Commit changes and push to reserved branch
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
BRANCH="chore/extract-translations"
|
||||
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@documenso.com'
|
||||
|
||||
git fetch origin
|
||||
|
||||
# Create branch locally (always reset to main)
|
||||
git checkout -B "$BRANCH" origin/main
|
||||
|
||||
# Stage translation output
|
||||
git add packages/lib/translations
|
||||
git diff --staged --quiet --exit-code || (git commit -m "chore: extract translations" && git push)
|
||||
|
||||
# If no changes, exit early
|
||||
if git diff --staged --quiet; then
|
||||
echo "No translation changes found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Commit fresh snapshot
|
||||
git commit -m "chore: extract translations"
|
||||
|
||||
# Force push reserved branch
|
||||
git push origin "$BRANCH" --force
|
||||
|
||||
# Does a PR already exist?
|
||||
EXISTING_PR=$(gh pr list \
|
||||
--state open \
|
||||
--head "$BRANCH" \
|
||||
--json number \
|
||||
--jq '.[0].number // empty')
|
||||
|
||||
if [ -z "$EXISTING_PR" ]; then
|
||||
echo "No existing PR — creating new one."
|
||||
gh pr create \
|
||||
--title "chore: extract translations" \
|
||||
--body "Automated translation extraction" \
|
||||
--base main \
|
||||
--head "$BRANCH"
|
||||
else
|
||||
echo "PR #$EXISTING_PR already exists — not creating a new one."
|
||||
fi
|
||||
|
||||
- name: Compile translations
|
||||
id: compile_translations
|
||||
|
||||
+13
@@ -50,3 +50,16 @@ yarn-error.log*
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# logs
|
||||
logs.json
|
||||
|
||||
# claude
|
||||
.claude
|
||||
CLAUDE.md
|
||||
|
||||
# agents
|
||||
.specs
|
||||
|
||||
# scripts
|
||||
scripts/output*
|
||||
|
||||
@@ -4,9 +4,7 @@ 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:
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run commitlint -- $1
|
||||
|
||||
+1
-7
@@ -1,15 +1,9 @@
|
||||
#!/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/web/public/"
|
||||
git add "$MONOREPO_ROOT/apps/remix/public/"
|
||||
|
||||
npx lint-staged
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
auto-install-peers = true
|
||||
legacy-peer-deps = true
|
||||
prefer-dedupe = true
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
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.
|
||||
Vendored
+2
-1
@@ -17,5 +17,6 @@
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
},
|
||||
"prisma.pinToPrisma6": true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# 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
@@ -0,0 +1,692 @@
|
||||
# Documenso Code Style Guide
|
||||
|
||||
This document captures the code style, patterns, and conventions used in the Documenso codebase. It covers both enforceable rules and subjective "taste" elements that make our code consistent and maintainable.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [General Principles](#general-principles)
|
||||
2. [TypeScript Conventions](#typescript-conventions)
|
||||
3. [Imports & Dependencies](#imports--dependencies)
|
||||
4. [Functions & Methods](#functions--methods)
|
||||
5. [React & Components](#react--components)
|
||||
6. [Error Handling](#error-handling)
|
||||
7. [Async/Await Patterns](#asyncawait-patterns)
|
||||
8. [Whitespace & Formatting](#whitespace--formatting)
|
||||
9. [Naming Conventions](#naming-conventions)
|
||||
10. [Pattern Matching](#pattern-matching)
|
||||
11. [Database & Prisma](#database--prisma)
|
||||
12. [TRPC Patterns](#trpc-patterns)
|
||||
|
||||
---
|
||||
|
||||
## General Principles
|
||||
|
||||
- **Functional over Object-Oriented**: Prefer functional programming patterns over classes
|
||||
- **Explicit over Implicit**: Be explicit about types, return values, and error cases
|
||||
- **Early Returns**: Use guard clauses and early returns to reduce nesting
|
||||
- **Immutability**: Favor `const` over `let`; avoid mutation where possible
|
||||
|
||||
---
|
||||
|
||||
## TypeScript Conventions
|
||||
|
||||
### Type Definitions
|
||||
|
||||
```typescript
|
||||
// ✅ Prefer `type` over `interface`
|
||||
type CreateDocumentOptions = {
|
||||
templateId: number;
|
||||
userId: number;
|
||||
recipients: Recipient[];
|
||||
};
|
||||
|
||||
// ❌ Avoid interfaces unless absolutely necessary
|
||||
interface CreateDocumentOptions {
|
||||
templateId: number;
|
||||
}
|
||||
```
|
||||
|
||||
### Type Imports
|
||||
|
||||
```typescript
|
||||
// ✅ Use `type` keyword for type-only imports
|
||||
import type { Document, Recipient } from '@prisma/client';
|
||||
import { DocumentStatus } from '@prisma/client';
|
||||
|
||||
// Types in function signatures
|
||||
export const findDocuments = async ({ userId, teamId }: FindDocumentsOptions) => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
### Inline Types for Function Parameters
|
||||
|
||||
```typescript
|
||||
// ✅ Extract inline types to named types
|
||||
type FinalRecipient = Pick<Recipient, 'name' | 'email' | 'role' | 'authOptions'> & {
|
||||
templateRecipientId: number;
|
||||
fields: Field[];
|
||||
};
|
||||
|
||||
const finalRecipients: FinalRecipient[] = [];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Imports & Dependencies
|
||||
|
||||
### Import Organization
|
||||
|
||||
Imports should be organized in the following order with blank lines between groups:
|
||||
|
||||
```typescript
|
||||
// 1. React imports
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
// 2. Third-party library imports (alphabetically)
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Document, Recipient } from '@prisma/client';
|
||||
import { DocumentStatus, RecipientRole } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
// 3. Internal package imports (from @documenso/*)
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
// 4. Relative imports
|
||||
import { getTeamById } from '../team/get-team';
|
||||
import type { FindResultResponse } from './types';
|
||||
```
|
||||
|
||||
### Destructuring Imports
|
||||
|
||||
```typescript
|
||||
// ✅ Destructure specific exports
|
||||
// ✅ Use type imports for types
|
||||
import type { Document } from '@prisma/client';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Functions & Methods
|
||||
|
||||
### Arrow Functions
|
||||
|
||||
```typescript
|
||||
// ✅ Always use arrow functions for functions
|
||||
export const createDocument = async ({
|
||||
userId,
|
||||
title,
|
||||
}: CreateDocumentOptions) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
// ✅ Callbacks and handlers
|
||||
const onSubmit = useCallback(async () => {
|
||||
// ...
|
||||
}, [dependencies]);
|
||||
|
||||
// ❌ Avoid regular function declarations
|
||||
function createDocument() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Function Parameters
|
||||
|
||||
```typescript
|
||||
// ✅ Use destructured object parameters for multiple params
|
||||
export const findDocuments = async ({
|
||||
userId,
|
||||
teamId,
|
||||
status = ExtendedDocumentStatus.ALL,
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
}: FindDocumentsOptions) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
// ✅ Destructure on separate line when needed
|
||||
const onFormSubmit = form.handleSubmit(onSubmit);
|
||||
|
||||
// ✅ Deconstruct nested properties explicitly
|
||||
const { user } = ctx;
|
||||
const { templateId } = input;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## React & Components
|
||||
|
||||
### Component Definition
|
||||
|
||||
```typescript
|
||||
// ✅ Use const with arrow function
|
||||
export const AddSignersFormPartial = ({
|
||||
documentFlow,
|
||||
recipients,
|
||||
fields,
|
||||
onSubmit,
|
||||
}: AddSignersFormProps) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
// ❌ Never use classes
|
||||
class MyComponent extends React.Component {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Hooks
|
||||
|
||||
```typescript
|
||||
// ✅ Group related hooks together with blank line separation
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { currentStep, totalSteps, previousStep } = useStep();
|
||||
|
||||
const form = useForm<TFormSchema>({
|
||||
resolver: zodResolver(ZFormSchema),
|
||||
defaultValues: {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Event Handlers
|
||||
|
||||
```typescript
|
||||
// ✅ Use arrow functions with descriptive names
|
||||
const onFormSubmit = async () => {
|
||||
await form.trigger();
|
||||
// ...
|
||||
};
|
||||
|
||||
const onFieldCopy = useCallback(
|
||||
(event?: KeyboardEvent | null) => {
|
||||
event?.preventDefault();
|
||||
// ...
|
||||
},
|
||||
[dependencies],
|
||||
);
|
||||
|
||||
// ✅ Inline handlers for simple operations
|
||||
<Button onClick={() => setOpen(false)}>Close</Button>
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
```typescript
|
||||
// ✅ Descriptive state names with auxiliary verbs
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [hasError, setHasError] = useState(false);
|
||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||
|
||||
// ✅ Complex state in single useState when related
|
||||
const [coords, setCoords] = useState({
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Try-Catch Blocks
|
||||
|
||||
```typescript
|
||||
// ✅ Use try-catch for operations that might fail
|
||||
try {
|
||||
const document = await getDocumentById({
|
||||
documentId: Number(documentId),
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: document,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
status: 404,
|
||||
body: {
|
||||
message: 'Document not found',
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Throwing Errors
|
||||
|
||||
```typescript
|
||||
// ✅ Use AppError for application errors
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Template not found',
|
||||
});
|
||||
|
||||
// ✅ Use descriptive error messages
|
||||
if (!template) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: `Template with ID ${templateId} not found`,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Error Parsing on Frontend
|
||||
|
||||
```typescript
|
||||
// ✅ Parse errors on the frontend
|
||||
try {
|
||||
await updateOrganisation({ organisationId, data });
|
||||
} catch (err) {
|
||||
const error = AppError.parseError(err);
|
||||
console.error(error);
|
||||
|
||||
toast({
|
||||
title: t`An error occurred`,
|
||||
description: error.message,
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Async/Await Patterns
|
||||
|
||||
### Async Function Definitions
|
||||
|
||||
```typescript
|
||||
// ✅ Mark async functions clearly
|
||||
export const createDocument = async ({
|
||||
userId,
|
||||
title,
|
||||
}: Options): Promise<Document> => {
|
||||
// ...
|
||||
};
|
||||
|
||||
// ✅ Use await for promises
|
||||
const document = await prisma.document.create({ data });
|
||||
|
||||
// ✅ Use Promise.all for parallel operations
|
||||
const [document, recipients] = await Promise.all([
|
||||
getDocumentById({ documentId }),
|
||||
getRecipientsForDocument({ documentId }),
|
||||
]);
|
||||
```
|
||||
|
||||
### Void for Fire-and-Forget
|
||||
|
||||
```typescript
|
||||
// ✅ Use void for intentionally unwaited promises
|
||||
void handleAutoSave();
|
||||
|
||||
// ✅ Or in event handlers
|
||||
onClick={() => void onFormSubmit()}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Whitespace & Formatting
|
||||
|
||||
### Blank Lines Between Concepts
|
||||
|
||||
```typescript
|
||||
// ✅ Blank line after imports
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export const findDocuments = async () => {
|
||||
// ...
|
||||
};
|
||||
|
||||
// ✅ Blank line between logical sections
|
||||
const user = await prisma.user.findFirst({ where: { id: userId } });
|
||||
|
||||
let team = null;
|
||||
|
||||
if (teamId !== undefined) {
|
||||
team = await getTeamById({ userId, teamId });
|
||||
}
|
||||
|
||||
// ✅ Blank line before return statements
|
||||
const result = await someOperation();
|
||||
|
||||
return result;
|
||||
```
|
||||
|
||||
### Function/Method Spacing
|
||||
|
||||
```typescript
|
||||
// ✅ No blank lines between chained methods in same operation
|
||||
const documents = await prisma.document
|
||||
.findMany({ where: { userId } })
|
||||
.then((docs) => docs.map(maskTokens));
|
||||
|
||||
// ✅ Blank line between different operations
|
||||
const document = await createDocument({ userId });
|
||||
|
||||
await sendDocument({ documentId: document.id });
|
||||
|
||||
return document;
|
||||
```
|
||||
|
||||
### Object and Array Formatting
|
||||
|
||||
```typescript
|
||||
// ✅ Multi-line when complex
|
||||
const options = {
|
||||
userId,
|
||||
teamId,
|
||||
status: ExtendedDocumentStatus.ALL,
|
||||
page: 1,
|
||||
};
|
||||
|
||||
// ✅ Single line when simple
|
||||
const coords = { x: 0, y: 0 };
|
||||
|
||||
// ✅ Array items on separate lines when objects
|
||||
const recipients = [
|
||||
{
|
||||
name: 'John',
|
||||
email: 'john@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Jane',
|
||||
email: 'jane@example.com',
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Variables
|
||||
|
||||
```typescript
|
||||
// ✅ camelCase for variables and functions
|
||||
const documentId = 123;
|
||||
const onSubmit = () => {};
|
||||
|
||||
// ✅ Descriptive names with auxiliary verbs for booleans
|
||||
const isLoading = false;
|
||||
const hasError = false;
|
||||
const canEdit = true;
|
||||
const shouldRender = true;
|
||||
|
||||
// ✅ Prefix with $ for DOM elements
|
||||
const $page = document.querySelector('.page');
|
||||
const $inputRef = useRef<HTMLInputElement>(null);
|
||||
```
|
||||
|
||||
### Types and Schemas
|
||||
|
||||
```typescript
|
||||
// ✅ PascalCase for types
|
||||
type CreateDocumentOptions = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
// ✅ Prefix Zod schemas with Z
|
||||
const ZCreateDocumentSchema = z.object({
|
||||
title: z.string(),
|
||||
});
|
||||
|
||||
// ✅ Prefix type from Zod schema with T
|
||||
type TCreateDocumentSchema = z.infer<typeof ZCreateDocumentSchema>;
|
||||
```
|
||||
|
||||
### Constants
|
||||
|
||||
```typescript
|
||||
// ✅ UPPER_SNAKE_CASE for true constants
|
||||
const DEFAULT_DOCUMENT_DATE_FORMAT = 'dd/MM/yyyy';
|
||||
const MAX_FILE_SIZE = 1024 * 1024 * 5;
|
||||
|
||||
// ✅ camelCase for const variables that aren't "constants"
|
||||
const userId = await getUserId();
|
||||
```
|
||||
|
||||
### Functions
|
||||
|
||||
```typescript
|
||||
// ✅ Verb-based names for functions
|
||||
const createDocument = async () => {};
|
||||
const findDocuments = async () => {};
|
||||
const updateDocument = async () => {};
|
||||
const deleteDocument = async () => {};
|
||||
|
||||
// ✅ On prefix for event handlers
|
||||
const onSubmit = () => {};
|
||||
const onClick = () => {};
|
||||
const onFieldCopy = () => {}; // 'on' is also acceptable
|
||||
```
|
||||
|
||||
### Clarity Over Brevity
|
||||
|
||||
```typescript
|
||||
// ✅ Prefer descriptive names over abbreviations
|
||||
const superLongMethodThatIsCorrect = () => {};
|
||||
const recipientAuthenticationOptions = {};
|
||||
const documentMetadata = {};
|
||||
|
||||
// ❌ Avoid abbreviations that sacrifice clarity
|
||||
const supLongMethThatIsCorrect = () => {};
|
||||
const recipAuthOpts = {};
|
||||
const docMeta = {};
|
||||
|
||||
// ✅ Common abbreviations that are widely understood are acceptable
|
||||
const userId = 123;
|
||||
const htmlElement = document.querySelector('div');
|
||||
const apiResponse = await fetch('/api');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pattern Matching
|
||||
|
||||
### Using ts-pattern
|
||||
|
||||
```typescript
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
// ✅ Use match for complex conditionals
|
||||
const result = match(status)
|
||||
.with(ExtendedDocumentStatus.DRAFT, () => ({
|
||||
status: 'draft',
|
||||
}))
|
||||
.with(ExtendedDocumentStatus.PENDING, () => ({
|
||||
status: 'pending',
|
||||
}))
|
||||
.with(ExtendedDocumentStatus.COMPLETED, () => ({
|
||||
status: 'completed',
|
||||
}))
|
||||
.exhaustive();
|
||||
|
||||
// ✅ Use .otherwise() for default case when not exhaustive
|
||||
const value = match(type)
|
||||
.with('text', () => 'Text field')
|
||||
.with('number', () => 'Number field')
|
||||
.otherwise(() => 'Unknown field');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database & Prisma
|
||||
|
||||
### Query Structure
|
||||
|
||||
```typescript
|
||||
// ✅ Destructure commonly used fields
|
||||
const { id, email, name } = user;
|
||||
|
||||
// ✅ Use select to limit returned fields
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
|
||||
// ✅ Use include for relations
|
||||
const document = await prisma.document.findFirst({
|
||||
where: { id: documentId },
|
||||
include: {
|
||||
recipients: true,
|
||||
fields: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Transactions
|
||||
|
||||
```typescript
|
||||
// ✅ Use transactions for related operations
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const document = await tx.document.create({ data });
|
||||
|
||||
await tx.field.createMany({ data: fieldsData });
|
||||
|
||||
await tx.documentAuditLog.create({ data: auditData });
|
||||
|
||||
return document;
|
||||
});
|
||||
```
|
||||
|
||||
### Where Clauses
|
||||
|
||||
```typescript
|
||||
// ✅ Build complex where clauses separately
|
||||
const whereClause: Prisma.DocumentWhereInput = {
|
||||
AND: [
|
||||
{ userId: user.id },
|
||||
{ deletedAt: null },
|
||||
{ status: { in: [DocumentStatus.DRAFT, DocumentStatus.PENDING] } },
|
||||
],
|
||||
};
|
||||
|
||||
const documents = await prisma.document.findMany({
|
||||
where: whereClause,
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TRPC Patterns
|
||||
|
||||
### Router Structure
|
||||
|
||||
```typescript
|
||||
// ✅ Destructure context and input at start
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: { templateId },
|
||||
});
|
||||
|
||||
return await getTemplateById({
|
||||
id: templateId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Request/Response Schemas
|
||||
|
||||
```typescript
|
||||
// ✅ Name schemas clearly
|
||||
const ZCreateDocumentRequestSchema = z.object({
|
||||
title: z.string(),
|
||||
recipients: z.array(ZRecipientSchema),
|
||||
});
|
||||
|
||||
const ZCreateDocumentResponseSchema = z.object({
|
||||
documentId: z.number(),
|
||||
status: z.string(),
|
||||
});
|
||||
```
|
||||
|
||||
### Error Handling in TRPC
|
||||
|
||||
```typescript
|
||||
// ✅ Catch and transform errors appropriately
|
||||
try {
|
||||
const result = await createDocument({ userId, data });
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
return AppError.toRestAPIError(err);
|
||||
}
|
||||
|
||||
// ✅ Or throw AppError directly
|
||||
if (!template) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Template not found',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Patterns
|
||||
|
||||
### Optional Chaining
|
||||
|
||||
```typescript
|
||||
// ✅ Use optional chaining for potentially undefined values
|
||||
const email = user?.email;
|
||||
const recipientToken = recipient?.token ?? '';
|
||||
|
||||
// ✅ Use nullish coalescing for defaults
|
||||
const pageSize = perPage ?? 10;
|
||||
const status = documentStatus ?? DocumentStatus.DRAFT;
|
||||
```
|
||||
|
||||
### Array Operations
|
||||
|
||||
```typescript
|
||||
// ✅ Use functional array methods
|
||||
const activeRecipients = recipients.filter((r) => r.signingStatus === 'SIGNED');
|
||||
const recipientEmails = recipients.map((r) => r.email);
|
||||
const hasSignedRecipients = recipients.some((r) => r.signingStatus === 'SIGNED');
|
||||
|
||||
// ✅ Use find instead of filter + [0]
|
||||
const recipient = recipients.find((r) => r.id === recipientId);
|
||||
```
|
||||
|
||||
### Conditional Rendering
|
||||
|
||||
```typescript
|
||||
// ✅ Use && for conditional rendering
|
||||
{isLoading && <Loader />}
|
||||
|
||||
// ✅ Use ternary for either/or
|
||||
{isLoading ? <Loader /> : <Content />}
|
||||
|
||||
// ✅ Extract complex conditions to variables
|
||||
const shouldShowAdvanced = isAdmin && hasPermission && !isDisabled;
|
||||
{shouldShowAdvanced && <AdvancedSettings />}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When in Doubt
|
||||
|
||||
- **Consistency**: Follow the patterns you see in similar files
|
||||
- **Readability**: Favor code that's easy to read over clever one-liners
|
||||
- **Explicitness**: Be explicit rather than implicit
|
||||
- **Whitespace**: Use blank lines to separate logical sections
|
||||
- **Early Returns**: Use guard clauses to reduce nesting
|
||||
- **Functional**: Prefer functional patterns over imperative ones
|
||||
@@ -52,3 +52,53 @@ 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.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
> 🚨 We are live on Product Hunt 🎉 Check out our latest launch: <a href="documen.so/sign-everywhere">The Platform Plan</a>!
|
||||
|
||||
<a href="https://www.producthunt.com/posts/documenso-platform-plan?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-documenso-platform-plan" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=670576&theme=light" alt="Documenso Platform Plan - Whitelabeled signing flows in your product | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
<img src="https://github.com/documenso/documenso/assets/13398220/a643571f-0239-46a6-a73e-6bef38d1228b" alt="Documenso Logo">
|
||||
|
||||
<p align="center" style="margin-top: 20px">
|
||||
@@ -53,8 +49,6 @@ 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.
|
||||
@@ -73,9 +67,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>
|
||||
@@ -85,20 +79,17 @@ 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
|
||||
- [Next.js](https://nextjs.org/) - Framework
|
||||
- [Prisma](https://www.prisma.io/) - ORM
|
||||
- [ReactRouter](https://reactrouter.com/) - 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. -->
|
||||
|
||||
@@ -108,7 +99,7 @@ Contact us if you are interested in our Enterprise plan for large organizations
|
||||
|
||||
To run Documenso locally, you will need
|
||||
|
||||
- Node.js (v18 or above)
|
||||
- Node.js (v22 or above)
|
||||
- Postgres SQL Database
|
||||
- Docker (optional)
|
||||
|
||||
@@ -171,10 +162,8 @@ 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
|
||||
@@ -182,9 +171,11 @@ git clone https://github.com/<your-username>/documenso
|
||||
|
||||
5. Create the database schema by running `npm run prisma:migrate-dev`
|
||||
|
||||
6. Run `npm run dev` in the root directory to start
|
||||
6. Run `npm run translate:compile` in the root directory to compile lingui
|
||||
|
||||
7. Register a new user at http://localhost:3000/signup
|
||||
7. Run `npm run dev` in the root directory to start
|
||||
|
||||
8. Register a new user at http://localhost:3000/signup
|
||||
|
||||
---
|
||||
|
||||
@@ -225,8 +216,6 @@ 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:
|
||||
@@ -243,35 +232,33 @@ 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 both `NEXTAUTH_URL` and `NEXT_PUBLIC_WEBAPP_URL` variables!
|
||||
> 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!
|
||||
|
||||
Now you can install the dependencies and build it:
|
||||
|
||||
```
|
||||
npm i
|
||||
npm run build:web
|
||||
npm run build
|
||||
npm run prisma:migrate-deploy
|
||||
```
|
||||
|
||||
Finally, you can start it with:
|
||||
|
||||
```
|
||||
cd apps/web
|
||||
cd apps/remix
|
||||
npm run start
|
||||
```
|
||||
|
||||
This will start the server on `localhost:3000`. For now, any reverse proxy can then do the frontend and SSL termination.
|
||||
|
||||
> If you want to run with another port than 3000, you can start the application with `next -p <ANY PORT>` from the `apps/web` folder.
|
||||
> 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.
|
||||
|
||||
### Run as a service
|
||||
|
||||
@@ -286,7 +273,7 @@ After=network.target
|
||||
Environment=PATH=/path/to/your/node/binaries
|
||||
Type=simple
|
||||
User=www-data
|
||||
WorkingDirectory=/var/www/documenso/apps/web
|
||||
WorkingDirectory=/var/www/documenso/apps/remix
|
||||
ExecStart=/usr/bin/next start -p 3500
|
||||
TimeoutSec=15
|
||||
Restart=always
|
||||
@@ -321,7 +308,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 Next.js 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 Remix start command
|
||||
|
||||
For local docker run
|
||||
|
||||
|
||||
+16
-5
@@ -10,15 +10,26 @@ 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 command to do this:
|
||||
3. Combine the private key and the self-signed certificate to create the p12 certificate. You can run the following commands to do this:
|
||||
|
||||
`openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt`
|
||||
```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
|
||||
```
|
||||
|
||||
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**)
|
||||
4. **IMPORTANT**: A certificate password is required to prevent signing failures. Make sure to use a strong password (minimum 4 characters) when prompted. Certificates without passwords will cause "Failed to get private key bags" errors during document signing.
|
||||
|
||||
5. Place the certificate `/apps/web/resources/certificate.p12` (If the path does not exist, it needs to be created)
|
||||
5. Place the certificate `/apps/remix/resources/certificate.p12` (If the path does not exist, it needs to be created)
|
||||
|
||||
## Docker
|
||||
|
||||
|
||||
@@ -34,3 +34,8 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# next-sitemap output
|
||||
/public/sitemap.xml
|
||||
/public/robots.txt
|
||||
/public/sitemap-*.xml
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Button } from '../primitives/button';
|
||||
import { Card, CardContent } from '../primitives/card';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
|
||||
type CallToActionProps = {
|
||||
className?: string;
|
||||
@@ -0,0 +1,5 @@
|
||||
/** @type {import('next-sitemap').IConfig} */
|
||||
module.exports = {
|
||||
siteUrl: 'https://docs.documenso.com', // Replace with your actual site URL
|
||||
generateRobotsTxt: true, // Generates robots.txt
|
||||
};
|
||||
@@ -1,5 +1,8 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
import type { NextConfig } from 'next';
|
||||
|
||||
import nextra from 'nextra';
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
transpilePackages: [
|
||||
'@documenso/assets',
|
||||
'@documenso/lib',
|
||||
@@ -9,9 +12,10 @@ const nextConfig = {
|
||||
],
|
||||
};
|
||||
|
||||
const withNextra = require('nextra')({
|
||||
const withNextra = nextra({
|
||||
theme: 'nextra-theme-docs',
|
||||
themeConfig: './theme.config.tsx',
|
||||
codeHighlight: true,
|
||||
});
|
||||
|
||||
module.exports = withNextra(nextConfig);
|
||||
export default withNextra(nextConfig);
|
||||
@@ -7,8 +7,7 @@
|
||||
"build": "next build",
|
||||
"start": "next start -p 3002",
|
||||
"lint:fix": "next lint --fix",
|
||||
"clean": "rimraf .next && rimraf node_modules",
|
||||
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs"
|
||||
"clean": "rimraf .next && rimraf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@documenso/assets": "*",
|
||||
@@ -16,17 +15,18 @@
|
||||
"@documenso/tailwind-config": "*",
|
||||
"@documenso/trpc": "*",
|
||||
"@documenso/ui": "*",
|
||||
"next": "14.2.6",
|
||||
"next-plausible": "^3.12.0",
|
||||
"nextra": "^2.13.4",
|
||||
"nextra-theme-docs": "^2.13.4",
|
||||
"next": "15.5.9",
|
||||
"next-plausible": "^3.12.5",
|
||||
"nextra": "^3",
|
||||
"nextra-theme-docs": "^3",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react": "18.3.27",
|
||||
"@types/react-dom": "^18",
|
||||
"pagefind": "^1.2.0",
|
||||
"typescript": "5.6.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { PlausibleProvider } from '../providers/plausible.tsx';
|
||||
import '../styles.css';
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return (
|
||||
<PlausibleProvider>
|
||||
<Component {...pageProps} />
|
||||
</PlausibleProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/* 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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
export default {
|
||||
index: {
|
||||
type: 'page',
|
||||
title: 'Home',
|
||||
display: 'hidden',
|
||||
theme: {
|
||||
timestamp: false,
|
||||
},
|
||||
},
|
||||
users: {
|
||||
type: 'page',
|
||||
title: 'Users',
|
||||
},
|
||||
developers: {
|
||||
type: 'page',
|
||||
title: 'Developers',
|
||||
},
|
||||
updates: {
|
||||
title: "What's New",
|
||||
type: 'menu',
|
||||
items: {
|
||||
changelog: {
|
||||
title: 'Changelog',
|
||||
href: 'https://documenso.com/changelog',
|
||||
newWindow: true,
|
||||
},
|
||||
blog: {
|
||||
title: 'Blog',
|
||||
href: 'https://documenso.com/blog',
|
||||
newWindow: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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',
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default {
|
||||
index: 'Getting Started',
|
||||
'contributing-translations': 'Contributing Translations',
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"index": "Getting Started",
|
||||
"contributing-translations": "Contributing Translations"
|
||||
}
|
||||
@@ -25,7 +25,7 @@ The translation files are organized into folders represented by their respective
|
||||
Each PO file contains translations which look like this:
|
||||
|
||||
```po
|
||||
#: apps/web/src/app/(signing)/sign/[token]/no-longer-available.tsx:61
|
||||
#: apps/remix/app/(signing)/sign/[token]/no-longer-available.tsx:61
|
||||
msgid "Want to send slick signing links like this one? <0>Check out Documenso.</0>"
|
||||
msgstr "Möchten Sie auffällige Signatur-Links wie diesen senden? <0>Überprüfen Sie Documenso.</0>"
|
||||
```
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Field Coordinates
|
||||
description: Learn how to get the coordinates of a field in a document.
|
||||
---
|
||||
|
||||
## Field Coordinates
|
||||
|
||||
Field coordinates represent the position of a field in a document. They are returned in the `pageX`, `pageY`, `width` and `height` properties of the field.
|
||||
|
||||
To enable field coordinates, you can use the `devmode` query parameter.
|
||||
|
||||
```bash
|
||||
# Legacy editor
|
||||
|
||||
https://app.documenso.com/t/<team-url>/documents/<envelope-id>/legacy_editor?devmode=true
|
||||
```
|
||||
|
||||

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

|
||||
@@ -0,0 +1,11 @@
|
||||
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,10 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
---
|
||||
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.
|
||||
@@ -111,6 +111,83 @@ The colors will be automatically converted to the appropriate format internally.
|
||||
|
||||
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)
|
||||
|
||||
@@ -52,9 +52,9 @@ Platform customers have access to advanced styling options to customize the embe
|
||||
<EmbedDirectTemplate
|
||||
token={token}
|
||||
cssVars={{
|
||||
colorPrimary: '#0000FF',
|
||||
colorBackground: '#F5F5F5',
|
||||
borderRadius: '8px',
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
}}
|
||||
/>
|
||||
```
|
||||
@@ -169,6 +169,19 @@ Once you've obtained the appropriate tokens, you can integrate the signing exper
|
||||
|
||||
If you're using **web components**, the integration process is slightly different. Keep in mind that web components are currently less tested but can still provide flexibility for general use cases.
|
||||
|
||||
## Embedded Authoring
|
||||
|
||||
In addition to embedding signing experiences, Documenso now supports **embedded authoring**, allowing your users to create documents and templates directly within your application.
|
||||
|
||||
With embedded authoring, you can:
|
||||
|
||||
- Create new documents with custom fields
|
||||
- Configure document properties and settings
|
||||
- Set up recipients and signing workflows
|
||||
- Customize the authoring experience
|
||||
|
||||
For detailed implementation instructions and code examples, see our [Embedded Authoring](/developers/embedding/authoring) guide.
|
||||
|
||||
## Related
|
||||
|
||||
- [React Integration](/developers/embedding/react)
|
||||
@@ -178,3 +191,4 @@ If you're using **web components**, the integration process is slightly differen
|
||||
- [Preact Integration](/developers/embedding/preact)
|
||||
- [Angular Integration](/developers/embedding/angular)
|
||||
- [CSS Variables](/developers/embedding/css-variables)
|
||||
- [Embedded Authoring](/developers/embedding/authoring)
|
||||
|
||||
@@ -95,9 +95,9 @@ const MyEmbeddingComponent = () => {
|
||||
}
|
||||
`;
|
||||
const cssVars = {
|
||||
colorPrimary: '#0000FF',
|
||||
colorBackground: '#F5F5F5',
|
||||
borderRadius: '8px',
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -99,9 +99,9 @@ const MyEmbeddingComponent = () => {
|
||||
`}
|
||||
// CSS Variables
|
||||
cssVars={{
|
||||
colorPrimary: '#0000FF',
|
||||
colorBackground: '#F5F5F5',
|
||||
borderRadius: '8px',
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
}}
|
||||
// Dark Mode Control
|
||||
darkModeDisabled={true}
|
||||
|
||||
@@ -95,9 +95,9 @@ const MyEmbeddingComponent = () => {
|
||||
}
|
||||
`;
|
||||
const cssVars = {
|
||||
colorPrimary: '#0000FF',
|
||||
colorBackground: '#F5F5F5',
|
||||
borderRadius: '8px',
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -97,9 +97,9 @@ Platform customers have access to advanced styling options:
|
||||
}
|
||||
`;
|
||||
const cssVars = {
|
||||
colorPrimary: '#0000FF',
|
||||
colorBackground: '#F5F5F5',
|
||||
borderRadius: '8px',
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -97,9 +97,9 @@ Platform customers have access to advanced styling options:
|
||||
}
|
||||
`;
|
||||
const cssVars = {
|
||||
colorPrimary: '#0000FF',
|
||||
colorBackground: '#F5F5F5',
|
||||
borderRadius: '8px',
|
||||
primary: '#0000FF',
|
||||
background: '#F5F5F5',
|
||||
radius: '8px',
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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 { Card, Cards } from 'nextra/components';
|
||||
import { Cards } from 'nextra/components';
|
||||
|
||||
# Developer Documentation
|
||||
|
||||
The developer documentation is a comprehensive guide to help you:
|
||||
|
||||
<Cards>
|
||||
<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.Card title="Set up dev environment" href="/developers/local-development" />
|
||||
<Cards.Card title="Use the API" href="/developers/public-api" />
|
||||
<Cards.Card title="Integrate webhooks" href="/developers/webhooks" />
|
||||
<Cards.Card title="Contribute to the project" href="/developers/contributing" />
|
||||
<Cards.Card title="Self-host Documenso" href="/developers/self-hosting" />
|
||||
</Cards>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
index: 'Get Started',
|
||||
quickstart: 'Developer Quickstart',
|
||||
manual: 'Manual Setup',
|
||||
gitpod: 'Gitpod',
|
||||
'signing-certificate': 'Signing Certificate',
|
||||
translations: 'Translations',
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"index": "Get Started",
|
||||
"quickstart": "Developer Quickstart",
|
||||
"manual": "Manual Setup",
|
||||
"gitpod": "Gitpod",
|
||||
"signing-certificate": "Signing Certificate",
|
||||
"translations": "Translations"
|
||||
}
|
||||
@@ -16,18 +16,16 @@ Pick the one that fits your needs the best.
|
||||
## Tech Stack
|
||||
|
||||
- [Typescript](https://www.typescriptlang.org/) - Language
|
||||
- [Next.js](https://nextjs.org/) - Framework
|
||||
- [React Router](https://reactrouter.com/) - Framework
|
||||
- [Prisma](https://www.prisma.io/) - ORM
|
||||
- [Tailwind](https://tailwindcss.com/) - CSS
|
||||
- [shadcn/ui](https://ui.shadcn.com/) - Component Library
|
||||
- [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,10 +32,8 @@ 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 favourite database client to connect to the database.
|
||||
- Port: 54320
|
||||
- Connection: Use your favorite database client to connect to the database.
|
||||
- S3 Storage Dashboard - http://localhost:9001
|
||||
|
||||
@@ -13,35 +13,13 @@ 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/macro** (not @lingui/react).
|
||||
Wrap all text to translate in **`<Trans></Trans>`** tags exported from **@lingui/react/macro**.
|
||||
|
||||
```html
|
||||
<h1>
|
||||
@@ -64,8 +42,9 @@ For text that is broken into elements, but represent a whole sentence, you must
|
||||
### Constants outside of react components
|
||||
|
||||
```tsx
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
// Wrap text in msg`text to translate` when it's in a constant here, or another file/package.
|
||||
export const CONSTANT_WITH_MSG = {
|
||||
@@ -98,31 +77,13 @@ 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 } = setupI18nSSR();
|
||||
const { i18n } = useLingui();
|
||||
|
||||
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>;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
index: 'Get Started',
|
||||
authentication: 'Authentication',
|
||||
'rate-limits': 'Rate Limits',
|
||||
versioning: 'Versioning',
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"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 "**[User settings](https://app.documenso.com/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 "Team Settings" from the dropdown menu.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||

|
||||
|
||||
@@ -37,31 +37,17 @@ 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/v1/documents?page=1&perPage=1' \
|
||||
--header 'Authorization: api_xxxxxxxxxxxxxxxx'
|
||||
curl --location 'https://app.documenso.com/api/v2/envelope/create' \
|
||||
--header 'Authorization: api_xxxxxxxxxxxxxxxx' \
|
||||
--form 'payload={ "title": "Some Title", "type": "DOCUMENT" }'
|
||||
```
|
||||
|
||||
Here's a sample response from the API based on the above cURL request:
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
"id": "envelope_xxxxxxxxxxxxx"
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
The API key has access to your account and all its resources. Please keep it secure and do not share it with others. If you suspect your key has been compromised, you can revoke it from the "API Tokens" page in your user settings.
|
||||
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.
|
||||
|
||||
@@ -3,20 +3,38 @@ 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.
|
||||
|
||||
## Swagger Documentation
|
||||
## API V2 - Stable
|
||||
|
||||
The [Swagger documentation](https://app.documenso.com/api/v1/openapi) also provides information about the API endpoints, request parameters, response formats, and authentication methods.
|
||||
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)
|
||||
|
||||
## Availability
|
||||
|
||||
The API is available to individual users and teams.
|
||||
The API is available to individual users, teams and higher plans. [Fair Use](https://documen.so/fair) applies.
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
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,528 +7,359 @@ import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# API Reference
|
||||
|
||||
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.
|
||||
|
||||
## Upload a Document
|
||||
|
||||
Uploading a document to your Documenso account requires a two-step process.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Create Document
|
||||
|
||||
First, you need to make a `POST` request to the `/api/v1/documents` endpoint, which takes a JSON payload with the following fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "string",
|
||||
"externalId": "string",
|
||||
"recipients": [
|
||||
{
|
||||
"name": "string",
|
||||
"email": "user@example.com",
|
||||
"role": "SIGNER",
|
||||
"signingOrder": 0
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `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.
|
||||
On this page we will refer to both Documents and Templates as Envelopes for convenience, unless otherwise specified.
|
||||
|
||||
<Callout type="info">
|
||||
The `globalActionAuth` property is only available for Enterprise accounts.
|
||||
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>
|
||||
|
||||
Here's an example of the JSON payload for uploading a document:
|
||||
## Get Envelope
|
||||
|
||||
```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"
|
||||
}
|
||||
}
|
||||
For the vast majority of use cases, you will need to retrieve the envelope to get details for further operations.
|
||||
|
||||
The main details you generally want to extract are the following:
|
||||
|
||||
- **Recipients** - The individuals who will be signing the document
|
||||
- **Fields** - The fields the user will sign
|
||||
- **Items** - The PDF files the user will see and sign
|
||||
|
||||
This is done by doing a the following GET [request](https://openapi.documenso.com/reference#tag/envelope/POST/envelope/create)
|
||||
|
||||
```sh
|
||||
curl -X GET "https://app.documenso.com/api/v2/envelope/envelope_xxxxxxxxxxxxx" \
|
||||
-H "Authorization: api_xxxxxxxxxxxxxx"
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
</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/v1/templates/{templateId}/generate-document`, and it takes a JSON payload with the following fields:
|
||||
|
||||
```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 template ID from the Documenso dashboard. You can find the template ID in the URL by navigating to the template details page.
|
||||
|
||||

|
||||
|
||||
In this case, the template ID is "99999".
|
||||
|
||||
### Retrieve the Recipient(s) ID(s)
|
||||
|
||||
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}`.
|
||||
|
||||
A successful response looks as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"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": 0,
|
||||
"recipientId": 0,
|
||||
"type": "SIGNATURE",
|
||||
"page": 0,
|
||||
"positionX": "string",
|
||||
"positionY": "string",
|
||||
"width": "string",
|
||||
"height": "string"
|
||||
}
|
||||
],
|
||||
"Recipient": [
|
||||
{
|
||||
"id": 0,
|
||||
"email": "user@example.com",
|
||||
"name": "string",
|
||||
"signingOrder": 0,
|
||||
"authOptions": "string",
|
||||
"role": "CC"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
</Steps>
|
||||
|
||||
## Add Fields to Document
|
||||
|
||||
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.
|
||||
|
||||
To add fields to a document, you need to make a `POST` request with a JSON payload containing the field(s) information.
|
||||
|
||||
```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
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
<Callout type="info">This endpoint accepts either one field or an array of fields.</Callout>
|
||||
|
||||
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.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Retrieve the Recipient(s) ID(s)
|
||||
|
||||
Perform a `GET` request to the `/api/v1/documents/{id}` to retrieve the details of a specific document, including the recipient's information.
|
||||
|
||||
An example response would look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 137,
|
||||
"externalId": null,
|
||||
"userId": 3,
|
||||
"teamId": null,
|
||||
"title": "documenso.pdf",
|
||||
"internalVersion": 2,
|
||||
"type": "TEMPLATE",
|
||||
"status": "DRAFT",
|
||||
"documentDataId": "<document-data-id>",
|
||||
"createdAt": "2024-10-11T12:29:12.725Z",
|
||||
"updatedAt": "2024-10-11T12:29:12.725Z",
|
||||
"source": "TEMPLATE",
|
||||
"visibility": "EVERYONE",
|
||||
"templateType": "PRIVATE",
|
||||
"id": "envelope_xxxxxxxxxxxxxxxx",
|
||||
"secondaryId": "template_9",
|
||||
"externalId": null,
|
||||
"createdAt": "2025-11-12T11:36:38.391Z",
|
||||
"updatedAt": "2025-11-12T11:36:55.648Z",
|
||||
"completedAt": null,
|
||||
"deletedAt": null,
|
||||
"title": "Title",
|
||||
"recipients": [
|
||||
{
|
||||
"id": 55,
|
||||
"documentId": 137,
|
||||
"email": "ashdrew@email.com",
|
||||
"name": "Ash Drew",
|
||||
"envelopeId": "envelope_xxxxxxxxxxxxxxxx",
|
||||
"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>"
|
||||
"id": 0,
|
||||
"email": "recipient+1@documenso.com",
|
||||
"name": "",
|
||||
"token": "cxH2Ss79Hj94M1U3PxRAG",
|
||||
"documentDeletedAt": null,
|
||||
"expired": null,
|
||||
"signedAt": null,
|
||||
"authOptions": {
|
||||
"accessAuth": [],
|
||||
"actionAuth": []
|
||||
},
|
||||
"signingOrder": 1,
|
||||
"rejectionReason": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
From this response, you'll only need the recipient ID, which is `55` in this case.
|
||||
|
||||
### (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
|
||||
"envelopeItems": [
|
||||
{
|
||||
"envelopeId": "envelope_xxxxxxxxxxxxxxxx",
|
||||
"id": "envelope_item_xxxxxxxxxxxxxxxxx",
|
||||
"title": "FileOne",
|
||||
"order": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
// Other attributes
|
||||
}
|
||||
```
|
||||
|
||||
## Create Envelope
|
||||
|
||||
This example request will create a document with the following:
|
||||
|
||||
- Two PDF files
|
||||
- Two recipients with one field each
|
||||
|
||||
You will need to:
|
||||
|
||||
- Replace api_xxxxxxxxxxxxxx with your API key
|
||||
- Replace the file paths with the actual paths to your files
|
||||
|
||||
Note that the identifier on a field is linking to the files you upload. It can either be the index of the file or the filename. In this example it uses the index of the file.
|
||||
|
||||
```sh
|
||||
curl -X POST "https://app.documenso.com/api/v2/envelope/create" \
|
||||
-H "Authorization: api_xxxxxxxxxxxxxx" \
|
||||
-H "Content-Type: multipart/form-data" \
|
||||
-F 'payload={
|
||||
"type": "DOCUMENT",
|
||||
"title": "Envelope Full Field Test",
|
||||
"recipients": [
|
||||
{
|
||||
"email": "recipient+1@example.com",
|
||||
"name": "Recipient One",
|
||||
"role": "SIGNER",
|
||||
"fields": [
|
||||
{
|
||||
"identifier": 0,
|
||||
"type": "SIGNATURE",
|
||||
"page": 1,
|
||||
"positionX": 0,
|
||||
"positionY": 0,
|
||||
"width": 50,
|
||||
"height": 50
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"email": "recipient-2@example.com",
|
||||
"name": "Recipient Two",
|
||||
"role": "SIGNER",
|
||||
"fields": [
|
||||
{
|
||||
"identifier": 1,
|
||||
"type": "SIGNATURE",
|
||||
"page": 1,
|
||||
"positionX": 0,
|
||||
"positionY": 0,
|
||||
"width": 50,
|
||||
"height": 50
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}' \
|
||||
-F "files=@./field-font-alignment.pdf;type=application/pdf" \
|
||||
-F "files=@./field-meta.pdf;type=application/pdf"
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
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.
|
||||
See the full request and response formats for the create endpoint
|
||||
[here](https://openapi.documenso.com/reference#tag/envelope/POST/envelope/create)
|
||||
</Callout>
|
||||
|
||||
<Callout type="warning">
|
||||
It's important to pass the `type` in the `fieldMeta` property for the advanced fields. [Read more
|
||||
here](#a-note-on-advanced-fields)
|
||||
## Use Template
|
||||
|
||||
Documenso allows you to generate documents from templates. This is useful when you have a standard document format you want to reuse.
|
||||
|
||||
The API endpoint for generating a document from a template is `/api/v2/envelopes/use`, and it takes a JSON payload with the following fields:
|
||||
|
||||
<Callout type="info">
|
||||
See the full request and response formats for the use endpoint
|
||||
[here](https://openapi.documenso.com/reference#tag/envelope/POST/envelope/use)
|
||||
</Callout>
|
||||
|
||||
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.
|
||||
<Steps>
|
||||
|
||||

|
||||
### Grab the Template ID
|
||||
|
||||
The first step is to retrieve the ID of the template you want to use. This can be done via the API or from the Documenso UI.
|
||||
|
||||
You can get the ID by navigating to the template page and looking at the URL. The ID is the last part of the URL which looks like this "envelope_xxxxxxxx"
|
||||
|
||||
### Retrieve the Recipient(s) ID(s)
|
||||
|
||||
Once you have the template ID, you will want to retrieve the full template so you can see the recipients.
|
||||
|
||||
This is optional, and you only will want to do this if you want to modify the recipient details prior to creating the new document.
|
||||
|
||||
See the [Get Envelope](#get-envelope) section for more details on how to retrieve the envelope.
|
||||
|
||||
### Generate the Document
|
||||
|
||||
To generate a document from the template, you need to make a POST request to the [`/api/v2/envelope/use`](https://openapi.documenso.com/reference#tag/envelope/POST/envelope/use) endpoint.
|
||||
|
||||
You will need to replace the following:
|
||||
|
||||
- `api_xxxxxxxxxxxxxx` with your API key
|
||||
- `envelope_xxxxxxxxxxxxxxx` with the template ID
|
||||
- `RECIPIENT_ID_HERE` with the recipient ID you want to modify prior to sending
|
||||
- `RECIPIENT_EMAIL_HERE` with the new recipient email you want to use
|
||||
- `RECIPIENT_NAME_HERE` with the new recipient name you want to use
|
||||
|
||||
If you want to send the document immediately, you can add the `distributeDocument` parameter to the payload.
|
||||
|
||||
```sh
|
||||
curl -X POST "https://app.documenso.com/api/v2/envelope/use" \
|
||||
-H "Authorization: api_xxxxxxxxxxxxxx" \
|
||||
-H "Content-Type: multipart/form-data" \
|
||||
-F 'payload={
|
||||
"envelopeId": "envelope_xxxxxxxxxxxxxxx",
|
||||
"recipients": [
|
||||
{
|
||||
"id": RECIPIENT_ID_HERE,
|
||||
"email": "RECIPIENT_EMAIL_HERE",
|
||||
"name": "RECIPIENT_NAME_HERE"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
#### Generate the Document with Custom Files
|
||||
|
||||
You can also generate a document from a template using custom PDF files to replace the template's default PDF files.
|
||||
|
||||
To do this, you will need to grab the `envelopeItemId` of the item you want to replace, this is found in the GET request in the previous step.
|
||||
|
||||
You will need to replace the following:
|
||||
|
||||
- `CUSTOM_FILE_NAME_HERE.pdf` with the custom file name
|
||||
- `envelope_item_xxxxxxxxxxxxxxxxx` with the `envelopeItemId` of the item you want to replace
|
||||
|
||||
```sh
|
||||
curl -X POST "https://app.documenso.com/api/v2/envelope/use" \
|
||||
-H "Authorization: api_xxxxxxxxxxxxxx" \
|
||||
-H "Content-Type: multipart/form-data" \
|
||||
-F 'payload={
|
||||
"envelopeId": "envelope_xxxxxxxxxxxxxxx",
|
||||
"recipients": [
|
||||
{
|
||||
"id": RECIPIENT_ID_HERE,
|
||||
"email": "RECIPIENT_EMAIL_HERE",
|
||||
"name": "RECIPIENT_NAME_HERE"
|
||||
}
|
||||
],
|
||||
"customDocumentData": [
|
||||
{
|
||||
"identifier": "CUSTOM_FILE_NAME_HERE.pdf",
|
||||
"envelopeItemId": "envelope_item_xxxxxxxxxxxxxxxxx"
|
||||
}
|
||||
]
|
||||
}' \
|
||||
-F "files=@./CUSTOM_FILE_NAME_HERE.pdf;type=application/pdf"
|
||||
```
|
||||
|
||||
You can now access the document in your Documenso account dashboard.
|
||||
|
||||
#### Pre-fill Fields On Document Creation
|
||||
|
||||
The API allows you to pre-fill fields on document creation. This is useful when you want to create a document from an existing template and pre-fill the fields with specific values.
|
||||
|
||||
```json
|
||||
{
|
||||
"prefillFields": [
|
||||
{
|
||||
"id": 21,
|
||||
"type": "text",
|
||||
"label": "my-label",
|
||||
"placeholder": "my-placeholder",
|
||||
"value": "my-value"
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "number",
|
||||
"label": "my-label",
|
||||
"placeholder": "my-placeholder",
|
||||
"value": "123"
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"type": "checkbox",
|
||||
"label": "my-label",
|
||||
"placeholder": "my-placeholder",
|
||||
"value": ["option-1", "option-2"]
|
||||
}
|
||||
]
|
||||
// Other payload properties...
|
||||
}
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
#### A Note on Advanced Fields
|
||||
## Add Recipients
|
||||
|
||||
The advanced fields are: text, checkbox, radio, number, and select. Whenever you append any of these advanced fields to a document, you need to pass the `type` in the `fieldMeta` property:
|
||||
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.
|
||||
|
||||
```json
|
||||
...
|
||||
"fieldMeta": {
|
||||
"type": "text",
|
||||
}
|
||||
...
|
||||
The following is an example of a request which creates 2 new recipients on the envelope.
|
||||
|
||||
```sh
|
||||
curl https://app.documenso.com/api/v2/envelope/recipient/create-many \
|
||||
--request POST \
|
||||
--header 'Authorization: api_xxxxxxxxxxxxxx' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"envelopeId": "envelope_brxkyaxywuxkyeuv",
|
||||
"data": [
|
||||
{
|
||||
"name": "Recipient Name",
|
||||
"email": "example@documenso.com",
|
||||
"role": "SIGNER"
|
||||
},
|
||||
{
|
||||
"name": "Recipient Two",
|
||||
"email": "example+2@documenso.com",
|
||||
"role": "APPROVER"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
Replace the `text` value with the corresponding field type:
|
||||
## Add Fields
|
||||
|
||||
- For the `TEXT` field it should be `text`.
|
||||
- For the `CHECKBOX` field it should be `checkbox`.
|
||||
- For the `RADIO` field it should be `radio`.
|
||||
- For the `NUMBER` field it should be `number`.
|
||||
- For the `SELECT` field it should be `select`. (check this before merge)
|
||||
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.
|
||||
|
||||
You must pass this property at all times, even if you don't need to set any other properties. If you don't, the endpoint will throw an error.
|
||||
Before adding fields to an envelope, you will need the following details:
|
||||
|
||||
- **Envelope ID** - Which envelope the field will be added to
|
||||
- **Recipient ID** - Which recipient the field will be added to
|
||||
- **Envelope Item ID** - Which file the field will be added to. If blank, the field will be added to the first file.
|
||||
|
||||
See the [Get Envelope](#get-envelope) section for more details on how to retrieve these details.
|
||||
|
||||
The following is an example of a request which creates 2 new fields on the first page of the envelope.
|
||||
|
||||
Note that width, height, positionX and positionY are percentage numbers between 0 and 100, which scale the field relative to the size of the PDF.
|
||||
|
||||
```sh
|
||||
curl https://app.documenso.com/api/v2/envelope/field/create-many \
|
||||
--request POST \
|
||||
--header 'Authorization: api_xxxxxxxxxxxxxx' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"envelopeId": "envelope_xxxxxxxxxx",
|
||||
"data": [
|
||||
{
|
||||
"recipientId": recipient_id_here,
|
||||
"envelopeItemId": "envelope_item_id_here",
|
||||
"type": "TEXT",
|
||||
"page": 1,
|
||||
"positionX": 0,
|
||||
"positionY": 0,
|
||||
"width": 50,
|
||||
"height": 50,
|
||||
"fieldMeta": {
|
||||
"type": "text",
|
||||
"label": "First test field"
|
||||
}
|
||||
},
|
||||
{
|
||||
"recipientId": recipient_id_here,
|
||||
"envelopeItemId": "envelope_item_id_here",
|
||||
"type": "TEXT",
|
||||
"page": 1,
|
||||
"positionX": 50,
|
||||
"positionY": 50,
|
||||
"width": 50,
|
||||
"height": 50,
|
||||
"fieldMeta": {
|
||||
"type": "text",
|
||||
"label": "Second test field"
|
||||
}
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
Field meta allows you to further configure fields, for example it will allow you to add multiple items for checkboxes or radios.
|
||||
|
||||
A successful request will return a JSON response with the newly added fields.
|
||||
|
||||
@@ -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 `v1`.</Callout>
|
||||
<Callout type="info">The current version of the API is `v2`.</Callout>
|
||||
|
||||
The API version is specified in the URL. For example, the base URL for the `v1` API is `https://app.documenso.com/api/v1`.
|
||||
The API version is specified in the URL. For example, the base URL for the `v2` API is `https://app.documenso.com/api/v2`.
|
||||
|
||||
We may make changes to the API without incrementing the version number. We will always try to avoid breaking changes, but in some cases, it may be necessary to make changes that are not backward compatible. In these cases, we will increment the version number and provide information about the changes in the release notes.
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
index: 'Getting Started',
|
||||
'signing-certificate': 'Signing Certificate',
|
||||
'how-to': 'How To',
|
||||
'setting-up-oauth-providers': 'Setting up OAuth Providers',
|
||||
telemetry: 'Telemetry',
|
||||
'ai-features': 'AI Recipient & Field Detection',
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"index": "Getting Started",
|
||||
"signing-certificate": "Signing Certificate",
|
||||
"how-to": "How To",
|
||||
"setting-up-oauth-providers": "Setting up OAuth Providers"
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
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 '@documenso/ui/components/call-to-action';
|
||||
import { CallToAction } from '../../../components/call-to-action';
|
||||
|
||||
# Self Hosting
|
||||
|
||||
@@ -35,10 +35,8 @@ 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
|
||||
@@ -46,8 +44,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 both
|
||||
the `NEXTAUTH_URL` and `NEXT_PUBLIC_WEBAPP_URL` variables!
|
||||
If you use a reverse proxy in front of Documenso, don't forget to provide the public URL for the
|
||||
`NEXT_PUBLIC_WEBAPP_URL` variable!
|
||||
</Callout>
|
||||
|
||||
### Install the Dependencies
|
||||
@@ -56,7 +54,7 @@ Install the project dependencies as follows:
|
||||
|
||||
```bash
|
||||
npm i
|
||||
npm run build:web
|
||||
npm run build
|
||||
npm run prisma:migrate-deploy
|
||||
```
|
||||
|
||||
@@ -71,7 +69,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/web` folder.
|
||||
If you want to run with another port than `3000`, you can start the application with `next -p <ANY PORT>` from the `apps/remix` folder.
|
||||
</Callout>
|
||||
|
||||
</Steps>
|
||||
@@ -121,16 +119,92 @@ NEXT_PRIVATE_SMTP_USERNAME="<your-username>"
|
||||
NEXT_PRIVATE_SMTP_PASSWORD="<your-password>"
|
||||
```
|
||||
|
||||
### Update the Volume Binding
|
||||
For full AI setup details (including model availability notes), see the [AI Recipient & Field Detection (Self-hosting)](./ai-features) page.
|
||||
|
||||
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:
|
||||
### Set Up Your Signing Certificate
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /path/to/your/keyfile.p12:/opt/documenso/cert.p12
|
||||
```
|
||||
<Callout type="warning">
|
||||
This is the most common source of issues for self-hosters. Please follow these steps carefully.
|
||||
</Callout>
|
||||
|
||||
After updating the volume binding, save the `compose.yml` file and run the following command to start the containers:
|
||||
The `cert.p12` file is required to sign and encrypt documents. You have three options:
|
||||
|
||||
#### Option A: Generate Certificate Inside Container (Recommended)
|
||||
|
||||
This method avoids file permission issues by creating the certificate directly inside the Docker container:
|
||||
|
||||
1. Start your containers:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
2. Set certificate password securely and generate certificate inside the container:
|
||||
|
||||
```bash
|
||||
# Set certificate password securely (won't appear in command history)
|
||||
read -s -p "Enter certificate password: " CERT_PASS
|
||||
echo
|
||||
|
||||
# Generate certificate inside container using environment variable
|
||||
docker exec -e CERT_PASS="$CERT_PASS" -it documenso-production-documenso-1 bash -c "
|
||||
mkdir -p /app/certs && \
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout /tmp/private.key \
|
||||
-out /tmp/certificate.crt \
|
||||
-subj '/C=US/ST=State/L=City/O=Organization/CN=localhost' && \
|
||||
openssl pkcs12 -export -out /app/certs/cert.p12 \
|
||||
-inkey /tmp/private.key -in /tmp/certificate.crt \
|
||||
-passout env:CERT_PASS && \
|
||||
rm /tmp/private.key /tmp/certificate.crt
|
||||
"
|
||||
```
|
||||
|
||||
3. Add the certificate passphrase to your `.env` file:
|
||||
|
||||
```bash
|
||||
NEXT_PRIVATE_SIGNING_PASSPHRASE="your_password_here"
|
||||
```
|
||||
|
||||
4. Restart the container to apply changes:
|
||||
```bash
|
||||
docker-compose restart documenso
|
||||
```
|
||||
|
||||
#### Option B: Use an Existing Certificate File
|
||||
|
||||
If you have an existing `.p12` certificate file:
|
||||
|
||||
1. **Place your certificate file** in an accessible location on your host system
|
||||
2. **Set proper permissions:**
|
||||
|
||||
```bash
|
||||
# Make sure the certificate is readable
|
||||
chmod 644 /path/to/your/cert.p12
|
||||
|
||||
# For Docker, ensure proper ownership
|
||||
chown 1001:1001 /path/to/your/cert.p12
|
||||
```
|
||||
|
||||
3. **Update the volume binding** in the `compose.yml` file:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /path/to/your/cert.p12:/opt/documenso/cert.p12:ro
|
||||
```
|
||||
|
||||
4. **Add certificate configuration** to your `.env` file:
|
||||
```bash
|
||||
NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/opt/documenso/cert.p12
|
||||
NEXT_PRIVATE_SIGNING_PASSPHRASE=your_certificate_password
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
Your certificate MUST have a password. Certificates without passwords will cause "Failed to get
|
||||
private key bags" errors.
|
||||
</Callout>
|
||||
|
||||
After setting up your certificate, save the `compose.yml` file and run the following command to start the containers:
|
||||
|
||||
```bash
|
||||
docker-compose --env-file ./.env up -d
|
||||
@@ -171,7 +245,6 @@ 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>"
|
||||
@@ -197,48 +270,95 @@ 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.
|
||||
|
||||
| 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. |
|
||||
For AI setup specifics, see the [AI Recipient & Field Detection (Self-hosting)](./ai-features) page.
|
||||
|
||||
| Variable | Description |
|
||||
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `PORT` | The port on which the Documenso application runs. It defaults to `3000`. |
|
||||
| `NEXTAUTH_SECRET` | The secret key used by NextAuth.js for encryption and signing. |
|
||||
| `NEXT_PRIVATE_ENCRYPTION_KEY` | The primary encryption key for symmetric encryption and decryption (at least 32 characters). |
|
||||
| `NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY` | The secondary encryption key for symmetric encryption and decryption (at least 32 characters). |
|
||||
| `NEXT_PRIVATE_GOOGLE_CLIENT_ID` | The Google client ID for Google authentication (optional). |
|
||||
| `NEXT_PRIVATE_GOOGLE_CLIENT_SECRET` | The Google client secret for Google authentication (optional). |
|
||||
| `NEXT_PRIVATE_MICROSOFT_CLIENT_ID` | The Microsoft client ID for Microsoft authentication (optional). |
|
||||
| `NEXT_PRIVATE_MICROSOFT_CLIENT_SECRET` | The Microsoft client secret for Microsoft authentication (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_CLIENT_ID` | The OIDC client ID for OIDC authentication (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_CLIENT_SECRET` | The OIDC client secret for OIDC authentication (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_WELL_KNOWN` | The well-known URL for the OIDC provider (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_PROVIDER_LABEL` | The label to display for the OIDC provider button (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_SKIP_VERIFY` | Whether to skip email verification for OIDC accounts (optional, default `false`). |
|
||||
| `NEXT_PUBLIC_WEBAPP_URL` | The URL for the web application. |
|
||||
| `NEXT_PUBLIC_SUPPORT_EMAIL` | The support email address displayed to users (default `support@documenso.com`). |
|
||||
| `NEXT_PRIVATE_DATABASE_URL` | The URL for the primary database connection (with connection pooling). |
|
||||
| `NEXT_PRIVATE_DIRECT_DATABASE_URL` | The URL for the direct database connection (without connection pooling). |
|
||||
| `NEXT_PRIVATE_SIGNING_TRANSPORT` | The signing transport to use. Available options: local (default), gcloud-hsm |
|
||||
| `NEXT_PRIVATE_SIGNING_PASSPHRASE` | The passphrase for the key file. |
|
||||
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS` | The base64-encoded contents of the key file will be used instead of the file path. |
|
||||
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH` | The path to the key file, default `/opt/documenso/cert.p12`. |
|
||||
| `NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY` | Comma-separated list of timestamp authority URLs for PDF signing. Enables LTV and archival timestamps. |
|
||||
| `NEXT_PUBLIC_SIGNING_CONTACT_INFO` | Contact info to embed in PDF signatures. Defaults to the webapp URL. |
|
||||
| `NEXT_PRIVATE_USE_LEGACY_SIGNING_SUBFILTER` | Set to "true" to use the legacy adbe.pkcs7.detached subfilter instead of ETSI.CAdES.detached. |
|
||||
| `NEXT_PUBLIC_UPLOAD_TRANSPORT` | The transport for file uploads (database or s3). |
|
||||
| `NEXT_PRIVATE_UPLOAD_ENDPOINT` | The endpoint for the S3 storage transport (for third-party S3-compatible providers). |
|
||||
| `NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE` | Whether to force path-style URLs for the S3 storage transport. |
|
||||
| `NEXT_PRIVATE_UPLOAD_REGION` | The region for the S3 storage transport (defaults to us-east-1). |
|
||||
| `NEXT_PRIVATE_UPLOAD_BUCKET` | The bucket to use for the S3 storage transport. |
|
||||
| `NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID` | The access key ID for the S3 storage transport. |
|
||||
| `NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY` | The secret access key for the S3 storage transport. |
|
||||
| `NEXT_PRIVATE_SMTP_TRANSPORT` | The transport to send emails (smtp-auth, smtp-api, resend, or mailchannels). |
|
||||
| `NEXT_PRIVATE_SMTP_HOST` | The host for the SMTP server for SMTP transports. |
|
||||
| `NEXT_PRIVATE_SMTP_PORT` | The port for the SMTP server for SMTP transports. |
|
||||
| `NEXT_PRIVATE_SMTP_USERNAME` | The username for the SMTP server for the `smtp-auth` transport. |
|
||||
| `NEXT_PRIVATE_SMTP_PASSWORD` | The password for the SMTP server for the `smtp-auth` transport. |
|
||||
| `NEXT_PRIVATE_SMTP_APIKEY_USER` | The API key user for the SMTP server for the `smtp-api` transport. |
|
||||
| `NEXT_PRIVATE_SMTP_APIKEY` | The API key for the SMTP server for the `smtp-api` transport. |
|
||||
| `NEXT_PRIVATE_SMTP_SECURE` | Whether to force the use of TLS for the SMTP server for SMTP transports. |
|
||||
| `NEXT_PRIVATE_SMTP_UNSAFE_IGNORE_TLS` | Whether to ignore TLS errors for the SMTP server (useful for self-signed certificates). |
|
||||
| `NEXT_PRIVATE_SMTP_FROM_ADDRESS` | The email address for the "from" address. |
|
||||
| `NEXT_PRIVATE_SMTP_FROM_NAME` | The sender name for the "from" address. |
|
||||
| `NEXT_PRIVATE_RESEND_API_KEY` | The API key for Resend.com for the `resend` transport. |
|
||||
| `NEXT_PRIVATE_MAILCHANNELS_API_KEY` | The optional API key for MailChannels (if using a proxy) for the `mailchannels` transport. |
|
||||
| `NEXT_PRIVATE_MAILCHANNELS_ENDPOINT` | The optional endpoint for the MailChannels API (if using a proxy) for the `mailchannels` transport. |
|
||||
| `NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN` | The domain for DKIM signing with MailChannels for the `mailchannels` transport. |
|
||||
| `NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR` | The selector for DKIM signing with MailChannels for the `mailchannels` transport. |
|
||||
| `NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY` | The private key for DKIM signing with MailChannels for the `mailchannels` transport. |
|
||||
| `NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT` | The maximum document upload limit displayed to the user (in MB). |
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | The optional PostHog key for analytics and feature flags. |
|
||||
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Whether to disable user signups through the /signup page. |
|
||||
| `NEXT_PRIVATE_BROWSERLESS_URL` | The URL for a Browserless.io instance to generate PDFs (optional). |
|
||||
| `DOCUMENSO_DISABLE_TELEMETRY` | Set to `true` to disable anonymous telemetry (see [Telemetry](#telemetry) section below). |
|
||||
| `GOOGLE_VERTEX_PROJECT_ID` | Google Cloud project ID used for Vertex AI (required for AI detection). |
|
||||
| `GOOGLE_VERTEX_API_KEY` | Vertex AI Express API key with access to Gemini models (required for AI detection). See [AI Recipient & Field Detectionfor](./ai-features) for details. |
|
||||
| `GOOGLE_VERTEX_LOCATION` | Optional Vertex region, defaults to `global`. Not all models are available in every region. |
|
||||
|
||||
## Telemetry
|
||||
|
||||
Documenso collects anonymous telemetry data to help us understand how the software is being used and improve the product. This telemetry is **enabled by default** for self-hosted instances.
|
||||
|
||||
### What We Collect
|
||||
|
||||
We collect minimal, privacy-preserving data:
|
||||
|
||||
- **App Version**: The version of Documenso you are running
|
||||
- **Installation ID**: A unique identifier for your installation (stored in your database)
|
||||
- **Node ID**: A unique identifier for each server/container instance (stored in the OS temp directory)
|
||||
|
||||
We do **not** collect any personal data, document contents, user information, or usage patterns.
|
||||
|
||||
### Events
|
||||
|
||||
- **Server Startup**: Captured once when the server starts
|
||||
- **Server Heartbeat**: Captured every hour while the server is running
|
||||
|
||||
### Disabling Telemetry
|
||||
|
||||
To disable telemetry, set the following environment variable:
|
||||
|
||||
```bash
|
||||
DOCUMENSO_DISABLE_TELEMETRY=true
|
||||
```
|
||||
|
||||
This will completely disable all telemetry data collection.
|
||||
|
||||
## Run as a Service
|
||||
|
||||
@@ -253,7 +373,7 @@ After=network.target
|
||||
Environment=PATH=/path/to/your/node/binaries
|
||||
Type=simple
|
||||
User=www-data
|
||||
WorkingDirectory=/var/www/documenso/apps/web
|
||||
WorkingDirectory=/var/www/documenso/apps/remix
|
||||
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 '@documenso/ui/components/call-to-action';
|
||||
import { CallToAction } from '../../../components/call-to-action';
|
||||
|
||||
# Getting Started with Self-Hosting
|
||||
|
||||
|
||||
@@ -27,3 +27,33 @@ 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,15 +53,21 @@ 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 Google Cloud Credentials file path for the gcloud-hsm signing transport. 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 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. |
|
||||
|
||||
</Steps>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
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.
|
||||
@@ -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 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.
|
||||
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.
|
||||
|
||||
Some of the common use cases for webhooks include:
|
||||
|
||||
@@ -21,26 +21,27 @@ Documenso supports Webhooks and allows you to subscribe to the following events:
|
||||
- `document.signed`
|
||||
- `document.completed`
|
||||
- `document.rejected`
|
||||
- `document.cancelled`
|
||||
|
||||
## Create a webhook subscription
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
Then, navigate to the "Webhooks" tab, which takes you to the webhooks main page.
|
||||
|
||||

|
||||

|
||||
|
||||
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`.
|
||||
- Select the event(s) you want to subscribe to: `document.created`, `document.sent`, `document.opened`, `document.signed`, `document.completed`, `document.rejected`, `document.cancelled`.
|
||||
- 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.
|
||||
|
||||

|
||||

|
||||
|
||||
After you have filled in the required information, click on the "**Create Webhook**" button to save your subscription.
|
||||
|
||||
@@ -48,7 +49,22 @@ The screenshot below illustrates a newly created webhook subscription.
|
||||
|
||||

|
||||
|
||||
You can edit or delete your webhook subscriptions by clicking the "**Edit**" or "**Delete**" buttons next to the webhook.
|
||||
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.
|
||||
|
||||

|
||||
|
||||
You can go even further and check the execution details of each call by clicking on a specific webhook call.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
## Webhook fields
|
||||
|
||||
@@ -528,6 +544,116 @@ Example payload for the `document.rejected` event:
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
This opens a dialog where you can select the event type to test.
|
||||
|
||||

|
||||
|
||||
Choose the event you want to test and click "Send". You’ll 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.
|
||||
|
||||

|
||||
|
||||
This will send the webhook event to the webhook URL again.
|
||||
|
||||
## Availability
|
||||
|
||||
Webhooks are available to individual users and teams.
|
||||
Webhooks are available to teams only.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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',
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"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",
|
||||
"teams": "Teams",
|
||||
"-- Legal Overview": {
|
||||
"type": "separator",
|
||||
"title": "Legal Overview"
|
||||
},
|
||||
"fair-use": "Fair Use Policy",
|
||||
"licenses": "Licenses",
|
||||
"compliance": "Compliance"
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
@@ -0,0 +1,4 @@
|
||||
export default {
|
||||
'signature-levels': 'Signature Levels',
|
||||
'standards-and-regulations': 'Standards and Regulations',
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"signature-levels": "Signature Levels",
|
||||
"standards-and-regulations": "Standards and Regulations"
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Signature Levels
|
||||
description: Learn about the different signature levels for Documenso.
|
||||
---
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# Signature Levels
|
||||
@@ -26,20 +31,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
|
||||
|
||||
@@ -50,9 +55,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)
|
||||
|
||||
@@ -69,8 +74,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
|
||||
|
||||
@@ -85,8 +90,8 @@ 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,3 +1,8 @@
|
||||
---
|
||||
title: Standards and Regulations
|
||||
description: Learn about the different standards and regulations for Documenso.
|
||||
---
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
## 21 CFR Part 11
|
||||
@@ -19,13 +24,13 @@ device, and other FDA-regulated industries.
|
||||
- [x] User Access Management
|
||||
- [x] Quality Assurance Documentation
|
||||
|
||||
## SOC/ SOC II
|
||||
## SOC 2
|
||||
|
||||
<Callout type="warning" emoji="⏳">
|
||||
Status: [Planned](https://github.com/documenso/backlog/issues/24)
|
||||
<Callout type="info" emoji="✅">
|
||||
Status: [Compliant](https://documen.so/trust)
|
||||
</Callout>
|
||||
|
||||
SOC II is a framework for managing and auditing the security, availability, processing integrity, confidentiality,
|
||||
SOC 2 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).
|
||||
|
||||
@@ -34,9 +39,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
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
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',
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: AI Recipient & Field Detection
|
||||
description: Use Documenso’s AI helpers to detect recipients and fields in draft documents.
|
||||
---
|
||||
|
||||
# AI Recipient & Field Detection
|
||||
|
||||
Documenso can suggest recipients and place fields automatically using Google Vertex AI (Gemini). The feature is optional and only available when your organisation or team has **AI Features** enabled. Documents are processed securely and providers do not retain your data for training.
|
||||
|
||||
## Requirements
|
||||
|
||||
- AI Features must be enabled in **Document Preferences** for your organisation or team.
|
||||
- The envelope must be in **Draft** status.
|
||||
- Helpful rate limits are in place (up to 3 detection requests per minute per IP) to prevent abuse. If you see a “too many requests” message, wait a minute or two and try again.
|
||||
|
||||
### Enable AI features
|
||||
|
||||
1. **Organisation settings**:
|
||||
|
||||
Settings → Document Preferences → **AI Features** → Enabled.
|
||||
|
||||
_This applies to teams that inherit organisation defaults._
|
||||
|
||||
2. **Team settings**:
|
||||
|
||||
Team Settings → Document Preferences → **AI Features** → choose Enabled, Disabled, or Inherit.
|
||||
|
||||
## Detect recipients
|
||||
|
||||
Use this to identify who needs to sign or approve.
|
||||
|
||||
1. Open a draft document/template and go to the **Recipients** panel.
|
||||
2. Select the **sparkle** button to start detection. If AI is enabled, uploads launched from the dashboard will open the detector automatically.
|
||||
|
||||

|
||||
|
||||
3. Wait for progress to finish, then review the suggested recipients.
|
||||
4. Remove any incorrect entries, then **Add recipients** to apply them. Existing recipients and duplicates are preserved.
|
||||
|
||||
Notes:
|
||||
|
||||
- Detection is unavailable once an envelope is completed.
|
||||
- You can re-run detection if you update the document; each run counts toward the rate limit.
|
||||
|
||||
## Detect fields
|
||||
|
||||
Use this to auto-place fields on the pages of a draft.
|
||||
|
||||
1. Open the envelope editor and switch to the **Fields** tab.
|
||||
2. Select **Detect with AI**. Provide optional context (e.g., “Alice is the tenant, Bob is the landlord”) to improve recipient assignment.
|
||||
|
||||

|
||||

|
||||
|
||||
3. Watch the progress indicators; they update per page and total fields found.
|
||||
4. Review the summary and choose **Add fields** to place them in the editor.
|
||||
|
||||
Notes:
|
||||
|
||||
- Works only for draft envelopes and teams with AI features enabled.
|
||||
- Existing fields are masked during detection to avoid duplicates.
|
||||
- Fields are assigned to recipients based on nearby labels and your context message; you can edit them after adding.
|
||||
|
||||
## Best practices
|
||||
|
||||
- Keep labels near the intended fields (e.g., “Tenant signature”, “Buyer email”).
|
||||
- Provide short context when roles are ambiguous.
|
||||
- Always review suggestions before sending; AI assists but does not replace final checks.
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Default Document Recipients
|
||||
description: Learn how to set default recipients with various roles for your documents.
|
||||
---
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# Default Document Recipients
|
||||
|
||||
Documenso allows you to set default recipients for your documents. This is useful when you require specific recipients to be added to every document you send.
|
||||
|
||||
You can add default recipients with the same roles as the recipients you can add when sending a document:
|
||||
|
||||
- **Signer** - The recipient will be required to sign the document.
|
||||
- **Approver** - The recipient will be required to approve the document.
|
||||
- **Viewer** - The recipient will be required to view the document.
|
||||
- **CC** - The recipient will receive a copy of the document.
|
||||
|
||||
You can set default recipients at the organisation or team level.
|
||||
|
||||
### Organisation level
|
||||
|
||||
To set default recipients at the organisation level, navigate to the organisation settings page and click the "Document" tab under the "Preferences" section.
|
||||
|
||||
Then scroll down to the "Default Recipients" section and add the recipients you want to be included in every document you send.
|
||||
|
||||

|
||||
|
||||
The recipients are added with the "CC" role by default, but you can select a different role for each recipient.
|
||||
|
||||

|
||||
|
||||
### Team level
|
||||
|
||||
Setting the default recipients at the team level follows the same process as setting them at the organisation level.
|
||||
|
||||
<Callout type="info">
|
||||
Setting the default recipients at the team level will override organisation-level defaults.
|
||||
</Callout>
|
||||
|
||||
To set default recipients at the team level, navigate to the team settings page and click the "Document" tab under the "Preferences" section.
|
||||
|
||||
Then scroll down to the "Default Recipients" section. By default, the team will inherit the default recipients from the organisation. You can override these defaults by adding the recipients you want to be added to every document you send.
|
||||
|
||||

|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Preferences
|
||||
description: Learn how to manage your team's global preferences.
|
||||
---
|
||||
|
||||
import Image from 'next/image';
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# Document Preferences
|
||||
|
||||
Document preferences allow you to set the default settings when creating new documents and templates.
|
||||
|
||||
For example, you can set the default language for documents sent by the team, or set the allowed signatures types.
|
||||
|
||||
## Preferences
|
||||
|
||||
Document 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 **Document** tab under the **Preferences** section.
|
||||
|
||||

|
||||
|
||||
- **Document Visibility** - Set the default visibility of the documents created by team members. Learn more about [document visibility](/users/documents/document-visibility).
|
||||
- **Default Document Language** - This setting allows you to set the default language for the documents uploaded in the organisation. The default language is used as the default language in the email communications with the document recipients.
|
||||
- **Default Time Zone** - The timezone to use for date fields and signing the document.
|
||||
- **Default Date Format** - The date format to use for date fields and signing the document.
|
||||
- **Signature Settings** - Controls what signatures are allowed to be used when signing the documents.
|
||||
- **Sender Details** - Set whether the sender's name should be included in the emails sent by the team. See more below [sender details](/users/documents/document-preferences#sender-details).
|
||||
- **Include the Signing Certificate** - This setting controls whether the signing certificate should be included in the signed documents. If enabled, the signing certificate is included in the signed documents. If disabled, the signing certificate is not included in the signed documents. Regardless of this setting, the signing certificate is always available in the document's audit log page.
|
||||
|
||||
Document visibility, language and signature settings can be overriden on a per document basis.
|
||||
|
||||
### Sender Details
|
||||
|
||||
If the **Sender Details** setting is enabled, the emails sent by the team will include the sender's name. The email will say:
|
||||
|
||||
> "Example User" on behalf of "Example Team" has invited you to sign "document.pdf"
|
||||
|
||||
If the **Sender Details** setting is disabled, the emails sent by the team will not include the sender's name. The email will say:
|
||||
|
||||
> "Example Team" has invited you to sign "document.pdf"
|
||||
+11
-5
@@ -5,19 +5,25 @@ description: Learn how to control the visibility of your team documents.
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# Team's Document Visibility
|
||||
# Document Visibility
|
||||
|
||||
The default document visibility option allows you to control who can view and access the documents uploaded to your team account. The document visibility can be set to one of the following options:
|
||||
The default document visibility option allows you to control who can view and access the documents uploaded within a team.
|
||||
|
||||
This value can either be set in the [document preferences](/users/documents/document-preferences), or when you [create the document](/users/documents/send-document)
|
||||
|
||||
## Document Visibility Options
|
||||
|
||||
The document visibility can be set to one of the following options:
|
||||
|
||||
- **Everyone** - The document is visible to all team members.
|
||||
- **Managers and above** - The document is visible to team members with the role of _Manager or above_ and _Admin_.
|
||||
- **Admin only** - The document is only visible to the team's admins.
|
||||
|
||||

|
||||
The default document visibility is set to "_EVERYONE_" by default. You can change this setting by going to the [document preferences page](/users/documents/document-preferences) and selecting a different visibility option.
|
||||
|
||||
The default document visibility is set to "_EVERYONE_" by default. You can change this setting by going to the [team's general preferences page](/users/teams/preferences) and selecting a different visibility option.
|
||||

|
||||
|
||||
Here's how it works:
|
||||
## How it works
|
||||
|
||||
- If a user with the "_Member_" role creates a document and the default document visibility is set to "_Everyone_", the document's visibility is set to "_EVERYONE_".
|
||||
- The user can't change the visibility of the document in the document editor.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Email Preferences
|
||||
description: Learn how to set the email preferences for your team account.
|
||||
---
|
||||
|
||||
import Image from 'next/image';
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# Email Preferences
|
||||
|
||||
Email preferences allow you to set the default settings when emailing documents to your recipients.
|
||||
|
||||
## Preferences
|
||||
|
||||
Email 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 **Email** tab under the **Preferences** section.
|
||||
|
||||

|
||||
|
||||
- **Default Email** - Use a custom email address when sending documents to your recipients. See [email domains](/users/email-domains) for more information.
|
||||
- **Reply To** - The email address that will be used in the "Reply To" field in emails
|
||||
- **Email Settings** - Which emails to send to recipients during document signing
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user