> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://learning.postman.com/llms.txt. For full content including API reference and SDK examples, see https://learning.postman.com/llms-full.txt.

# Generate an SDK with custom code using the Postman CLI

This guide explains how to use the Postman CLI to generate an SDK from a Postman Collection, add custom code to the generated SDK, and regenerate the SDK while preserving your custom edits.

The SDK generator supports multiple programming languages, including TypeScript, Python, Java, Kotlin, C#, Go, PHP, Ruby, and Rust. The examples in this guide use generic syntax that can be adapted to any supported language.

Use cases for custom code include adding utility methods, custom logging, custom READMEs, or any additional logic that you want to maintain across SDK regenerations.

The [`postman sdk generate`](/docs/sdk-generator/sdk-cli/#postman-sdk-generate) command generates a client SDK from a Postman Collection or API specification. Change tracking is included by default and enables the CLI to detect your local edits and merge them with the newly generated code on subsequent runs. The CLI uploads your local edits as custom code before triggering a new build.

Custom code SDKs are generated with the same structure as regular SDKs, but with additional handling to preserve user edits:

* Customizations are applied after code formatting to prevent user-added custom code from causing the code formatter to fail, thereby allowing users to define their own formatting and SDK layout.

* Developers' edits to files are intended to persist, treating the local file system like a GitHub repository. Updates from the SDK generator are applied alongside user changes, and any conflicts are marked for users to resolve.

These changes don't impact the size of the SDK because the last build artifact is retrieved from the database.

Follow the steps below to generate an SDK with custom code preserved across regenerations.

## Step 1: Generate the initial SDK

The following command generates an SDK from a Postman Collection. Replace `<language>` with your preferred `--language` / `-l` value: `typescript`, `python`, `java`, `kotlin`, `csharp` (C#), `go`, `php`, `ruby`, or `rust`.

```bash
postman sdk generate 12345678-abcd-1234-1234-123456789abc -l <language>
```

This example uses TypeScript (`-l typescript`):

```bash
postman sdk generate 12345678-abcd-1234-1234-123456789abc -l typescript
```

The CLI output shows the progress of the build, including any warnings or issues encountered:

```
Identifying ID type (collection or specification)...
Identified as: collection
Starting SDK generation...
Collection ID: 12345678-abcd-1234-1234-123456789abc
Build type: Postman Cloud (updates stored SDK)
Language(s): <language>
Output: ./sdks
Creating SDK build...
✓ Build created successfully
Build ID: 7836
Status: RECEIVED
Languages: <language>

Watching build progress...
Status: IN_PROGRESS
Status: IN_PROGRESS
...
Status: SUCCESS

✓ Build completed successfully!
Duration: 11s
```

After the build completes, the CLI prints a build log summary listing any warnings or issues encountered:

```
Build Log Summary

1 issue(s) across 1 endpoint(s):

1) "Cloud platforms/New Request"
   WARNING: No URL was provided for the request field of this item
   in the postman collection. The SDK will not include this item.
   If you want to include this item, please provide a URL.

Notes: 4 note(s) (use --log-level info to expand)
```

<Note>
  Endpoints in the collection that do not have a URL are excluded from the generated SDK. To include them, add a URL to the request in the collection before regenerating.
</Note>

Once the build succeeds, the CLI downloads the generated SDK to the output directory:

```
Downloading SDKs...
✓ <language>: sdks/<language>
✓ All SDKs downloaded successfully!
Manifest written for <language>
```

The generated SDK is saved to `./sdks/<language>/` (for example, `./sdks/typescript/`) and includes the following structure:

* `.sdk-gen/` — Internal SDK generation metadata
* `documentation/` — Auto-generated API documentation
* `examples/` — Usage examples
* Dependencies — SDK dependency artifacts (for example, `node_modules/` for TypeScript or `requirements.txt` for Python)
* Build files — Build and utility scripts
* Source files — Generated source files, organized into service modules (for example, `services/payment/`, `services/users/`, `services/billing/`)
* Configuration files — Example environment configuration (for example, `.env.example`)

## Step 2: Add custom code to the SDK

After the initial generation, you can edit the generated SDK source files to add custom logic. These edits are preserved on subsequent regenerations when you rerun the SDK generation command.

The following examples use TypeScript-like pseudocode to illustrate where custom code can be added in a generated SDK. Adapt the syntax, type system, and idioms to your chosen language's conventions.

<AccordionGroup>
  <Accordion title="Example 1: Add custom logging methods" defaultOpen={false}>
    To add a custom logging method to a service class, use the following TypeScript-like example based on a `PaymentService` file such as `sdks/<language>/src/services/payment/payment-service.<ext>`, then adapt it to your target language:

    ```
    // Add a custom logging method to the class
    _customLog(input) {
      console.log(input);
    }
    ```

    You can then call this method from any service method within the class:

    ```
    setAddCreditCardForUserConfig(config) {
      this._customLog('hello world');
      this.addCreditCardForUserConfig = config;
      return this;
    }
    ```
  </Accordion>

  <Accordion title="Example 2: Customize the existing README for branding" defaultOpen={false}>
    You can also customize the auto-generated README file to add branding, company-specific information, or additional usage examples. The SDK generates a basic README at `sdks/<language>/README.md`, which you can modify.

    Open the `README` file and add your custom content. For example, you can add a company logo, installation instructions, company-specific authentication details, enterprise support information, and relevant links.

    These customizations will be preserved when you regenerate the SDK.
  </Accordion>

  <Accordion title="Example 3: Add custom data transformation utilities" defaultOpen={false}>
    You can add utility methods to transform API responses or prepare request data. This example shows TypeScript modifications to `sdks/<language>/src/services/payment/payment-service.<ext>`:

    ```typescript
    // Define custom types for transformed data
    interface PaymentSummary {
      id: string;
      amount: string;
      status: string;
      createdAt: Date;
      isCompleted: boolean;
      displayName: string;
    }

    interface RawPayment {
      payment_id: string;
      amount_units: number;
      status: string;
      created_timestamp: string | number | Date;
    }

    class PaymentService {
      transformPaymentData(rawPayment: RawPayment): PaymentSummary {
        return {
          id: rawPayment.payment_id,
          amount: this.formatCurrency(rawPayment.amount_units),
          status: rawPayment.status.toUpperCase(),
          createdAt: new Date(rawPayment.created_timestamp),
          // Add custom calculated fields
          isCompleted: rawPayment.status === 'completed',
          displayName: `Payment ${rawPayment.payment_id.slice(-8)}`
        };
      }

      formatCurrency(units: number): string {
        return new Intl.NumberFormat('en-US', {
          style: 'currency',
          currency: 'USD'
        }).format(units / 100);
      }

      async getPayment(params: { paymentId: string }): Promise<RawPayment> {
        return {} as RawPayment;
      }

      async getPaymentWithFormatting(paymentId: string): Promise<PaymentSummary> {
        const rawPayment = await this.getPayment({ paymentId });
        return this.transformPaymentData(rawPayment);
      }
    }
    ```

    This adds data transformation capabilities that format API responses into more user-friendly structures, including currency formatting and computed fields.
  </Accordion>

  <Accordion title="Example 4: Add custom validation and business logic" defaultOpen={false}>
    You can add business rule validation and custom logic to service methods. This TypeScript example shows modifications to `sdks/typescript/src/services/payment/payment-service.ts`:

    ```typescript
    // Define custom types for validation
    interface ValidationResult {
      isValid: boolean;
      errors: string[];
    }

    interface CreatePaymentRequest {
      amount: number; // Amount in smallest currency unit (e.g., cents for USD)
      currency: string;
      customerId: string;
      // Additional payment fields as needed
    }

    export class PaymentService extends BaseService {
      // Add custom validation utilities
      validatePaymentAmount(amount: number, currency: string = 'USD'): ValidationResult {
        const errors: string[] = [];

        if (amount <= 0) {
          errors.push('Payment amount must be greater than zero');
        }

        if (currency === 'USD' && amount >= 1000000) {
          errors.push('USD payments cannot exceed $10,000.00');
        }

        // Check if amount is a valid integer (since we're working in smallest currency units)
        if (!Number.isInteger(amount)) {
          errors.push('Payment amount must be in valid currency units (whole numbers)');
        }

        return {
          isValid: errors.length === 0,
          errors
        };
      }

      // Enhanced payment method with validation
      async createValidatedPayment(paymentData: CreatePaymentRequest): Promise<any> {
        // Apply custom business rules
        const validation = this.validatePaymentAmount(paymentData.amount, paymentData.currency);

        if (!validation.isValid) {
          throw new Error(`Payment validation failed: ${validation.errors.join(', ')}`);
        }

        // Add custom audit logging
        console.log(`Creating payment: ${paymentData.amount} ${paymentData.currency} for customer ${paymentData.customerId.slice(-4)}`);

        // Call the original SDK method
        return this.createPayment(paymentData);
      }
    }
    ```

    This adds custom business rule validation and audit logging that wraps the generated SDK methods with additional logic specific to your application needs.

    <Note>
      When implementing audit logging, be careful to avoid logging sensitive information such as full customer IDs, payment tokens, personal data, or API keys. In production environments, consider redacting or omitting these fields entirely, or use structured logging with appropriate data classification.
    </Note>
  </Accordion>
</AccordionGroup>

## Step 3: Regenerate the SDK with custom code preserved

When you run `postman sdk generate`, the CLI automatically detects your local edits and handles the merge:

```
User edits detected for: <language>
Uploading modified files for 3-way merge...
  <language>: 1 modified file(s)
    + src/services/payment/payment-service.<ext>
Custom code uploaded: custom-code/f9e967ad-d27d-4cfe-8d1f-f516b1dec40e.zip
Creating SDK build...
✓ Build created successfully
Build ID: 7837
Status: RECEIVED
Languages: <language>

Watching build progress...
Status: IN_PROGRESS
...
Status: SUCCESS

✓ Build completed successfully!
Duration: 12s
```

If the SDK output directory already exists, the CLI will prompt before overwriting:

```
The following SDK directories already exist: <language>
✓ Do you want to overwrite them? … yes
```

After confirming, the SDK is downloaded again with your custom code merged in:

```
Downloading SDKs...
✓ <language>: sdks/<language>
✓ All SDKs downloaded successfully!
Manifest written for <language>
```

## Step 4: Resolve merge conflicts (if any)

When both you and the generator change the same lines in a file, the CLI handles the conflict based on the `conflictStrategy` setting. With the default `mark` strategy, the CLI adds conflict markers to the file, allowing you to manually resolve the conflicts. The conflict markers look like this:

```typescript
<<<<<<< user
// Your version of the code
export function getPet(id: string): Pet {
    return this.cache.get(id) ?? this.fetchPet(id);
}
=======
// Generator's version of the code
export function getPet(id: string): Promise<Pet> {
    return this.httpClient.get(`/pets/${id}`);
}
>>>>>>> generated
```

In this example, the user modified the `getPet` method to first check a local cache before making an API call, while the generator updated the method to return a Promise that resolves with the API response. The conflict markers indicate that both versions changed the same lines of code, and manual resolution is needed to determine how to merge these changes.

The CLI reports files with conflicts after download:

```
Warning: Merge completed with 1 conflict(s) in typescript:
  - src/services/pets/pets-service.ts
Resolve conflicts manually (look for <<<<<<< user / >>>>>>> generated markers).
```

You can then open the file, review the conflicting changes, and edit the code to resolve the conflict. After resolving, save the file and continue using the SDK as normal.

## How change detection works

Each generated SDK has a `.manifest.json` in its root. When change tracking is enabled, this file includes a `fileHashes` field — a map of every generated file to its SHA-256 hash:

```json
{
  "sdkVersion": "1.0.4",
  "date": "2026-04-21T...",
  "config": { ... },
  "files": ["src/index.ts", "package.json", ...],
  "fileHashes": {
    "src/index.ts": "a1b2c3d4...",
    "src/services/pets/pets-service.ts": "e5f6a7b8...",
    "package.json": "c9d0e1f2..."
  }
}
```

On the next regeneration, the CLI does the following:

1. Reads `fileHashes` from `.manifest.json`.

2. Computes the current hash of each file on disk.

3. Compares the files. Any mismatch means the file was edited.

4. Scans for user-added files not in the manifest.

5. Checks for files in the manifest that no longer exist on disk (user-deleted).

Only the changed files are uploaded. The generator uses the previous build's output as the merge base for a three-way merge.

When you regenerate, the CLI uploads a zip file with the following structure:

```
customizations/{language}/
    src/services/pets/pets-service.ts     # Modified file
added/{language}/
    src/utils/my-helper.ts                # New file you created
meta/{language}/
    .manifest.json                        # Manifest with file hashes
    deleted.json                          # ["src/old-file.ts"] — files you deleted
```

Only the changed or added files and metadata are uploaded and not the entire SDK.

The following directories are excluded from change detection and hashing to avoid tracking dependencies and build output.

* Dependencies and packages:
  * `node_modules`
  * `vendor`
  * `.venv`

* Build outputs:
  * `dist`
  * `build`
  * `target`
  * `bin`
  * `obj`

* Build tools and cache:
  * `__pycache__`
  * `.gradle`

* Internal and version control:
  * `.sdk-gen`
  * `.git`

## How change tracking works

The [`customCode`](/docs/sdk-generator/configure/customization-options/) section of the SDK configuration controls change-tracking and merge behavior. The CLI terminal output shows the effective configuration being applied. This example corresponds to running with `--conflict-strategy mark`, which adds conflict markers to files when both the user and generator modify the same lines:

```json
{
  "sdk": {
    "customCode": {
      "trackChanges": true,
      "conflictStrategy": "mark",
      "noMerge": false
    }
  }
}
```

The following is priority order for these settings, from highest to lowest:

1. CLI flags (for example, `trackChanges` is set to `true` and `conflictStrategy` defaults to `"mark"`).
   * If you run the command with `--no-track-changes`, change tracking will be turned off for that run. This means the generator will perform a clean regeneration that overwrites all files, and no merge will be attempted to preserve user edits.
   * If you run the command with `--conflict-strategy ours`, the conflict strategy will be set to `"ours"` for that run, and the generator will keep your changes and discard the generator's changes.
2. `customCode` values set in the config file.
   * Setting `"trackChanges": false` is equivalent to `--no-track-changes`.
   * Setting `"noMerge": true` is equivalent to `--no-merge`.

This means CLI flags always override values in the config file.

### Limitations

Change tracking requires the generator to support writing `fileHashes` to `.manifest.json`. This is enabled by default on the server side.

The three-way merge requires a previous build artifact to exist in the cloud. If the previous build was cleaned up, customizations are applied as-is (no merge, just overlay).

Binary files are skipped during conflict scanning.