Skip to main content
AI Generated Content
πŸ€– This documentation was generated with AI assistance. Please report any issues you find.

Creating an Addon

Addons are reusable Pikku packages β€” they contain functions, services, secrets, variables, credentials, and wirings, all publishable as npm packages that consumers can wire into their projects.

Scaffolding with the CLI​

The fastest way to create an addon:

npx pikku new addon my-integration

This generates a complete addon project with package.json, tsconfig, pikku.config.json, service factory, API service class, types, and a test harness.

Scaffold Options​

FlagDescription
--secretInclude a secret definition file (wireSecret)
--variableInclude a variable definition file (wireVariable)
--oauthInclude OAuth2 credential wiring, secret for app credentials, and OAuth2Client-based API service
--credential apikeyPer-user API key credential (wireCredential with type: 'wire')
--credential bearerPer-user bearer token credential
--credential oauth2Per-user OAuth2 credential (same as --oauth but using the credential system)
--openapi ./spec.yamlGenerate functions from an OpenAPI spec
--mcpAdd mcp: true to generated functions (expose as MCP tools)
--camelCaseConvert snake_case properties to camelCase in generated Zod schemas
--display-name "My Tool"Human-readable display name (defaults to PascalCase of name)
--description "..."Package description
--category GeneralCategory for the addon registry (default: General)
--dir ./packagesOutput directory (defaults to scaffold.addonDir or cwd)
--no-testSkip generating the test harness

Examples​

# Simple addon with a secret
npx pikku new addon sendgrid --secret

# OAuth2 integration (Google Sheets, Slack, etc.)
npx pikku new addon google-sheets --oauth

# Per-user API key credential
npx pikku new addon stripe --credential apikey

# Generate from an OpenAPI spec
npx pikku new addon github --openapi ./github-api.yaml --credential bearer

# Full options
npx pikku new addon hubspot \
--oauth \
--variable \
--mcp \
--openapi ./hubspot-openapi.yaml \
--camelCase \
--display-name "HubSpot CRM" \
--category "CRM"

Generated Structure​

my-integration/
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ index.ts # Export your functions
β”‚ β”œβ”€β”€ services.ts # Service factory (pikkuAddonServices)
β”‚ β”œβ”€β”€ my-integration-api.service.ts # API client class
β”‚ β”œβ”€β”€ my-integration.types.ts # Zod schemas for API types
β”‚ β”œβ”€β”€ my-integration.secret.ts # Secret definition (with --secret)
β”‚ β”œβ”€β”€ my-integration.variable.ts # Variable definition (with --variable)
β”‚ └── my-integration.credential.ts # Credential wiring (with --credential/--oauth)
β”œβ”€β”€ types/
β”‚ └── application-types.d.ts # Service type declarations
β”œβ”€β”€ test/ # Test harness (separate pikku project)
β”‚ β”œβ”€β”€ src/
β”‚ β”‚ β”œβ”€β”€ addons.ts # wireAddon call
β”‚ β”‚ β”œβ”€β”€ services.ts # Test services factory
β”‚ β”‚ β”œβ”€β”€ my-integration-tests.function.ts
β”‚ β”‚ └── my-integration.test.ts
β”‚ β”œβ”€β”€ pikku.config.json
β”‚ └── package.json
β”œβ”€β”€ .pikku/ # Generated by CLI
β”œβ”€β”€ pikku.config.json
β”œβ”€β”€ tsconfig.json
β”œβ”€β”€ package.json
└── versions.pikku.json # Contract version manifest

Manual Setup​

If you prefer to set up an addon by hand, the key pieces are:

Mark as Addon​

In pikku.config.json:

{
"tsconfig": "./tsconfig.json",
"srcDirectories": ["src", "types"],
"outDir": "./.pikku",
"addon": true
}

Or with metadata:

{
"addon": {
"displayName": "SendGrid",
"description": "SendGrid email integration",
"categories": ["Email"]
}
}

Define Secrets​

Declare what secrets your addon needs:

// sendgrid.secret.ts
import { wireSecret } from '@pikku/core/secret'
import { z } from 'zod'

export const sendgridSecretsSchema = z.object({
apiKey: z.string(),
})

wireSecret({
name: 'sendgrid',
displayName: 'SendGrid API Key',
description: 'API key for sending emails',
secretId: 'SENDGRID_API_KEY',
schema: sendgridSecretsSchema,
})

Define Credentials​

For per-user credentials or OAuth2:

// google-sheets.credential.ts
import { wireCredential } from '@pikku/core/credential'
import { z } from 'zod'

wireCredential({
name: 'googleSheets',
displayName: 'Google Sheets',
type: 'wire',
schema: z.object({
accessToken: z.string(),
refreshToken: z.string().optional(),
}),
oauth2: {
appCredentialSecretId: 'GOOGLE_OAUTH_APP',
tokenSecretId: 'GOOGLE_OAUTH_TOKENS',
authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
},
})

See Credentials for the full reference.

Create Services​

Use pikkuAddonServices to create your service factory. It receives the consumer's shared services (logger, secrets, variables):

// services.ts
import { SendgridService } from './sendgrid-api.service.js'
import { pikkuAddonServices } from '#pikku'

export const createSingletonServices = pikkuAddonServices(
async (config, { secrets }) => {
const apiKey = await secrets.getSecretJSON<string>('SENDGRID_API_KEY')
const sendgrid = new SendgridService(apiKey)
return { sendgrid }
}
)

For per-user credentials, use pikkuAddonWireServices instead β€” the credential is resolved per-request via the wire:

// services.ts
import { StripeService } from './stripe-api.service.js'
import { pikkuAddonWireServices } from '#pikku'

export const createWireServices = pikkuAddonWireServices(
async ({ variables }, wire) => {
const cred = await wire.getCredential<{ apiKey: string }>('stripe')
const stripe = new StripeService(cred)
return { stripe }
}
)

Write Functions​

Functions use your injected services like any other Pikku function:

import { pikkuSessionlessFunc } from '#pikku'
import { z } from 'zod'

export const mailSend = pikkuSessionlessFunc({
description: 'Sends an email through SendGrid',
input: z.object({ to: z.string(), subject: z.string(), body: z.string() }),
output: z.object({ success: z.boolean() }),
func: async ({ sendgrid }, data) => {
await sendgrid.request('POST', '/mail/send', { body: data })
return { success: true }
},
})

Export Functions​

Export all functions from src/index.ts:

export { mailSend } from './functions/mail/send.function.js'
export { listCreate } from './functions/lists/create.function.js'

OpenAPI Generation​

When you pass --openapi, the CLI parses the spec and generates:

  • Zod schemas for all request/response types
  • Function files for each API operation
  • Typed API service with correct endpoints
  • MCP tool metadata (with --mcp)
npx pikku new addon github --openapi ./github-openapi.yaml --camelCase --mcp

The --camelCase flag converts snake_case property names from the spec to camelCase in the generated Zod schemas. The generated contract hash is stored in pikku.config.json so you can detect when the upstream API changes.

Publishing​

Your package.json must export the .pikku/ directory:

{
"files": ["dist", ".pikku"],
"exports": {
".": {
"types": "./dist/src/index.d.ts",
"import": "./dist/src/index.js"
},
"./.pikku/*": "./dist/.pikku/*"
}
}

Build and publish:

npx pikku all && tsc && npm publish

Contract Versioning​

The scaffold generates a versions.pikku.json manifest. Use version commands to track breaking changes:

npx pikku versions check    # Validate contracts against manifest
npx pikku versions update # Update manifest with current hashes

Trigger Sources​

Addons can export trigger source functions that consumers wire to their own handlers:

import { pikkuTriggerFunc } from '#pikku'

export const onChanges = pikkuTriggerFunc<
{ table: string; events: ('INSERT' | 'UPDATE' | 'DELETE')[] },
{ event: string; data: any }
>({
title: 'Postgres Changes',
description: 'Triggers on row changes in a PostgreSQL table',
func: async ({ postgres }, { table, events }, { trigger }) => {
// Set up LISTEN/NOTIFY, call trigger.invoke() on changes
},
})

Consumers wire this to their own handler β€” see Consuming Addons.