CDS pluginCDS plugintypeInstalls into a CAP project and extends the runtime. CAP loads it through cds.requires or the plugin hook.See every one →What this type is →independently maintainedindependently maintainedoriginPublished by one person or a small company outside SAP. Says nothing about quality; the readings say that.See every one →

cap-handler-framework

Convention-based handler layer for CAP: methods named beforeCreate, onRead or afterUpdate are mapped automatically, with the draft lifecycle given its own explicit hooks rather than folded into the active-entity ones.

NEW, PATCH, EDIT, SAVE and DISCARD get separate hooks from the active entity events, which is the distinction hand-written handlers most often collapse. Bound and unbound operations are named apart, several services are supported in one project, and a CDS plugin regenerates handlers/index.ts on cds watch without a reload loop.

Facts

Latestv1.2.2ReleasedLast pushLicenceMIT

Where it sits

Lives in
Provides
Draft handlingCAPDraft handlingcapabilityLetting a user edit a record over several requests before it becomes real — saving unfinished changes, locking, and activating a completed draft.TypeScript patternsCAPTypeScript patternscapabilityHow an application is arranged once it is written in TypeScript — where handlers live, what registers them, and how business logic is kept apart from persistence. Distinct from typing, which is about the model being visible to the compiler; this is about the shape of the code around it.
Flavors
Node.jsfrom its runtime
Requires
@sap/cds^7.0.0 || ^8.0.0 || ^9.0.0@sap/cdsartifactThe peer range, as published.

Who is behind it

Patrick frisonPatrick frisonPersonMaintains this
In the atlas: maintains 1, contributes to 1
@Mubuntu
maintainer

Before choosing this

Conditions

LicenceSAP Developer License Agreement 3.2 CAP§ 1, 2, 3A cds-plugin is used with SAP CAP materials, or an artifact is explicitly identified as a CAP plugin or application.
  • § 1 License. Productive customer applications are limited to SAP BTP or another SAP-licensed platform, and mass extraction to a non-SAP product is restricted except where solely required for SAP interoperability.
  • § 2 Intellectual Property. The agreement retains SAP's rights in the covered materials and limits making APIs, tools or software available to third parties, while preserving ownership of customer applications subject to SAP's underlying rights.
  • § 3 Artificial Intelligence Training. The agreement prohibits use of the covered materials and customer applications for training artificial-intelligence models or systems.
SAP Developer License Agreement 3.2 CAP →

What it declares

Every artifact

Type
CDS plugin
Origin
Independently maintained
Runtime
Node.js

As a CDS plugin

Declared for
^7.0.0 || ^8.0.0 || ^9.0.0
Type declarations
declared
Distributed through
npm

For typeScript patterns

Supports
Dependency injectionDraft events
How handlers bind
method names
property statedstated nonot said

Tasks this contributes to2Beta

Handle a draft's own events apart from the active entity'sA draft-enabled entity's own events — NEW, PATCH, EDIT, SAVE and DISCARD — are handled in places of their own in a CAP service written in TypeScript, apart from the CREATE, UPDATE and DELETE handlers of the active entity.Node.jsCoversseparate draft hooks
What it does

Gives the draft events hooks of their own, named apart from the active-entity ones, and its README states the separation as the point: explicit hooks for NEW, PATCH, EDIT, SAVE and DISCARD, kept away from the active entity's hooks.

Remaining work

A handler class states that it handles drafts before the draft hooks are mapped, and what each hook does is the application's code. Which hook registers on the drafts entity and which on the active one follows the runtime's own rule.

All options on the TypeScript patterns page →
Split one CAP service implementation into a class per entityThe custom logic of a CAP service written in TypeScript sits in one class per entity rather than in a single service implementation file, and each class's methods still reach the framework as the before, on and after handlers for that entity.Node.jsCovershandler registration from method names
What it does

A handler class is matched by the names of its methods, beforeCreate, onRead, afterUpdate, with nothing annotated. The CDS plugin generates the handlers/index.ts that the service's registerHandlers call reads, so a new handler file is picked up without an edit elsewhere.

Remaining work

The service implementation still calls registerHandlers in its init(), and the entity a class belongs to is returned by a method on the class rather than declared to the framework.

All options on the TypeScript patterns page →

Connections2

Install

npm add cap-handler-framework

Derived from the package name. No recipe recorded; the README has the configuration.

README

quoted from Mubuntu/cap-handler-framework under MIT, read
Mubuntu/cap-handler-framework/README.mdOn GitHub

cap-handler-framework

Handler framework for SAP CAP applications — convention-based, TypeScript-first, draft-aware.


✨ Features

  • ✅ Convention-based — auto-maps methods like beforeCreate, onRead, afterUpdate
  • ✅ Correct draft lifecycle — explicit hooks for NEW/PATCH/EDIT/SAVE/DISCARD, separated from active entity hooks
  • ✅ Actions & functions — bound and unbound operations with clear naming (onBoundAction_, onUnboundAction_, …)
  • ✅ Multi-service — support for multiple CAP services in one project
  • ✅ Type-safe — full TypeScript support
  • ✅ Performance — ExpandTree optimization (50–80% fewer remote calls)
  • ✅ Auto-generation — CDS plugin generates handlers/index.ts automatically
  • ✅ Watch support — cds watch triggers index regeneration without infinite reload loops
  • ✅ Dependency injection — shared context for external services and utilities
  • ✅ Local dev — npm workspace setup for framework development without publishing

📦 Installation

npm install cap-handler-framework

🚀 Quick start

1. Create a handler
// srv/my-service/handlers/entities/BooksHandler.ts
import { BaseHandler } from 'cap-handler-framework';
import type { TypedRequest } from 'cap-handler-framework';

export default class BooksHandler extends BaseHandler {
  getEntityName() { return 'Books'; }

  async beforeCreate(req: TypedRequest): Promise<void> {
    req.data.createdAt = new Date().toISOString();
  }

  async onRead(req: TypedRequest, next: () => Promise<any>): Promise<any> {
    this.initializeExpandTree(req);
    const result = await next();
    if (this.isExpanded('author')) {
      await this.enrichAuthor(result);
    }
    return result;
  }
}
2. Register handlers in your service
// srv/my-service.ts
import { ApplicationService } from '@sap/cds';
import { registerHandlers } from 'cap-handler-framework';
import { HANDLER_CLASSES } from './my-service/handlers';

export class MyService extends ApplicationService {
  async init() {
    await registerHandlers(this, { handlerClasses: HANDLER_CLASSES });
    return super.init();
  }
}
3. Start the server
cds watch

The HANDLER_CLASSES import is auto-generated. ✅


🎯 Active entity hooks

MethodPhaseCAP eventRegisters on
beforeCreatebeforeCREATEentity
afterCreateafterCREATEentity
beforeReadbeforeREADentity
onReadonREADentity
afterReadafterREADentity (+ entity.drafts if draft-enabled)
beforeUpdatebeforeUPDATEentity
afterUpdateafterUPDATEentity
beforeDeletebeforeDELETEentity
afterDeleteafterDELETEentity

beforeCreate also fires when a draft is activated (SAVE → INSERT on active entity). This is correct CAP behaviour.


🗂️ Draft lifecycle hooks

Enable draft support in your handler:

shouldHandleDrafts(): boolean { return true; }
MethodPhaseCAP eventRegisters on
beforeNewDraftbeforeNEWentity (active)
afterNewDraftafterNEWentity
beforeCreateDraftbeforeCREATEentity.drafts
afterCreateDraftafterCREATEentity.drafts
beforePatchDraftbeforePATCHentity.drafts
afterPatchDraftafterPATCHentity.drafts
beforeEditDraftbeforeEDITentity (active)
afterEditDraftafterEDITentity
beforeSaveDraftbeforeSAVEentity.drafts
afterSaveDraftafterSAVEentity.drafts
beforeDiscardDraftbeforeCANCELentity.drafts
afterDiscardDraftafterCANCELentity.drafts

beforeEditDraft and beforeNewDraft fire on the active entity — CAP fires NEW and EDIT on the active entity, not on the drafts table.

export default class TradeSlipsHandler extends BaseHandler {
  getEntityName() { return 'TradeSlips'; }
  shouldHandleDrafts() { return true; }

  // Fires during draft activation (SAVE → CREATE on active entity)
  async beforeCreate(req: TypedRequest): Promise<void> {
    req.data.tradeSlipIndex = await this.sequenceManager.nextIndex();
  }

  // User changed a field in the draft form
  async afterPatchDraft(data: any, req: TypedRequest): Promise<void> {
    await this.autoFillDeliveryAddress(this.toArray(data)[0], req);
  }

  // Final validation before activation
  async beforeSaveDraft(req: TypedRequest): Promise<void> {
    if (!req.data.customerNumber) req.error(400, 'Customer is required');
  }

  // User clicked "Discard"
  async beforeDiscardDraft(req: TypedRequest): Promise<void> {
    this.logger.info('Draft discarded');
  }
}

⚡ Actions and functions

Naming convention
Method prefixRegisters as
onBoundAction_<Name>srv.on('<Name>', entity, handler)
onUnboundAction_<Name>srv.on('<Name>', handler)
onBoundFunction_<Name>srv.on('<Name>', entity, handler)
onUnboundFunction_<Name>srv.on('<Name>', handler)
on<Name> (legacy)auto-detected from CDS model
Bound action example
// CDS definition
entity TradeSlips ... actions {
  action DuplicateTradeSlip() returns TradeSlips;
};
// Handler
async onBoundAction_DuplicateTradeSlip(req: TypedRequest): Promise<any> {
  const { ID } = req.params[0] as any; // entity key
  const tx = this.tx(req);
  // ... duplicate logic ...
  return copy;
}
POST /odata/v4/opportunity-management/TradeSlips(ID=550e8400...)/DuplicateTradeSlip
Unbound action example
// CDS definition
service OpportunityManagementService {
  action CreateWithReference(quote_ID: UUID) returns String;
}
// Handler
async onUnboundAction_CreateWithReference(req: TypedRequest): Promise<any> {
  const { quote_ID } = req.data;
  // ... create from reference ...
  return `Created from quote ${quote_ID}`;
}
POST /odata/v4/opportunity-management/CreateWithReference
{ "quote_ID": "..." }

🔌 External services

await registerHandlers(this, {
  handlerClasses: HANDLER_CLASSES,
  externalServices: ['API_BUSINESS_PARTNER', 'API_PRODUCT_SRV'],
  utilities: { sequenceManager: new SequenceManager() },
});

In the handler:

const bpApi = this.getExternalService('API_BUSINESS_PARTNER');
const result = await bpApi.run(SELECT.from('A_BusinessPartner').where({ ... }));

🏗️ Project structure

srv/
└── opportunity-management/
    ├── handlers/
    │   ├── index.ts             ← AUTO-GENERATED by cds-plugin
    │   ├── entities/
    │   │   ├── TradeSlipsHandler.ts
    │   │   └── TradeSlipItemHandler.ts
    │   └── proxies/
    │       └── BusinessPartnersProxyHandler.ts
    └── utils/
        └── SequenceManager.ts

📖 Documentation

DocumentTopic
docs/GETTING_STARTED.mdFull setup walkthrough — handlers, operations, proxies
docs/HOOKS.mdActive entity lifecycle hooks
docs/DRAFTS.mdDraft lifecycle — NEW, PATCH, EDIT, SAVE, DISCARD
docs/ACTIONS_AND_FUNCTIONS.mdBound/unbound actions and functions
docs/SERVICE_LAYER.mdService layer pattern — BaseService, Pattern C, testing
docs/HANDLER_INDEX_GENERATION.mdCDS plugin, safe write, file watcher
docs/LOCAL_DEVELOPMENT.mdLocal dev with file: symlink and switching to npm
docs/QUICK_REFERENCE.mdOne-page cheat sheet

🛠️ Local development

Use a file: dependency to develop the framework alongside a CAP project without publishing:

// my-cap-project/package.json
"cap-handler-framework": "file:../cap-handler-framework"
cd cap-handler-framework && npm run watch   # recompile on save
cd my-cap-project && cds-ts watch           # CAP dev server picks up changes via symlink

When you're ready to switch back to the published npm version:

# Remove the symlink first (npm install alone won't replace it)
rm my-cap-project/node_modules/cap-handler-framework
cd my-cap-project && npm install cap-handler-framework@latest

See docs/LOCAL_DEVELOPMENT.md for the full setup and troubleshooting guide.


📝 License

MIT

MIT, read

History

Latest

v1.2.2

  1. v1.2.1

  2. v1.2.0

  3. v1.1.2

  4. v1.1.1

  5. v1.1.0

  6. v1.0.3

  7. v1.0.2

  8. v1.0.1

  9. v1.0.0

On npm
0downloads per week, npm
Weekly downloads for cap-handler-framework26 complete calendar weeks from 2026-04-27 to 2026-10-25; the latest total is 0.27 Apr – 3 May: 144 May – 10 May: 1511 May – 17 May: 1618 May – 24 May: 13325 May – 31 May: 311 Jun – 7 Jun: 258 Jun – 14 Jun: 43215 Jun – 21 Jun: 4122 Jun – 28 Jun: 4329 Jun – 5 Jul: 396 Jul – 12 Jul: 2513 Jul – 19 Jul: 2720 Jul – 26 Jul: 2527 Jul – 2 Aug: 33 Aug – 9 Aug: 1810 Aug – 16 Aug: 4417 Aug – 23 Aug: 1424 Aug – 30 Aug: 1431 Aug – 6 Sept: 157 Sept – 13 Sept: 1714 Sept – 20 Sept: 1021 Sept – 27 Sept: 728 Sept – 4 Oct: 105 Oct – 11 Oct: 212 Oct – 18 Oct: 319 Oct – 25 Oct: 0

Others providing12

Draft handling
  1. com.sap.cds:cds-services-bomMaven bill of materials pinning CAP Java module versionsfrom CAP
  2. @rizing/cds-extensionCAP plugin putting a draft-capable facade in front of imported ODatafrom CAP
  3. @sap/cds-fioriFiori draft handling and UI-facing behaviour for CAP servicesfrom CAP
  4. @sap/cdsCAP Node.js runtimefrom CAP
  5. cap-collaborative-draftCollaborative draft support for CAP Node.js servicesfrom CAP
TypeScript patterns
  1. @dxfrontier/cds-ts-dispatcherDecorator-driven handler registration for CAP in TypeScriptfrom CAP
  2. @dxfrontier/cds-ts-repositoryTyped BaseRepository over CAP entitiesfrom CAP
  3. cds-plugin-handlersOne handler file per entity in CAP servicesfrom CAP
  4. cds-routing-handlersDecorator-based routing of CAP service handlers in TypeScriptfrom CAP
  5. cds2typesCLI generating TypeScript interfaces from a CDS modelfrom CAP
  6. cds-plugin-typescriptCDS build task compiling TypeScript sources during cds buildfrom CAP
  7. cds-hyper-app-serviceTyped class-based service layer over CAP ApplicationServicefrom CAP

Details

Sources4
npm registrydiscovered here, keywords:cdschecked
npm registry, cap-handler-frameworkversion, releases, installschecked
github.com/git+https://github.com/Mubuntu/cap-handler-framework.gittree, pushes, contributors, CIchecked
README.mdquoted, and what the entry declareschecked

Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.

Compatibility and provenance
runtimesnode
declared for^7.0.0 || ^8.0.0 || ^9.0.0
latestv1.2.2
first release
releases and cadence10, ~1d median
discovered vianpm registry · keywords:cds
first seen in the atlas
ecosystem and domaincap, developer-experience
History in the atlas2
  1. what_it_does and three facet values
  2. First seen.
For your agent
Ref
artifact:mubuntu-cap-handler-framework
JSON
/artifacts/mubuntu-cap-handler-framework.json
MCP
Connecting an agent