| name | general-create-repository |
| description | Create or extend a repository in the Umbraco backoffice. Covers detail (CRUD), item (batch lookup), collection (paginated list), and action-specific (publish, duplicate, move, etc.) repositories. Use when the user says "create a repository", "add a data source", or when a feature needs to fetch or post data. Each repository type has its own template — pick the right one based on the operation. |
| allowed-tools | Read, Write, Edit, Grep, Glob |
Create Repository
Create or extend a repository and its data source for an entity feature in the Umbraco backoffice.
Foundational documentation
Read these before creating a repository — they define the conventions this skill builds on:
- Repositories — Repository categories, file structure, naming, extension registration, when to use which type
- Data Flow — Full data flow chain, tryExecute, model mapping, store direction
- Package Development — Folder conventions, vertical slices, public API rules
What you need from the user
- Entity name — singular, kebab-case (e.g.,
webhook, document, data-type)
- Repository type —
detail, item, collection, or action-specific
- Package path — which package directory (e.g.,
src/packages/webhook/webhook/)
Additional for action-specific: 4. Action name — what the operation does (e.g., duplicate, move-to, publishing, culture-and-hostnames) 5. Methods — what operations the repository exposes
Choosing the right type
See Repositories — When to Create Which for the decision matrix.
Option A: Detail Repository
The most common repository type. Handles full CRUD lifecycle with store caching.
Prerequisites
- Entity type constant in
entity.ts
- Detail model type in
types.ts (must extend UmbEntityModel — needs entityType and unique fields)
- Generated API client available for the entity
Files to create
{package-path}/repository/detail/
├── {entity}-detail.repository.ts
├── {entity}-detail.server.data-source.ts
├── {entity}-detail.store.ts
├── {entity}-detail.store.context-token.ts
├── constants.ts
└── manifests.ts
Step 1: Create store context token
File: {entity}-detail.store.context-token.ts
import type { UmbDetailStoreBase } from '@umbraco-cms/backoffice/store';
import type { Umb{EntityName}DetailModel } from '../../types.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export const UMB_{ENTITY}_DETAIL_STORE_CONTEXT = new UmbContextToken<UmbDetailStoreBase<Umb{EntityName}DetailModel>>(
'Umb{EntityName}DetailStore',
);
Step 2: Create store
File: {entity}-detail.store.ts
import type { Umb{EntityName}DetailModel } from '../../types.js';
import { UMB_{ENTITY}_DETAIL_STORE_CONTEXT } from './{entity}-detail.store.context-token.js';
import { UmbDetailStoreBase } from '@umbraco-cms/backoffice/store';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class Umb{EntityName}DetailStore extends UmbDetailStoreBase<Umb{EntityName}DetailModel> {
constructor(host: UmbControllerHost) {
super(host, UMB_{ENTITY}_DETAIL_STORE_CONTEXT);
}
}
export { Umb{EntityName}DetailStore as api };
Step 3: Create server data source
File: {entity}-detail.server.data-source.ts
The data source maps between server API types and domain models. See Data Flow for context on how this fits in the chain.
import type { Umb{EntityName}DetailModel } from '../../types.js';
import { UMB_{ENTITY}_ENTITY_TYPE } from '../../entity.js';
import { UmbId } from '@umbraco-cms/backoffice/id';
import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository';
import { {EntityName}Service } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
export class Umb{EntityName}DetailServerDataSource implements UmbDetailDataSource<Umb{EntityName}DetailModel> {
#host: UmbControllerHost;
constructor(host: UmbControllerHost) {
this.#host = host;
}
async createScaffold(preset: Partial<Umb{EntityName}DetailModel> = {}) {
const data: Umb{EntityName}DetailModel = {
entityType: UMB_{ENTITY}_ENTITY_TYPE,
unique: UmbId.new(),
name: '',
...preset,
};
return { data };
}
async read(unique: string) {
if (!unique) throw new Error('Unique is missing');
const { data, error } = await tryExecute(
this.#host,
{EntityName}Service.get{EntityName}ById({ path: { id: unique } }),
);
if (error || !data) {
return { error };
}
const model: Umb{EntityName}DetailModel = {
entityType: UMB_{ENTITY}_ENTITY_TYPE,
unique: data.id,
name: data.name ?? '',
};
return { data: model };
}
async create(model: Umb{EntityName}DetailModel, parentUnique: string | null) {
if (!model) throw new Error('Model is missing');
const body = {
id: model.unique,
name: model.name,
};
const { data, error } = await tryExecute(
this.#host,
{EntityName}Service.post{EntityName}({ body }),
);
if (data) {
return this.read(data as never);
}
return { error };
}
async update(model: Umb{EntityName}DetailModel) {
if (!model.unique) throw new Error('Unique is missing');
const body = {
name: model.name,
};
const { error } = await tryExecute(
this.#host,
{EntityName}Service.put{EntityName}ById({ path: { id: model.unique }, body }),
);
if (!error) {
return this.read(model.unique);
}
return { error };
}
async delete(unique: string) {
if (!unique) throw new Error('Unique is missing');
return tryExecute(
this.#host,
{EntityName}Service.delete{EntityName}ById({ path: { id: unique } }),
);
}
}
Important: Find the actual generated API service name and method names by checking @umbraco-cms/backoffice/external/backend-api. The names above are illustrative — the real generated service may differ.
Step 4: Create repository
File: {entity}-detail.repository.ts
import type { Umb{EntityName}DetailModel } from '../../types.js';
import { Umb{EntityName}DetailServerDataSource } from './{entity}-detail.server.data-source.js';
import { UMB_{ENTITY}_DETAIL_STORE_CONTEXT } from './{entity}-detail.store.context-token.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbDetailRepositoryBase } from '@umbraco-cms/backoffice/repository';
export class Umb{EntityName}DetailRepository extends UmbDetailRepositoryBase<Umb{EntityName}DetailModel> {
constructor(host: UmbControllerHost) {
super(host, Umb{EntityName}DetailServerDataSource, UMB_{ENTITY}_DETAIL_STORE_CONTEXT);
}
}
export { Umb{EntityName}DetailRepository as api };
Step 5: Create constants
File: constants.ts
export const UMB_{ENTITY}_DETAIL_REPOSITORY_ALIAS = 'Umb.Repository.{EntityName}.Detail';
export const UMB_{ENTITY}_DETAIL_STORE_ALIAS = 'Umb.Store.{EntityName}.Detail';
Step 6: Create manifests
File: manifests.ts
import { UMB_{ENTITY}_DETAIL_REPOSITORY_ALIAS, UMB_{ENTITY}_DETAIL_STORE_ALIAS } from './constants.js';
import { Umb{EntityName}DetailStore } from './{entity}-detail.store.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'repository',
alias: UMB_{ENTITY}_DETAIL_REPOSITORY_ALIAS,
name: '{EntityName} Detail Repository',
api: () => import('./{entity}-detail.repository.js'),
},
{
type: 'store',
alias: UMB_{ENTITY}_DETAIL_STORE_ALIAS,
name: '{EntityName} Detail Store',
api: Umb{EntityName}DetailStore,
},
];
Step 7: Wire manifests into parent
Import and spread in the parent module's manifests.ts:
import { manifests as detailRepositoryManifests } from './repository/detail/manifests.js';
export const manifests: Array<UmbExtensionManifest> = [
...detailRepositoryManifests,
];
Detail repository checklist
Option B: Item Repository
For batch-fetching lightweight display info (name, icon, entity type) by unique IDs. Used by pickers, reference lists, and breadcrumbs.
Prerequisites
- Item model type in
types.ts (needs unique field)
- Generated API client with a batch/list endpoint
Files to create
{package-path}/item/repository/
├── {entity}-item.repository.ts
├── {entity}-item.server.data-source.ts
├── {entity}-item.store.ts
├── {entity}-item.store.context-token.ts
├── constants.ts
└── manifests.ts
Step 1: Create item store context token
File: {entity}-item.store.context-token.ts
import type { UmbItemStore } from '@umbraco-cms/backoffice/store';
import type { Umb{EntityName}ItemModel } from '../../types.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export const UMB_{ENTITY}_ITEM_STORE_CONTEXT = new UmbContextToken<UmbItemStore<Umb{EntityName}ItemModel>>(
'Umb{EntityName}ItemStore',
);
Step 2: Create item store
File: {entity}-item.store.ts
import type { Umb{EntityName}ItemModel } from '../../types.js';
import { UMB_{ENTITY}_ITEM_STORE_CONTEXT } from './{entity}-item.store.context-token.js';
import { UmbItemStoreBase } from '@umbraco-cms/backoffice/store';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class Umb{EntityName}ItemStore extends UmbItemStoreBase<Umb{EntityName}ItemModel> {
constructor(host: UmbControllerHost) {
super(host, UMB_{ENTITY}_ITEM_STORE_CONTEXT);
}
}
export { Umb{EntityName}ItemStore as api };
Step 3: Create item server data source
File: {entity}-item.server.data-source.ts
import type { Umb{EntityName}ItemModel } from '../../types.js';
import { UMB_{ENTITY}_ENTITY_TYPE } from '../../entity.js';
import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository';
import type { {EntityName}ItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { {EntityName}Service } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class Umb{EntityName}ItemServerDataSource extends UmbItemServerDataSourceBase<
{EntityName}ItemResponseModel,
Umb{EntityName}ItemModel
> {
constructor(host: UmbControllerHost) {
super(host, {
getItems: (uniques) => {EntityName}Service.getItem{EntityName}({ query: { id: uniques } }),
mapper: (item) => ({
entityType: UMB_{ENTITY}_ENTITY_TYPE,
unique: item.id,
name: item.name ?? '',
}),
});
}
}
Step 4: Create item repository
File: {entity}-item.repository.ts
import type { Umb{EntityName}ItemModel } from '../../types.js';
import { Umb{EntityName}ItemServerDataSource } from './{entity}-item.server.data-source.js';
import { UMB_{ENTITY}_ITEM_STORE_CONTEXT } from './{entity}-item.store.context-token.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbItemRepositoryBase } from '@umbraco-cms/backoffice/repository';
export class Umb{EntityName}ItemRepository extends UmbItemRepositoryBase<Umb{EntityName}ItemModel> {
constructor(host: UmbControllerHost) {
super(host, Umb{EntityName}ItemServerDataSource, UMB_{ENTITY}_ITEM_STORE_CONTEXT);
}
}
export { Umb{EntityName}ItemRepository as api };
Step 5: Create constants and manifests
Follow the same pattern as detail (Step 5–6), using Item instead of Detail in aliases.
Item repository checklist
Option C: Collection Repository
For paginated/filtered listings. No base class — implement the UmbCollectionRepository interface on UmbRepositoryBase.
Files to create
{package-path}/collection/repository/
├── {entity}-collection.repository.ts
├── {entity}-collection.server.data-source.ts
└── manifests.ts
Step 1: Create collection server data source
File: {entity}-collection.server.data-source.ts
import type { Umb{EntityName}CollectionFilterModel } from '../types.js';
import { {EntityName}Service } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
export class Umb{EntityName}CollectionServerDataSource {
#host: UmbControllerHost;
constructor(host: UmbControllerHost) {
this.#host = host;
}
async getCollection(filter: Umb{EntityName}CollectionFilterModel) {
const { data, error } = await tryExecute(
this.#host,
{EntityName}Service.get{EntityName}({ query: { skip: filter.skip, take: filter.take } }),
);
if (error || !data) {
return { error };
}
return {
data: {
items: data.items.map((item) => ({
})),
total: data.total,
},
};
}
}
Step 2: Create collection repository
File: {entity}-collection.repository.ts
import { Umb{EntityName}CollectionServerDataSource } from './{entity}-collection.server.data-source.js';
import type { Umb{EntityName}CollectionFilterModel } from '../types.js';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
import type { UmbCollectionRepository } from '@umbraco-cms/backoffice/collection';
export class Umb{EntityName}CollectionRepository
extends UmbRepositoryBase
implements UmbCollectionRepository
{
#collectionSource = new Umb{EntityName}CollectionServerDataSource(this);
async requestCollection(filter: Umb{EntityName}CollectionFilterModel) {
return this.#collectionSource.getCollection(filter);
}
}
export { Umb{EntityName}CollectionRepository as api };
Collection repository checklist
Option D: Action-Specific Repository
For domain operations that don't fit CRUD — publish, duplicate, move, sort, recycle bin, etc.
Prerequisites
- Entity type constant
- Generated API client with the relevant endpoint
Files to create
{package-path}/{action-location}/repository/
├── {entity}-{action}.repository.ts
├── {entity}-{action}.server.data-source.ts
├── types.ts # Optional — args/response types
└── manifests.ts
Location rules:
- Entity actions:
entity-actions/{action-name}/repository/
- Bulk actions:
entity-bulk-actions/{action-name}/repository/
- Domain sub-features:
{feature-name}/repository/ (e.g., publishing/repository/)
Step 1: Create types (if needed)
File: types.ts
export interface Umb{Action}{EntityName}RequestArgs {
unique: string;
}
Step 2: Create server data source
File: {entity}-{action}.server.data-source.ts
import type { Umb{Action}{EntityName}RequestArgs } from './types.js';
import { {EntityName}Service } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
export class Umb{Action}{EntityName}ServerDataSource {
#host: UmbControllerHost;
constructor(host: UmbControllerHost) {
this.#host = host;
}
async {actionMethod}(args: Umb{Action}{EntityName}RequestArgs) {
if (!args.unique) throw new Error('Unique is missing');
return tryExecute(
this.#host,
{EntityName}Service.{apiMethod}({
path: { id: args.unique },
body: {
},
}),
);
}
}
Step 3: Create repository
File: {entity}-{action}.repository.ts
import { Umb{Action}{EntityName}ServerDataSource } from './{entity}-{action}.server.data-source.js';
import type { Umb{Action}{EntityName}RequestArgs } from './types.js';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
export class Umb{Action}{EntityName}Repository extends UmbRepositoryBase {
#{action}Source = new Umb{Action}{EntityName}ServerDataSource(this);
async request{Action}(args: Umb{Action}{EntityName}RequestArgs) {
const { data, error } = await this.#{action}Source.{actionMethod}(args);
if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
const notification = { data: { message: `{Action} completed` } };
notificationContext.peek('positive', notification);
}
return { data, error };
}
}
export { Umb{Action}{EntityName}Repository as api };
Step 4: Create manifests
File: manifests.ts
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'repository',
alias: 'Umb.Repository.{EntityName}.{Action}',
name: '{EntityName} {Action} Repository',
api: () => import('./{entity}-{action}.repository.js'),
},
];
Action-specific repository checklist
Reference examples
See Repositories — Reference Examples for real implementations to study.