Skip to content

feat: Add custom endpoints#53

Open
maevic wants to merge 3 commits into
janbuchar:masterfrom
maevic:add-custom-endpoints
Open

feat: Add custom endpoints#53
maevic wants to merge 3 commits into
janbuchar:masterfrom
maevic:add-custom-endpoints

Conversation

@maevic
Copy link
Copy Markdown
Contributor

@maevic maevic commented Aug 22, 2025

This PR adds custom endpoints to the schema, if the custom endpoint contains a custom.openapi property. It exports a new type for the user to help with writing the schema.

@janbuchar
Copy link
Copy Markdown
Owner

Hi @maevic and thanks for your contribution, can you please elaborate on the relationship of this PR with #47?

@maevic
Copy link
Copy Markdown
Contributor Author

maevic commented Aug 26, 2025

Hi, I think #47 is about injecting arbitrary paths into the openapi output. This PR is about the custom endpoints feature of Payload: https://payloadcms.com/docs/rest-api/overview#custom-endpoints

The ones that are added directly to a collection's config.

@maevic maevic force-pushed the add-custom-endpoints branch from e3ffca3 to add12a0 Compare September 27, 2025 10:03
Copy link
Copy Markdown
Owner

@janbuchar janbuchar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks solid at first glance, some tests would be much appreciated

Comment thread dev/collections/Pets.ts Outdated
},
},
},
} as CustomEndpointDocumentation<'3.1'>,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't feel right to require users to make a cast for type safety - perhaps a utility function would provide better DX?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea. I will implement it later

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I added a utility function to avoid the type cast. Let me know if you had something different in mind

Comment thread dev/collections/Pets.ts
required: ['name'],
},
responses: {
200: {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if the user could reference the various generated OpenAPI schemas here. Will a simple $ref work here? Can you add a test that verifies that please?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I understand you correctly. The generated OpenAPI schemas aren't yet available here, as this definition will be used to generate them.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you know the correct component name, you can write a ref inside your custom endpoint definition - $ref: "#/components/schemas/UserResponse" or something like that. My question is if this will work as expected

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification 👍 That should be possible. I'll add a test for it

Copy link
Copy Markdown

@scastlara scastlara Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what is worth I tested this branch on my project, and in order for this to work (using $ref to existing definitions), I needed to change this in generateCollectionCustomEndpointResponses:

const generateCollectionCustomEndpointResponses = (
  collection: Collection,
): Record<string, OpenAPIV3_1.ResponseObject & OpenAPIV3.ResponseObject> => {
      ...
      for (const [statusCode, resp] of Object.entries(documentation.responses || {})) {
        ...
        responses[key] = {
          description: `Custom endpoint response for ${singular} with method ${endpoint.method} and status code ${statusCode}`,
          content: {
            'application/json': {
              schema: resp.$ref
                ? composeRef('schemas', resp.$ref) // added this
                : composeRef('schemas', singular, {
                    suffix: `CustomEndpoint${upperFirst(endpoint.method)}${statusCode}`,
                  }),
            },
          },
        }
      }
    }
  }

  return responses
}

Otherwise, I could not get a ref working at all.

Now, I may be doing something dumb; I don't really know well how this package (or this PR) works.

With that change, I could reference the User object by doing this in the custom openapi section:

 responses: {
            200: {
              "$ref": "User"
            },
          },

UserResponse does not work.

Comment thread src/types.ts Outdated
? OpenAPIV3_1.SchemaObject
: OpenAPIV3.SchemaObject

export interface CustomEndpointDocumentation<TVersion extends OpenAPIVersion = '3.1'> {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit lost here. Does this still support both 3.0 and 3.1? Maybe we should enforce 3.1 syntax and convert it if the plugin is configured for 3.0?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a type generic for the user to specify what OpenAPI version he's using. If he omits this, 3.1 is used (for the types), but he has the possibility to specify 3.0 as well. The same way he needs to specify the version in the plugin config. Although I just noticed that 3.0 is the default there.

So yes, it still supports both 3.0 and 3.1. I would change it to default to 3.0, so that it's in sync with the default config version.

The plugin configuration uses 3.0 as the default version, so let's stick with that.
@mrjasonroy
Copy link
Copy Markdown

Can I help push this PR along at all? happy to add tests, etc?

@janbuchar
Copy link
Copy Markdown
Owner

Can I help push this PR along at all? happy to add tests, etc?

Hi @mrjasonroy, thanks for the initiative 🙂 Adding some basic tests that focus on the intended functionality would help for sure. Also, giving the PR a test run or reviewing the code would be much appreciated.

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.

4 participants