Skip to content

Authoring Extensions

Extensions are installed code packages that the GCS-SSC host discovers at startup. Author extensions when a business process needs local behaviour without changing the core agreement, proponent, program, or admin screens for every deployment.

Use this page for developer implementation. Operators should use Concepts: Extensions and the agency/stream extension tabs.

Authoring Contract

Import SDK contracts from @gcs-ssc/extensions, server helpers from @gcs-ssc/extensions/server, UI wrappers from @gcs-ssc/extensions/ui, and test helpers from @gcs-ssc/extensions/testing. Do not import host internals such as ~~/server, ~~/shared, ~/, or #imports for extension-owned contracts.

ContractUse
defineGcsExtensionDefines the extension manifest.
GCS_EXTENSION_SDK_VERSIONCurrent host SDK version for manifest compatibility.
GcsExtensionJsonConfigStream configuration JSON shape.
ExtensionEntityTabContextProps for entity tab components.
defineGcsExtensionMigrationWraps Kysely migrations owned by the extension.
defineGcsExtensionRouteHandlerWraps extension server handlers with stable route context.
registerGcsExtensionCreateOperationHandlerHooks core commitment/payment create operations.
createGcsExtensionUserErrorRaises localized, user-facing extension errors from server code.
KV helpersStore extension-owned non-secret state by owner type, owner id, and key.
Encrypted secret helpersStore sensitive values in host-managed encrypted secret storage.
UI wrappers and clientsRender host UI safely and call extension or host APIs from extension components.

Package Shape

A typical extension has:

File or folderPurpose
extension.config.tsRequired manifest exported with defineGcsExtension.
components/Vue components for admin config, runtime slots, entity tabs, create actions, or calculators.
server/api/Extension server handlers exposed through the host dispatcher.
server/migrations/Extension-owned migrations.
server/plugins/Nitro plugins for create-operation hooks.
server/runtime.tsOptional runtime resolver that can decide slot enablement dynamically.
client/ or assets folderStatic files mounted by the extension manifest.
i18n/Optional English/French message files.
tests/Unit tests for config parsing, route helpers, UI, and business logic.

Manifest Fields

ts
import { defineGcsExtension } from '@gcs-ssc/extensions'

export default defineGcsExtension({
  key: 'gcs-example',
  sdkVersion: '^0.1.0',
  requiredHostCapabilities: [
    'stream-config-modal',
    'server-handlers',
    'server-handler-rbac',
    'extension-api-client'
  ],
  name: { en: 'Example', fr: 'Exemple' },
  description: {
    en: 'Adds local behaviour.',
    fr: 'Ajoute un comportement local.'
  }
})
FieldRule
keyStable extension key. Use lowercase kebab-case and never change it after data exists.
sdkVersionRequired compatible SDK version range, such as ^0.1.0. The host rejects unsupported versions.
requiredHostCapabilitiesRequired list of host capabilities used by the manifest or code. The host rejects missing or unknown capabilities.
nameRequired bilingual display name.
descriptionOptional bilingual description for admin screens.
adminAgency config, stream config modal, or stream config page components.
clientRuntime slots, entity tabs, create actions, and payment calculators.
css, i18n, assetsOptional client styling, localized messages, and static assets.
serverHandlersAuthenticated extension routes exposed through the host dispatcher.
migrationsKysely migrations run when the extension is enabled or migrations are requested.
runtimeOptional resolver for slot enablement and config resolution.
nitroPluginOptional server plugin for hooks such as create-operation interception.

The host validates component, handler, asset, and migration paths so they stay inside the extension package.

Supported Capabilities

Declare every capability the extension depends on:

CapabilityUse
agency-configAgency admin configuration component.
stream-config-modalStream configuration rendered in the stream Extensions modal.
stream-config-pageFull-page stream configuration route through admin.streamConfigPage.
entity-tabsAgreement, proponent, claim, or monitor tabs.
textarea-slotsRuntime slot components in supported host page locations.
create-actionsExtension create actions for agreement commitments or payments.
payment-amount-calculatorsPayment amount calculator components.
server-handlersExtension API routes under /api/extensions/{extensionKey}.
server-handler-rbacHost-resolved RBAC/entity context for server handlers.
migrationsExtension-owned database migrations.
runtime-resolutionExtension runtime resolver for slot availability/config.
public-assetsStatic assets mounted by the host.
extension-uiHost-provided UI wrappers and runtime components.
extension-api-clientuseExtensionApi or extension API client helpers.
host-api-clientuseHostApi or stable host API client helpers.
extension-kvExtension key-value helpers for non-secret JSON state.
extension-secretsEncrypted extension secret helpers.
extension-create-operation-hooksCreate-operation Nitro hooks.
extension-lifecycle-hooksLifecycle/create hooks exposed through extension integration.

Stream Configuration

Use admin.agency when the extension needs agency-wide non-secret settings:

ts
admin: {
  agency: {
    path: './components/ExampleAgencyConfig.vue'
  }
}

Agency config components receive the current JSON config with v-model, plus extension and agencyId props. Store sensitive values through extension server handlers and encrypted secret helpers instead of putting them in agency config.

Use admin.streamConfig when the extension needs a modal-based stream configuration component:

ts
admin: {
  streamConfig: {
    path: './components/ExampleStreamConfig.vue'
  }
}

Use admin.streamConfigPage when the configuration needs a dedicated full page:

ts
admin: {
  streamConfigPage: {
    path: './components/ExampleStreamConfigPage.vue'
  }
}

Stream config components receive the current JSON config with v-model and stream context props:

vue
<script setup lang="ts">
import type { GcsExtensionJsonConfig } from '@gcs-ssc/extensions'
import type { GcsStreamConfigComponentProps } from '@gcs-ssc/extensions/ui'

defineProps<GcsStreamConfigComponentProps>()

const config = defineModel<GcsExtensionJsonConfig>({ required: true })
</script>

Full-page components also receive hostLayout: true and can use the page space for complex setup. Modal components should keep their layout compact.

RuleBehaviour
Config must be JSON-safeStore primitives, arrays, and objects only.
Config is not secret storageStore references to credentials, not credential values. Use encrypted secret helpers for secrets.
Agency enablement comes firstStream config is unavailable until the extension is enabled for the agency.
Components must tolerate optional IDsOlder or non-page contexts may omit transferPaymentId or agencyId.
Validate before saveReject incomplete combinations in the component or server-side stream config validation.
Keep config versionableAdd explicit version fields when config shape may change.

Runtime Slots

Slots render extension components inside existing host pages. Supported slot names are:

SlotHost area
textarea.afterGeneric text-area after-slot.
agreement.descriptions.afterAgreement English/French description area.
agreement.profile.classification.fieldsAgreement classification section.
agreement.profile.profile.fieldsAgreement profile section.
agreement.profile.risk-management.fieldsAgreement risk management section.
agreement.profile.sections.afterAfter agreement profile sections.
proponent.descriptions.afterProponent English/French description area.
ts
client: {
  slots: [
    {
      slot: 'agreement.profile.risk-management.fields',
      path: './components/AgreementRiskFields.vue'
    }
  ]
}

Slot components should be visually quiet and should not duplicate host-owned fields. When a slot writes extension-owned data, store it under the extension key so multiple extensions cannot overwrite each other.

Entity Tabs

Entity tabs can target agreement, proponent, claim, or monitor.

ts
client: {
  tabs: [
    {
      target: 'agreement',
      id: 'risk-notes',
      label: { en: 'Risk notes', fr: 'Notes de risque' },
      icon: 'i-lucide-database',
      path: './components/AgreementRiskNotesTab.vue',
      rbac: { subject: 'agreement', action: 'update' }
    }
  ]
}

Tab components receive:

PropContents
extensionKeyExtension key from the manifest.
contextTarget, agency, stream/agreement/proponent/claim/monitor ids, owner type, owner id, scope, and RBAC requirement.
configResolved stream or agency extension config.
rbacThe tab's declared RBAC requirement.
RuleBehaviour
Tab ids are unique per targetUse lowercase kebab-case ids.
Tabs require enablementAgreement, claim, and monitor tabs require agency and stream enablement; proponent tabs require agency enablement.
Tabs require RBACThe host checks the declared subject/action before rendering.
Proponents without lead agency do not show tabsProponent tabs resolve enablement through the lead agency.

Server Handlers

Use serverHandlers for authenticated extension endpoints:

ts
serverHandlers: [
  {
    route: '/agreements/[agreementId]/risk-notes',
    method: 'post',
    path: './server/api/risk-notes.post.ts',
    rbac: {
      subject: 'agreement',
      action: 'update',
      entity: {
        target: 'agreement',
        param: 'agreementId'
      }
    }
  }
]

Handler files should use defineGcsExtensionRouteHandler:

ts
import { defineGcsExtensionRouteHandler } from '@gcs-ssc/extensions/server'

export default defineGcsExtensionRouteHandler(async ({ db, params, config, entity, readBody }) => {
  const body = await readBody<{ note?: string }>()
  return { ok: true, agreementId: params.agreementId, config, entity, body, dbAvailable: Boolean(db) }
})

The stable route context contains db, params, auth, config, entity, stream, agency, authorizedScope, readBody, and getHeader. context.event remains available as an escape hatch, but normal handlers should not read host H3 internals directly.

RuleBehaviour
Declare RBAC for entity dataThe host resolves the entity from the route param, checks extension enablement, passes config/context, and enforces the declared subject/action.
Keep route params explicitThe entity.param, stream.param, or agency.param value must match a route param name.
Use auth: "manual" only deliberatelyManual handlers must perform their own domain authorization; they cannot combine auth: "manual" with rbac.
Throw GcsExtensionUserError for user-facing failuresUse localized extension messages so the UI can translate them.
Validate all inputExtension handlers are responsible for request validation.
Do not bypass host ownershipAlways resolve agreement, proponent, claim, monitor, stream, and agency ownership before writing when the host has not already done so.

UI Runtime

@gcs-ssc/extensions/ui exposes host-provided wrappers and composables. Use these instead of importing Nuxt UI or host Common* components directly.

Common exports include ExtensionButton, ExtensionFormField, ExtensionInput, ExtensionSelect, ExtensionTable, ExtensionResourceLayoutCard, ExtensionSection, ExtensionSaveButton, ExtensionStatusBadge, useExtensionI18n, useExtensionToast, useExtensionFetch, useExtensionApi, useHostApi, and useExtensionGroupedTableExpansion.

Use useExtensionApi(extensionKey) for extension-owned routes under /api/extensions/{extensionKey}. Use useHostApi() only for stable host API routes and declare host-api-client when doing so.

Create Actions

Extensions can add or replace create actions for:

OperationHost surface
agreement.commitments.createAgreement Commitments tab.
agreement.payments.createAgreement Payments tab.
ts
client: {
  createActions: [
    {
      operation: 'agreement.payments.create',
      id: 'generate-payments',
      mode: 'replace',
      label: { en: 'Generate payments', fr: 'Generer les paiements' },
      icon: 'i-lucide-wand-sparkles',
      path: './components/GeneratePaymentsAction.vue',
      rbac: { subject: 'agreement', action: 'update' }
    }
  ]
}
ModeBehaviour
appendKeeps the host Add button and adds the extension action beside it.
replaceHides the host Add button when exactly one enabled replacement exists.

If more than one enabled extension replaces the same operation, the host blocks the action until configuration is fixed. Create action components receive the operation, context, config, label, icon, RBAC requirement, and an onCreated callback. Call onCreated() after successful creation so the host refreshes the table.

Server-side create hooks belong in a Nitro plugin:

ts
import { registerGcsExtensionCreateOperationHandler } from '@gcs-ssc/extensions/server'

export default defineNitroPlugin(nitroApp => {
  registerGcsExtensionCreateOperationHandler(
    'gcs-example',
    'agreement.payments.create',
    async context => ({ status: 'continue' }),
    nitroApp
  )
})
Hook resultBehaviour
continue or no resultCore creation continues.
handledExtension supplies the response and core creation stops.

When a create hook blocks a user-correctable action, throw createGcsExtensionUserError with bilingual message and details values. The host resolves those messages using the request language and returns them through the normal API error shape.

Payment Amount Calculators

Payment calculators can provide a suggested amount, ceiling amount, currency, explanation details, loading state, and extension data for agreement.payments.create. Only one enabled calculator can apply to the payment creation surface at a time.

RuleBehaviour
Calculator must match the operationThe current host supports agreement payment creation.
Calculator must be unique for the operationConflicting enabled calculators block the host form.
Ceiling is enforced in the formThe user cannot save an amount above the calculator ceiling.
Server validation is still requiredRecheck generated amounts before writing records.

Migrations, KV, And Secrets

ts
import {
  defineGcsExtensionMigration,
  setEncryptedExtensionSecret,
  getEncryptedExtensionSecret,
  deleteEncryptedExtensionSecret
} from '@gcs-ssc/extensions/server'
StorageUse
Migrations are extension-ownedUse the extensions schema and extension-specific table names.
Migration paths are listed in the manifestThe host runs listed migrations for enabled extensions.
Standard package imports are allowedMigration files can import runtime dependencies such as kysely; the host resolves them from the application install.
Migration history is per extensionEach extension uses its own migration history and lock tables, so pending migrations are tracked independently.
KV helpersStore simple non-secret JSON state by owner type, owner id, and key. KV entries are soft-deleted.
Encrypted secret helpersStore sensitive JSON values such as private keys, API tokens, refresh tokens, signing secrets, or external-service credentials.
Prefer explicit tables for complex workflowsUse migrations when the extension needs reporting, relationships, workflow states, or large records.

Encrypted secrets use the extensions.secret_entry table and AES-256-GCM. Values are bound to extension key, owner type, owner id, secret key, and key version. Metadata can store non-sensitive listing fields such as a label or masked suffix.

The host root encryption key is GCS_EXTENSION_SECRETS_KEY, a base64-encoded 32-byte deployment secret. Do not store it in extension config, KV, source control, seed data for real environments, or browser-visible runtime config.

Encrypted secret helpers are exposed from @gcs-ssc/extensions/server: setEncryptedExtensionSecret, getEncryptedExtensionSecret, and deleteEncryptedExtensionSecret.

Assets And I18n

FeatureManifest fieldGuidance
Static extension assetsassetsMount only files needed at runtime; choose a unique baseURL.
Package assetsassets.package and packagePathUseful for model files or bundled third-party assets.
Bilingual messagesi18nProvide English and French message files for UI labels and errors.
CSScssKeep styles scoped and avoid overriding host design tokens globally.

Testing Checklist

Use installExtensionTestUiRuntime from @gcs-ssc/extensions/testing for standalone component tests that need host UI wrappers.

TestExpected result
Manifest importextension.config.ts imports and validates without host internals.
Capability declarationsEvery used host feature is listed in requiredHostCapabilities.
Agency enablementExtension appears on the agency Extensions tab and migrations run.
Stream configModal or full-page config saves valid JSON and rejects invalid combinations.
Runtime slots/tabsComponents render only when agency/stream enablement and RBAC allow them.
Server handlersOwnership, enablement, RBAC or manual authorization, and validation are enforced.
API clientsuseExtensionApi and useHostApi build expected paths and handle errors.
Secret handlingSecrets are encrypted/decrypted server-side and never returned to browser config.
Create action conflictsDuplicate replacement actions are detected.
Payment calculator conflictsDuplicate calculators are detected.
Bilingual UIEnglish and French labels, errors, and tab names are present.
Soft deletionExtension-owned deletes preserve historical data where required.