Skip to content

feat: Allow hooks on all methods and add externalMethods option (#3457)#3638

Closed
marshallswain wants to merge 3 commits intov6from
v6-hooks-all-methods
Closed

feat: Allow hooks on all methods and add externalMethods option (#3457)#3638
marshallswain wants to merge 3 commits intov6from
v6-hooks-all-methods

Conversation

@marshallswain
Copy link
Member

@marshallswain marshallswain commented Jan 10, 2026

Summary

This PR implements the feature requested in #3457, allowing hooks to run on all service methods while providing fine-grained control over which methods are exposed externally.

Changes

@feathersjs/feathers

Type Declarations (declarations.ts)

  • Updated methods description to clarify it controls which methods all hooks apply to
  • Added new externalMethods option to control HTTP transport exposure

Service (service.ts)

  • Added getExternalMethods() function (defaults to methods for backwards compatibility)
  • Updated normalizeServiceOptions() to include externalMethods

Hooks (hooks.ts)

  • Added allMethods to hook store to track which methods receive all hooks
  • Modified collectHooks() to only include all hooks for methods in allMethods
  • Updated hookMixin() to allow lazy creation of hook managers for any service method

HTTP Transport (http/index.ts)

  • Updated to use getExternalMethods() for HTTP access control

Tests

  • Added tests for lazy hook registration on unconfigured methods
  • Added tests for all hooks scoping
  • Added tests for externalMethods HTTP behavior

Features

1. Individual method hooks work without configuration

// Can register hooks on ANY method, even if not in `methods`
service.hooks({
  helperMethod: [myHook]  // works!
})

methods controls which methods receive all hooks

app.use('myService', new MyService(), {
  methods: ['find', 'get', 'create']
})

service.hooks({
  around: {
    all: [loggingHook]  // only runs on find, get, create
  }
})

3. externalMethods controls HTTP exposure

app.use('myService', new MyService(), {
  methods: ['find', 'get', 'create', 'internalAction'],  // all get hooks
  externalMethods: ['find', 'get', 'create']  // only these exposed via HTTP
})

Backwards Compatibility

  • If externalMethods is not specified, it defaults to methods
  • Existing code works without modification
  • All 354 existing tests pass

Related

- Individual method hook chains now work without prior configuration
- Add externalMethods option to control HTTP/transport exposure
- methods option now controls which methods receive 'all' hooks
- Backwards compatible: externalMethods defaults to methods
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jan 10, 2026

Deploying feathers-eagle with  Cloudflare Pages  Cloudflare Pages

Latest commit: 09bb238
Status: ✅  Deploy successful!
Preview URL: https://2c692814.feathers-a8l.pages.dev
Branch Preview URL: https://v6-hooks-all-methods.feathers-a8l.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jan 10, 2026

Deploying feathers-dove with  Cloudflare Pages  Cloudflare Pages

Latest commit: 09bb238
Status: ✅  Deploy successful!
Preview URL: https://09e7e607.feathers.pages.dev
Branch Preview URL: https://v6-hooks-all-methods.feathers.pages.dev

View logs

@daffl
Copy link
Member

daffl commented Jan 10, 2026

I'm wondering if we need to add a new API if we are promoting using decorators going forward. For internal methods you need to list the parameter names of the method anyway to make it useful (to have things like context.message).

This already works and lets you register hooks on any method:

import { hooks } from 'feathers'

class MyService {
  @hooks([]).params('message')
  internalMethod(message: string) {}
  
  @hooks([])
  find() {
  }
}

@marshallswain
Copy link
Member Author

There are still use cases where decorators will not work well, like when using built-in services from a database manager instance. It's a bit verbose to do custom adapter classes just to be able to add hooks.

@marshallswain
Copy link
Member Author

I forgot about the .params() calls

@daffl
Copy link
Member

daffl commented Jan 11, 2026

Wouldn't that work with the object wrapper?

import { hooks } from 'feathers'
import { MemoryService } from 'feathers-memory'

const messageService = hooks(new MemoryService(), {
  myMethod: middleware([]).params('message')
})

app.use('messages', messageService)

@marshallswain marshallswain marked this pull request as draft January 28, 2026 07:12
marshallswain added a commit that referenced this pull request Jan 28, 2026
- 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants