GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — feat: Enhanced custom methods with @method decorator by marshallswain · Pull Request #3642 · feathersjs/feathers

Conversation

@marshallswain

@marshallswain marshallswain commented Jan 28, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

This PR adds enhanced custom method support for Feathers v6, allowing custom methods to be first-class citizens with flexible signatures, proper HTTP verb mapping, and clean URLs.

Features

  • @method decorator for configuring custom service methods
  • Different argument signatures (not just data, params)
  • Different HTTP verbs (GET, POST, PUT, PATCH, DELETE)
  • Clean URL paths (e.g., /messages/:id/status instead of X-Service-Method header)
  • Internal-only methods with external: false
  • clientMethods() helper for client configuration

Example

import { feathers, method } from '@feathersjs/feathers'

class MessageService {
  @method({ args: ['id', 'params'], http: 'GET', path: ':id/status' })
  async status(id: Id, params: Params) {
    return { id, status: 'active' }
  }

  @method({ args: ['id', 'params'], http: 'POST', path: ':id/archive' })
  async archive(id: Id, params: Params) {
    return this.patch(id, { archived: true }, params)
  }

  @method({ args: ['data', 'params'], external: false })
  async internalProcess(data: any, params: Params) {
    return { processed: data }
  }
}

app.use('messages', new MessageService())
// GET /messages/123/status -> status('123', params)
// POST /messages/123/archive -> archive('123', params)

Documentation

  • Updated services documentation with full @method decorator guide
  • Updated REST client documentation with custom method paths
  • Added plan document at docs/v6-custom-methods-plan.md

Tests

All 405 tests pass.

Related

  • Closes Custom methods the Feathers way #1976
  • Replaces feat: Allow hooks on all methods and add externalMethods option (#3457) #3638
  • Similar WIP: Dynamic Method Definitions #3339

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@marshallswain
feat: Enhanced custom methods with @method decorator
d4b41ac
- Add @method decorator for configuring custom service methods
- Support different argument signatures (not just data, params)
- Support different HTTP verbs (GET, POST, PUT, PATCH, DELETE)
- Support clean URL paths (e.g., /messages/:id/status)
- Support internal-only methods with external: false
- Add buildMethodConfig() helper for client configuration
- Add InferServiceTypes type helper for typed clients
- Update REST client to support custom method paths and verbs
- Update services and REST client documentation

The @method decorator allows custom methods to be first-class citizens
with flexible signatures, proper HTTP verb mapping, and clean URLs
instead of relying on X-Service-Method headers.

Closes #1976
Replaces #3638
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jan 28, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
feathers-v6 d691de1 Jan 30 2026, 03:17 PM
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

marshallswain added 2 commits January 28, 2026 15:07
@marshallswain
fix: access route data property correctly for conflict detection
fb06f63
@marshallswain
refactor: Replace buildMethodConfig with clientMethods helper
d691de1
- Add clientMethods() to filter and prepare method configs for client
- Remove buildMethodConfig(), getClientMethodConfig(), InferServiceTypes
- clientMethods() filters out external:false methods and strips server-only properties
- Types come from service class definitions, not runtime config
- Remove any types from method.ts, use proper typing
- Add custom methods type test to declarations.test.ts
- Update documentation for new pattern
@marshallswain

marshallswain commented Jan 29, 2026

Copy link
Copy Markdown
Member Author

Update: Improved client configuration API**

Changed how method configuration is passed to the client to avoid bundling server-side code.

Before (problematic):

// This required importing service CLASSES, bundling server code into the client
import { buildMethodConfig, type InferServiceTypes } from '@feathersjs/feathers'
import { MessageService } from './services/messages.service'

const services = { messages: MessageService }
export const serviceMethods = buildMethodConfig(services)  // instantiated classes internally
export type ServiceTypes = InferServiceTypes<typeof services>

After (secure):

// Only imports the static methods CONFIG object, not the class implementation
import { clientMethods } from '@feathersjs/feathers'
import { MessageService } from './services/messages.service'

export const serviceMethods = clientMethods({
  messages: MessageService.methods  // just the static property
})

export type ServiceTypes = {
  messages: MessageService  // types defined separately
}

What clientMethods() does:

  • Filters out external: false methods (internal-only, shouldn't be exposed)
  • Filters out methods without path (standard CRUD doesn't need config)
  • Strips server-only properties (external, event) — only keeps args, http, path

Key insight: Types come from the service class definition itself. The runtime config (clientMethods()) only tells the HTTP client which verb/path to use — it doesn't affect TypeScript types at all.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@marshallswain
marshallswain force-pushed the v6-custom-methods branch from a5673bd to d691de1 Compare January 30, 2026 15:15
@marshallswain marshallswain closed this Jan 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@marshallswain
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.