Custom Code

While Packable automatically generates complete SDKs from your OpenAPI specification, you may occasionally need to add custom functionality that goes beyond what's specified in your API. Custom code allows you to extend your generated SDKs with additional features.

Use custom code with caution. While it provides flexibility, it can lead to merge conflicts during automated SDK updates. Always prefer adding functionality to your OpenAPI specification when possible.

How Custom Code Works

Custom code is manually added directly to your SDK repository's main (or default) branch. When Packable generates a new version of your SDK, it will:

  1. Generate the updated SDK code
  2. Attempt to merge it with your existing custom code
  3. Preserve your custom additions wherever possible

Adding Custom Code

To add custom code to your SDK:

  1. Clone your SDK repository locally or make changes directly on GitHub
  2. Create new files with your custom functionality (recommended approach)
  3. Commit and push to the main/default branch
  4. Document your changes so your team knows what's custom vs. generated

Best Practice: Create New Files

The safest way to add custom code is to create entirely new files:

typescript
// custom/helpers.ts - New file with custom utilities export function customFormatting(data: any) { // Your custom logic here return transformedData; }

Then export them from your SDK's main entry point or as a separate import.

Avoid: Modifying Generated Files

Editing generated files directly can cause merge conflicts during updates:

typescript
// ❌ AVOID: Editing generated files // src/Client.ts (auto-generated) export class ExampleClient { // Generated code... // Your custom method - WILL CONFLICT on updates customMethod() { // ... } }

Understanding Merge Conflicts

When Packable regenerates your SDK, it may encounter conflicts if:

  • You've modified lines in generated files
  • The API specification changed in ways that affect the same code
  • File structure changes conflict with your additions

When Conflicts Occur

If a merge conflict occurs during automated SDK generation:

  1. Packable will create a PR with conflict markers
  2. You'll receive a notification about the conflict
  3. You'll need to manually resolve the conflicts
  4. Review and merge the PR once resolved

Example conflict:

typescript
<<<<<<< HEAD (Your custom code) export function customMethod() { return "custom"; } ======= export function generatedMethod() { return "generated"; } >>>>>>> Packable Update

Safe Custom Code Patterns

Pattern 1: Extension Classes

Extend the generated client with custom methods:

typescript
// custom/ExtendedClient.ts import { ExampleClient } from '../Client'; export class ExtendedClient extends ExampleClient { async batchGet(ids: string[]) { return Promise.all(ids.map(id => this.getById(id))); } }

Pattern 2: Utility Functions

Create separate utility modules:

typescript
// custom/utils.ts export function validateInput(data: any): boolean { // Custom validation return true; } export function formatResponse(data: any) { // Custom formatting return formatted; }

Pattern 3: Factory Functions

Wrap client initialization:

typescript
// custom/factory.ts import { ExampleClient } from '../Client'; export function createClient(apiKey: string) { const client = new ExampleClient({ apiKey }); // Add custom setup return client; }

Organizing Custom Code

Keep custom code in a separate directory:

directory-structure
my-sdk/ ├── src/ │ ├── Client.ts # Generated │ ├── api/ # Generated │ ├── core/ # Generated │ ├── custom/ # Your code │ │ ├── ExtendedClient.ts │ │ ├── utils.ts │ │ └── index.ts │ └── index.ts └── tests/ └── custom/

Documenting Custom Code

Mark custom code with comments:

typescript
/** * CUSTOM CODE - Added 2025-01-15 * Batch operations not available in the generated API. */ export function batchOperation() { // ... }

Testing Custom Code

Write tests for custom functionality:

typescript
// tests/custom/extensions.test.ts import { ExtendedClient } from '../../src/custom/ExtendedClient'; test('batch operations work correctly', async () => { const client = new ExtendedClient({ apiKey: 'test' }); const results = await client.batchGet(['1', '2', '3']); expect(results).toHaveLength(3); });

When to Use Custom Code

Good use cases:

  • Client-side utilities (formatting, validation)
  • Caching or retry logic
  • Convenience methods for common patterns
  • Platform-specific integrations

When NOT to Use Custom Code

Prefer these alternatives:

  • Extend your OpenAPI spec instead of adding custom endpoints
  • Use SDK configuration for customizable behavior
  • Create a wrapper library for major extensions

If you're adding significant custom code, consider whether it belongs in your API spec or a separate wrapper library.

Troubleshooting

Merge conflicts?

  • Review the PR conflict markers
  • Check the SDK changelog
  • Resolve conflicts manually
  • Contact support if needed

Next Steps