> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/medusajs/medusa/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Services

> Learn how to create services in Medusa extending MedusaService with decorators.

Services in Medusa encapsulate business logic and data access for specific domains. They extend the `MedusaService` base class and use decorators for transaction management and event handling.

## What is a Service?

A Medusa service:

* Extends `MedusaService` to get automatic CRUD methods
* Manages data access for one or more entities
* Uses decorators for cross-cutting concerns (transactions, events)
* Can be injected into workflows, API routes, and other services
* Provides type-safe DTOs for inputs and outputs

## Creating a Service

<Steps>
  <Step title="Define Your Entity">
    First, create your data model:

    ```typescript title="src/modules/brand/models/brand.ts" theme={null}
    import { Entity, Property, OneToMany } from "@mikro-orm/core"
    import { BaseEntity } from "@medusajs/framework/utils"

    @Entity()
    export class Brand extends BaseEntity {
      @Property()
      name: string

      @Property({ nullable: true })
      description?: string

      @Property({ nullable: true })
      logo_url?: string

      @Property({ default: true })
      is_active: boolean = true
    }
    ```
  </Step>

  <Step title="Create the Service Class">
    Extend `MedusaService` with your entity configuration:

    ```typescript title="src/modules/brand/services/brand-module-service.ts" theme={null}
    import {
      Context,
      DAL,
      InternalModuleDeclaration,
    } from "@medusajs/framework/types"
    import {
      InjectManager,
      InjectTransactionManager,
      MedusaContext,
      MedusaService,
      ModulesSdkTypes,
    } from "@medusajs/framework/utils"
    import { Brand } from "../models"
    import { BrandDTO, CreateBrandDTO } from "../types"

    type InjectedDependencies = {
      baseRepository: DAL.RepositoryService
      brandService: ModulesSdkTypes.IMedusaInternalService<Brand>
    }

    export default class BrandModuleService extends MedusaService<{
      Brand: {
        dto: BrandDTO
      }
    }>({
      Brand,
    }) {
      protected baseRepository_: DAL.RepositoryService
      protected brandService_: ModulesSdkTypes.IMedusaInternalService<Brand>

      constructor(
        { baseRepository, brandService }: InjectedDependencies,
        protected readonly moduleDeclaration: InternalModuleDeclaration
      ) {
        super(...arguments)
        this.baseRepository_ = baseRepository
        this.brandService_ = brandService
      }
    }
    ```

    The service automatically gets these methods:

    * `createBrands(data: CreateBrandDTO[]): Promise<BrandDTO[]>`
    * `updateBrands(data: UpdateBrandDTO[]): Promise<BrandDTO[]>`
    * `listBrands(filters?, config?): Promise<BrandDTO[]>`
    * `listAndCountBrands(filters?, config?): Promise<[BrandDTO[], number]>`
    * `retrieveBrand(id, config?): Promise<BrandDTO>`
    * `deleteBrands(ids: string[]): Promise<void>`
    * `softDeleteBrands(ids: string[]): Promise<void>`
    * `restoreBrands(ids: string[]): Promise<void>`
  </Step>

  <Step title="Add Custom Methods">
    Implement custom business logic:

    ```typescript theme={null}
    export default class BrandModuleService extends MedusaService<{
      Brand: { dto: BrandDTO }
    }>({
      Brand,
    }) {
      // ... constructor ...

      @InjectManager()
      async findByName(
        name: string,
        @MedusaContext() sharedContext: Context = {}
      ): Promise<BrandDTO | null> {
        const [brand] = await this.brandService_.list(
          { name },
          { take: 1 },
          sharedContext
        )
        return brand ? await this.baseRepository_.serialize(brand) : null
      }

      @InjectManager()
      async listActiveBrands(
        @MedusaContext() sharedContext: Context = {}
      ): Promise<BrandDTO[]> {
        const brands = await this.brandService_.list(
          { is_active: true },
          {},
          sharedContext
        )
        return await this.baseRepository_.serialize(brands)
      }
    }
    ```
  </Step>
</Steps>

## Service Decorators

Decorators provide cross-cutting functionality for your service methods.

### @InjectManager

Injects the entity manager for database operations. Use on **public methods**:

```typescript theme={null}
@InjectManager()
async createBrand(
  data: CreateBrandDTO,
  @MedusaContext() sharedContext: Context = {}
): Promise<BrandDTO> {
  const [brand] = await this.createBrands([data], sharedContext)
  return brand
}
```

### @InjectTransactionManager

Injects a transactional entity manager. Use on **protected methods** that modify data:

```typescript theme={null}
@InjectTransactionManager()
protected async updateBrandStatus_(
  id: string,
  isActive: boolean,
  @MedusaContext() sharedContext: Context = {}
): Promise<Brand> {
  const brand = await this.brandService_.retrieve(id, {}, sharedContext)
  brand.is_active = isActive
  await this.brandService_.update([brand], sharedContext)
  return brand
}
```

### @MedusaContext

Marks the context parameter for transaction and manager injection:

```typescript theme={null}
async listBrands(
  filters: FilterableBrandProps = {},
  config: FindConfig<BrandDTO> = {},
  @MedusaContext() sharedContext: Context = {}
): Promise<BrandDTO[]> {
  return await this.brandService_.list(filters, config, sharedContext)
}
```

### @EmitEvents

Automatically emits events after method execution:

```typescript theme={null}
import { EmitEvents } from "@medusajs/framework/utils"

@InjectManager()
@EmitEvents()
async activateBrand(
  id: string,
  @MedusaContext() sharedContext: Context = {}
): Promise<BrandDTO> {
  const brand = await this.updateBrandStatus_(id, true, sharedContext)
  
  // Event automatically emitted
  this.aggregatedEvents({
    action: "activated",
    object: "brand",
    data: { id },
    context: sharedContext,
  })
  
  return await this.baseRepository_.serialize(brand)
}
```

## Complete Service Example

Here's a full service implementation with custom logic:

```typescript title="src/modules/brand/services/brand-module-service.ts" theme={null}
import {
  Context,
  DAL,
  FilterableProductProps,
  FindConfig,
  InternalModuleDeclaration,
} from "@medusajs/framework/types"
import {
  EmitEvents,
  InjectManager,
  InjectTransactionManager,
  MedusaContext,
  MedusaError,
  MedusaService,
  ModulesSdkTypes,
} from "@medusajs/framework/utils"
import { Brand } from "../models"
import { BrandDTO, CreateBrandDTO, UpdateBrandDTO } from "../types"

type InjectedDependencies = {
  baseRepository: DAL.RepositoryService
  brandService: ModulesSdkTypes.IMedusaInternalService<Brand>
}

export default class BrandModuleService extends MedusaService<{
  Brand: {
    dto: BrandDTO
  }
}>({
  Brand,
}) {
  protected baseRepository_: DAL.RepositoryService
  protected brandService_: ModulesSdkTypes.IMedusaInternalService<Brand>

  constructor(
    { baseRepository, brandService }: InjectedDependencies,
    protected readonly moduleDeclaration: InternalModuleDeclaration
  ) {
    super(...arguments)
    this.baseRepository_ = baseRepository
    this.brandService_ = brandService
  }

  // Custom retrieval methods
  @InjectManager()
  async findByName(
    name: string,
    @MedusaContext() sharedContext: Context = {}
  ): Promise<BrandDTO | null> {
    const [brand] = await this.brandService_.list(
      { name },
      { take: 1 },
      sharedContext
    )
    return brand ? await this.baseRepository_.serialize(brand) : null
  }

  @InjectManager()
  async listActiveBrands(
    filters: FilterableProductProps = {},
    config: FindConfig<BrandDTO> = {},
    @MedusaContext() sharedContext: Context = {}
  ): Promise<BrandDTO[]> {
    const brands = await this.brandService_.list(
      { ...filters, is_active: true },
      config,
      sharedContext
    )
    return await this.baseRepository_.serialize(brands)
  }

  // Custom business logic
  @InjectManager()
  @EmitEvents()
  async activateBrand(
    id: string,
    @MedusaContext() sharedContext: Context = {}
  ): Promise<BrandDTO> {
    const brand = await this.updateBrandStatus_(id, true, sharedContext)

    this.aggregatedEvents({
      action: "activated",
      object: "brand",
      data: { id },
      context: sharedContext,
    })

    return await this.baseRepository_.serialize(brand)
  }

  @InjectManager()
  @EmitEvents()
  async deactivateBrand(
    id: string,
    @MedusaContext() sharedContext: Context = {}
  ): Promise<BrandDTO> {
    const brand = await this.updateBrandStatus_(id, false, sharedContext)

    this.aggregatedEvents({
      action: "deactivated",
      object: "brand",
      data: { id },
      context: sharedContext,
    })

    return await this.baseRepository_.serialize(brand)
  }

  // Protected helper methods
  @InjectTransactionManager()
  protected async updateBrandStatus_(
    id: string,
    isActive: boolean,
    @MedusaContext() sharedContext: Context = {}
  ): Promise<Brand> {
    const brand = await this.brandService_.retrieve(id, {}, sharedContext)

    if (!brand) {
      throw new MedusaError(
        MedusaError.Types.NOT_FOUND,
        `Brand with id: ${id} was not found`
      )
    }

    brand.is_active = isActive
    const [updated] = await this.brandService_.update([brand], sharedContext)
    return updated
  }
}
```

## Working with Relationships

Define relationships in your entities:

```typescript title="src/modules/brand/models/brand.ts" theme={null}
import { Entity, Property, OneToMany, Collection } from "@mikro-orm/core"
import { BaseEntity } from "@medusajs/framework/utils"

@Entity()
export class Brand extends BaseEntity {
  @Property()
  name: string

  @OneToMany(() => Product, (product) => product.brand)
  products = new Collection<Product>(this)
}
```

```typescript title="src/modules/brand/models/product.ts" theme={null}
import { Entity, ManyToOne } from "@mikro-orm/core"

@Entity()
export class Product extends BaseEntity {
  @ManyToOne(() => Brand)
  brand: Brand
}
```

Query with relationships:

```typescript theme={null}
@InjectManager()
async getBrandWithProducts(
  id: string,
  @MedusaContext() sharedContext: Context = {}
): Promise<BrandDTO> {
  const brand = await this.brandService_.retrieve(
    id,
    {
      relations: ["products"],
    },
    sharedContext
  )
  return await this.baseRepository_.serialize(brand)
}
```

## Error Handling

Use `MedusaError` for consistent error handling:

```typescript theme={null}
import { MedusaError } from "@medusajs/framework/utils"

@InjectManager()
async retrieveBrandByName(
  name: string,
  @MedusaContext() sharedContext: Context = {}
): Promise<BrandDTO> {
  const brand = await this.findByName(name, sharedContext)

  if (!brand) {
    throw new MedusaError(
      MedusaError.Types.NOT_FOUND,
      `Brand with name "${name}" was not found`
    )
  }

  return brand
}
```

Common error types:

* `MedusaError.Types.NOT_FOUND` - Resource not found
* `MedusaError.Types.INVALID_DATA` - Invalid input data
* `MedusaError.Types.NOT_ALLOWED` - Operation not permitted
* `MedusaError.Types.DUPLICATE_ERROR` - Duplicate entry

## Emitting Events

Use `aggregatedEvents` to emit domain events:

```typescript theme={null}
this.aggregatedEvents({
  action: "created",
  object: "brand",
  data: { id: brand.id },
  context: sharedContext,
})
```

The framework automatically formats and emits these events through the event bus.

## Best Practices

* Use `@InjectManager()` for public methods
* Use `@InjectTransactionManager()` for protected methods that modify data
* Always include `@MedusaContext()` parameter for database operations
* Serialize entities before returning them: `this.baseRepository_.serialize()`
* Use the auto-generated CRUD methods instead of reimplementing them
* Throw `MedusaError` for error handling
* Emit events using `aggregatedEvents()` for important state changes
* Keep business logic in services, not in API routes
* Use protected methods (ending with `_`) for internal operations

## Next Steps

<CardGroup cols={2}>
  <Card title="Create Workflows" icon="diagram-project" href="/development/workflows">
    Compose services into workflows
  </Card>

  <Card title="Create Custom Modules" icon="cube" href="/development/custom-modules">
    Package services into modules
  </Card>
</CardGroup>
