| name | add-object-type |
| description | Add a new ABAP object type with full ADK model and abapGit serialization support. USE WHEN adding a new ABAP type (PROG, FUGR, TABL, DOMA, DTEL, MSAG, etc.), ADK support, or abapGit handler. Trigger words - new object type, add object type, support PROG, add abapGit handler. |
Add a New ABAP Object Type
This skill guides adding full support for a new ABAP object type. It covers all layers: ADT endpoint (schema + contract + fixture), ADK model, and abapGit serialization handler.
Overview of All Layers
ABAP Object Type (e.g., PROG/P, FUGR/F, TABL/DT)
↓
adt-schemas Schema literal + typed wrapper
adt-contracts Contract (CRUD or simple GET) for the ADT endpoint
adt-fixtures XML fixture for tests
↓
adk ADK kind constant + object model class
↓
adt-plugin-abapgit abapGit schema literal + TypeScript types + handler
Step 1: Gather Object Type Information
Before writing code, gather details about the object type from live system or research.
Tip: For undocumented object types, use $adt-reverse-engineering first to systematically discover endpoint details, XML structure, and content-types via live system discovery, open source projects, and Sourcegraph code search.
Information to collect
| Item | Example (for PROG) | Where to find |
|---|
| ADT object type | PROG/P | SAP ADT documentation |
| ADT endpoint path | /sap/bc/adt/programs/programs | Live system or community |
| ADT content-type | application/vnd.sap.adt.programs.programs.v2+xml | Live system response headers |
| ADT XML namespace | http://www.sap.com/adt/programs | Live system XML |
| ADT root element | abapProgram | Live system XML |
| abapGit serializer | LCL_OBJECT_PROG | abapGit source |
| abapGit main table | TRDIR | abapGit source |
| abapGit structure fields | NAME, SECU, EDTX, ... | SAP DDIC / abapGit XML sample |
| Has source code | Yes (.abap file) | abapGit repo samples |
Option A: Live System Available
Option B: No Live System — Web Research
-
ADT endpoint details:
-
abapGit serializer and schema:
- abapGit repository: https://github.com/abapGit/abapGit
- Look in
src/objects/ for the relevant handler class (ZCL_ABAPGIT_OBJECT_{TYPE})
- Find
.abap or .xml sample files in abapGit test fixtures
- Search abapGit issues/PRs for the object type
-
SAP DDIC table structure:
- abapGit XML files reveal the exact fields used
- SAP community/SE11 screenshots often available online
Step 2: Create the ADT Schema and Contract
Follow the full $add-endpoint skill for this step. Here is a summary specific to object types:
Rules: See xsd-best-practices for XSD validity, file-lifecycle for generated vs hand-maintained files, and adt-ddic-mapping for DDIC object specifics.
Schema (adt-schemas)
Create packages/adt-schemas/src/schemas/generated/schemas/sap/{schemaName}.ts:
import adtcore from './adtcore';
import abapsource from './abapsource';
export default {
$xmlns: {
adtcore: 'http://www.sap.com/adt/core',
abapsource: 'http://www.sap.com/adt/abapsource',
xsd: 'http://www.w3.org/2001/XMLSchema',
prog: 'http://www.sap.com/adt/programs',
},
$imports: [adtcore, abapsource],
targetNamespace: 'http://www.sap.com/adt/programs',
attributeFormDefault: 'qualified',
elementFormDefault: 'qualified',
element: [
{
name: 'abapProgram',
type: 'prog:AbapProgram',
},
],
complexType: [
{
name: 'AbapProgram',
complexContent: {
extension: {
base: 'abapsource:AbapSourceMainObject',
attribute: [
{
name: 'programType',
type: 'xsd:string',
},
],
},
},
},
],
} as const;
Then add to packages/adt-schemas/src/schemas/generated/typed.ts:
import type { AbapProgramSchema } from './types/sap/abapProgram.types';
import _abapProgram from './schemas/sap/abapProgram';
export const abapProgram: TypedSchema<AbapProgramSchema> =
typedSchema<AbapProgramSchema>(_abapProgram);
And add to packages/adt-contracts/src/generated/schemas.ts:
export const abapProgram = toSpeciSchema(adtSchemas.abapProgram);
Contract (adt-contracts)
Create packages/adt-contracts/src/adt/{module}/{name}.ts:
import { crud } from '../../base';
import { abapProgram, type InferTypedSchema } from '../../schemas';
export type ProgramResponse = InferTypedSchema<typeof abapProgram>;
export const programsContract = crud({
basePath: '/sap/bc/adt/programs/programs',
schema: abapProgram,
contentType: 'application/vnd.sap.adt.programs.programs.v2+xml',
sources: ['main'] as const,
});
export type ProgramsContract = typeof programsContract;
Register in the module index (create new module or add to existing).
Fixture (adt-fixtures)
Create packages/adt-fixtures/src/fixtures/{module}/{name}.xml and register in registry.ts.
Step 3: Add ADK Kind
Edit packages/adk/src/base/kinds.ts to add the new kind constant:
export const Package = 'Package' as const;
export const Class = 'Class' as const;
export const Interface = 'Interface' as const;
export const Program = 'Program' as const;
export type AdkKind =
| typeof TransportRequest
| typeof TransportTask
| typeof Package
| typeof Class
| typeof Interface
| typeof Program;
Step 4: Create the ADK Object Model
Create the directory and files for the new object type:
packages/adk/src/objects/repository/{typeLower}/
├── {typeLower}.model.ts ← ADK class
├── {typeLower}.types.ts ← Type definitions
└── index.ts ← Re-exports
{typeLower}.types.ts
export type ProgramIncludeType = 'main';
{typeLower}.model.ts
import { AdkMainObject } from '../../../base/model';
import { Program as ProgramKind } from '../../../base/kinds';
import { getGlobalContext } from '../../../base/global-context';
import type { AdkContext } from '../../../base/context';
import type { ProgramResponse } from '../../../base/adt';
export type ProgramXml = ProgramResponse['abapProgram'];
export class AdkProgram extends AdkMainObject<typeof ProgramKind, ProgramXml> {
static readonly kind = ProgramKind;
readonly kind = AdkProgram.kind;
get objectUri(): string {
return `/sap/bc/adt/programs/programs/${encodeURIComponent(this.name.toLowerCase())}`;
}
async getSource(): Promise<string> {
return this.lazy('source', async () => {
return this.crudContract.source.main.get(this.name);
});
}
protected override get wrapperKey() {
return 'abapProgram';
}
protected override get crudContract(): any {
return this.ctx.client.adt.programs.programs;
}
static async get(name: string, ctx?: AdkContext): Promise<AdkProgram> {
const context = ctx ?? getGlobalContext();
return new AdkProgram(context, name).load();
}
}
import { registerObjectType } from '../../../base/registry';
registerObjectType('PROG', ProgramKind, AdkProgram);
For objects with source code, also implement the save lifecycle methods:
protected override async savePendingSources(options?: {
lockHandle?: string;
transport?: string;
}): Promise<void> {
const pendingSource = (this as unknown as { _pendingSource?: string })
._pendingSource;
if (!pendingSource) return;
await this.saveMainSource(pendingSource, options);
delete (this as unknown as { _pendingSource?: string })._pendingSource;
}
protected override async checkPendingSourcesUnchanged(): Promise<void> {
const pendingSource = (this as unknown as { _pendingSource?: string })
._pendingSource;
if (!pendingSource) return;
try {
const currentSource = await this.getSource();
if (this.normalizeSource(currentSource) === this.normalizeSource(pendingSource)) {
this._unchanged = true;
delete (this as unknown as { _pendingSource?: string })._pendingSource;
}
} catch {
}
}
private normalizeSource(source: string): string {
return source.replace(/\s+$/gm, '').trimEnd();
}
protected override hasPendingSources(): boolean {
return !!(this as unknown as { _pendingSource?: string })._pendingSource;
}
IMPORTANT: Both savePendingSources() AND checkPendingSourcesUnchanged() must be implemented.
See adk-save-logic for edge cases around upsert, lock failures (405), and 422 "already exists" handling.
checkPendingSourcesUnchanged() runs before lock — compares pending source with SAP, sets _unchanged = true if identical
savePendingSources() runs after lock — does the actual PUT
- Without
checkPendingSourcesUnchanged(), unchanged objects will still be locked and PUT unnecessarily
- See
AdkClass (clas.model.ts) for multi-include example, AdkInterface (intf.model.ts) for single-source example
Notes:
- The
wrapperKey matches the root element name in the schema (e.g., abapProgram)
- The
crudContract path must match where you registered the contract in adtContract (e.g., ctx.client.adt.programs.programs)
registerObjectType('PROG', ...) uses the 4-letter ABAP type code (not the full PROG/P)
index.ts
export { AdkProgram } from './program.model';
export type { ProgramXml } from './program.model';
export type { ProgramIncludeType } from './program.types';
Update packages/adk/src/base/adt.ts
This file is the ADT integration layer bridge. ADK object models import response types from here (via ../../../base/adt) rather than directly from @abapify/adt-client. Add the response type re-export:
import type { ProgramResponse as _ProgramResponse } from '@abapify/adt-client';
export type ProgramResponse = Extract<
_ProgramResponse,
{ abapProgram: unknown }
>;
Then in the model file, import as:
import type { ProgramResponse } from '../../../base/adt';
Update the ADK index
Edit packages/adk/src/index.ts to export the new object:
export { AdkProgram } from './objects/repository/prog';
export type { ProgramXml } from './objects/repository/prog';
Update packages/adk/src/base/kinds.ts KindToObject mapping
export type AdkObjectForKind<K extends AdkKind> = K extends typeof Class
? AdkClass
: K extends typeof Interface
? AdkInterface
: K extends typeof Package
? AdkPackage
: K extends typeof Program
? AdkProgram
: AdkObject;
Step 5: Create the abapGit Schema
The abapGit schema captures the structure of the XML file that abapGit writes for this object type.
5a: Research the abapGit XML format
Find out what fields the abapGit serializer writes. Resources:
- abapGit repository: https://github.com/abapGit/abapGit — look in
src/objects/ for ZCL_ABAPGIT_OBJECT_{TYPE}.clas.abap
- Look for
CREATE_VSEO*, ZIF_ABAPGIT_OBJECT~DESERIALIZE, or SERIALIZE methods
- Look at
.xml files in abapGit test repositories
- Search for
{TYPE} in abapGit's docs/ directory
A typical abapGit XML looks like:
<?xml version="1.0" encoding="utf-8"?>
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<TRDIR>
<NAME>ZTEST_PROGRAM</NAME>
<SECU>S</SECU>
<EDTX>X</EDTX>
<SUBC>1</SUBC>
<APPL/>
</TRDIR>
</asx:values>
</asx:abap>
5b: Create the abapGit schema literal
Create packages/adt-plugin-abapgit/src/schemas/generated/schemas/{type}.ts:
export default {
$xmlns: {
xs: 'http://www.w3.org/2001/XMLSchema',
asx: 'http://www.sap.com/abapxml',
},
targetNamespace: 'http://www.sap.com/abapxml',
elementFormDefault: 'unqualified',
element: [
{
name: 'abapGit',
complexType: {
sequence: {
element: [
{
ref: 'asx:abap',
},
],
},
attribute: [
{
name: 'version',
type: 'xs:string',
use: 'required',
},
{
name: 'serializer',
type: 'xs:string',
use: 'required',
},
{
name: 'serializer_version',
type: 'xs:string',
use: 'required',
},
],
},
},
{
name: 'Schema',
abstract: true,
},
{
name: 'abap',
type: 'asx:AbapType',
},
],
complexType: [
{
name: 'AbapValuesType',
all: {
element: [
{
name: 'TRDIR',
type: 'asx:TrdirType',
minOccurs: '0',
},
],
},
},
{
name: 'TrdirType',
all: {
element: [
{
name: 'NAME',
type: 'xs:string',
},
{
name: 'SECU',
type: 'xs:string',
minOccurs: '0',
},
{
name: 'EDTX',
type: 'xs:string',
minOccurs: '0',
},
{
name: 'SUBC',
type: 'xs:string',
minOccurs: '0',
},
],
},
},
{
name: 'AbapType',
sequence: {
element: [
{
name: 'values',
type: 'asx:AbapValuesType',
},
],
},
attribute: [
{
name: 'version',
type: 'xs:string',
default: '1.0',
},
],
},
],
} as const;
Pattern rules for abapGit schemas (follow existing schemas like intf.ts, dtel.ts):
- All fields optional (
minOccurs: '0') except primary key field
- Use
asx: prefix for types in the AbapValuesType and type definitions
- Structure name matches SAP DDIC table/structure name (e.g.,
TRDIR, VSEOCLASS, DD04V)
5c: Create TypeScript types for the abapGit schema
Create packages/adt-plugin-abapgit/src/schemas/generated/types/{type}.ts:
export type ProgSchema =
| {
abapGit: {
abap: {
values: {
TRDIR?: {
NAME: string;
SECU?: string;
EDTX?: string;
SUBC?: string;
ABAP_LANGUAGE_VERSION?: string;
};
};
version?: string;
};
version: string;
serializer: string;
serializer_version: string;
};
}
| {
abap: {
values: {
TRDIR?: {
NAME: string;
SECU?: string;
EDTX?: string;
SUBC?: string;
ABAP_LANGUAGE_VERSION?: string;
};
};
version?: string;
};
};
Pattern: The type is always a union of two variants:
{ abapGit: { abap: { values: ... }, version, serializer, serializer_version } } — full document
{ abap: { values: ... } } — inner abap fragment
5d: Register the schema in the abapGit index
Edit packages/adt-plugin-abapgit/src/schemas/generated/index.ts:
import _prog from './schemas/prog';
import type { ProgSchema as _ProgSchema } from './types/prog';
type ProgAbapGitType = Extract<_ProgSchema, { abapGit: unknown }>;
export const prog = abapGitSchema<
ProgAbapGitType,
ProgAbapGitType['abapGit']['abap']['values']
>(_prog);
Step 6: Create the abapGit Handler
Create packages/adt-plugin-abapgit/src/lib/handlers/objects/{type}.ts:
import { AdkProgram } from '../adk';
import { prog } from '../../../schemas/generated';
import { createHandler } from '../base';
export const programHandler = createHandler(AdkProgram, {
schema: prog,
version: 'v1.0.0',
serializer: 'LCL_OBJECT_PROG',
serializer_version: 'v1.0.0',
toAbapGit: (prog) => ({
TRDIR: {
NAME: prog.name ?? '',
SECU: 'S',
EDTX: 'X',
SUBC: '1',
},
}),
getSource: (prog) => prog.getSource(),
fromAbapGit: ({ TRDIR }) => ({
name: (TRDIR?.NAME ?? '').toUpperCase(),
type: 'PROG/P',
description: undefined,
}),
setSources: (prog, sources) => {
if (sources.main) {
(prog as unknown as { _pendingSource: string })._pendingSource =
sources.main;
}
},
});
For objects with multiple source files (like CLAS):
getSources: (obj) =>
obj.includes.map((inc) => ({
suffix: SUFFIX_MAP[inc.includeType],
content: obj.getIncludeSource(inc.includeType),
})),
suffixToSourceKey: {
'locals_def': 'definitions',
'locals_imp': 'implementations',
'testclasses': 'testclasses',
'macros': 'macros',
},
setSources: (obj, sources) => {
(obj as unknown as { _pendingSources: Record<string, string> })._pendingSources = sources;
if (sources.main) {
(obj as unknown as { _pendingSource: string })._pendingSource = sources.main;
}
},
For objects without source (like DEVC):
- Do not provide
getSource or getSources
setSources is not needed
- Only XML file will be serialized
Register handler in the handlers index
Edit packages/adt-plugin-abapgit/src/lib/handlers/objects/index.ts:
export * from './clas';
export * from './devc';
export * from './doma';
export * from './dtel';
export * from './intf';
export * from './prog';
Step 7: Update adk.ts re-exports in abapGit plugin
Edit packages/adt-plugin-abapgit/src/lib/handlers/adk.ts to re-export the new ADK class:
export { AdkProgram } from '@abapify/adk';
Step 8: Build and Verify
Follow the verification checklist in after-changes.
bunx nx build adt-schemas adt-contracts adk adt-plugin-abapgit adt-fixtures
bunx nx typecheck
bunx nx test adt-schemas adt-contracts adk adt-plugin-abapgit
bunx nx lint
Quick smoke tests
Schema parsing test:
import { fixtures } from '@abapify/adt-fixtures';
import { abapProgram } from '@abapify/adt-schemas';
const xml = await fixtures.programs.program.load();
const data = abapProgram.parse(xml);
expect(data.abapProgram?.['adtcore:name']).toBe('ZTEST_PROGRAM');
abapGit handler test:
import { prog } from '@abapify/adt-plugin-abapgit/schemas/generated';
import { getHandler } from '@abapify/adt-plugin-abapgit/lib/handlers/base';
const handler = getHandler('PROG');
expect(handler).toBeDefined();
Common Object Type Reference
| ABAP Type | ADT Path | abapGit Serializer | Main Table |
|---|
CLAS/OC | /sap/bc/adt/oo/classes | LCL_OBJECT_CLAS | VSEOCLASS |
INTF/OI | /sap/bc/adt/oo/interfaces | LCL_OBJECT_INTF | VSEOINTERF |
DEVC/K | /sap/bc/adt/packages | LCL_OBJECT_DEVC | DEVC |
DTEL/DE | /sap/bc/adt/ddic/dataelements | LCL_OBJECT_DTEL | DD04V |
DOMA/DO | /sap/bc/adt/ddic/domains | LCL_OBJECT_DOMA | DD01V |
PROG/P | /sap/bc/adt/programs/programs | LCL_OBJECT_PROG | TRDIR |
FUGR/F | /sap/bc/adt/functions/groups | LCL_OBJECT_FUGR | ENLFDIR |
TABL/DT | /sap/bc/adt/ddic/tables | LCL_OBJECT_TABL | DD02V |
MSAG/E | /sap/bc/adt/messageclass | LCL_OBJECT_MSAG | T100A |
Checklist
ADT Endpoint Layer
ADK Layer
abapGit Layer
Verification