Skip to content

Support hiding individual operations #30

Description

@tago-SE

Add filtering logic on collection level to hide endpoints for example as below.

I think this pattern of writing the configuration in the custom section is quite powerful and would allow for more complex manipulation in the future. Such as adding headers, tags, or other customizations.

const userOpenApiConfig: CustomCollectionOpenApiConfiguration = {
  openapi: {
    hidden: false,
    paths: {
      "/api/{slug}": {
        post: {
          hidden: true,
        },
        get: {
          hidden: true,
        },
      },
      "/api/{slug}/{id}": {
        patch: {
          hidden: true,
        },
      },
    },
  },
};

export const Users: CollectionConfig = {
  slug: "user",
  custom: {
    ...userOpenApiConfig,
  },
}

Example filter implementation on collection

const getCollectionOpenApiConfig = (collection: Collection) => {
  const collectionOpenApiConfig = collection.config
    .custom as CustomCollectionOpenApiConfiguration;
  if (
    collectionOpenApiConfig &&
    openApiCollectionConfigKeyName in collectionOpenApiConfig
  ) {
    const updatedPaths = Object.fromEntries(
      Object.entries(collectionOpenApiConfig.openapi.paths).map(
        ([path, methods]) => [
          path.replace("{slug}", collection.config.slug),
          methods,
        ]
      )
    );
    return {
      ...collectionOpenApiConfig.openapi,
      paths: updatedPaths,
    };
  }
  return undefined;
};

export const filterHiddenEndpoints = (
  apiSchema: Record<
    string,
    OpenAPIV3.PathItemObject & OpenAPIV3_1.PathItemObject
  >,
  collection: Collection
) => {
  const openApiConfig = getCollectionOpenApiConfig(collection);
  if (!openApiConfig) {
    return apiSchema;
  }
  for (const [path, methods] of Object.entries(apiSchema)) {
    for (const [method, operation] of Object.entries(methods)) {
      const pathConfig = openApiConfig.paths[path];
      if (!!pathConfig) {
        const methodConfig = (pathConfig as any)[
          method
        ] as OpenApiMethodOptions;
        if (!!methodConfig && !!methodConfig.hidden) {
          console.log(`${path}.${method} - hidden`);
          delete (methods as any)[method];
        }
      }
    }
    if (Object.keys(methods).length === 0) {
      console.log(`${path} - all methods hidden, removing path`);
      delete apiSchema[path];
    }
  }
  return apiSchema;
};

It would also be nice to be able to filter away collections in the PluginConfig, for example if we want to hide all "payload-" collections.

const excludeCollections: (string | RegExp)[] = [
  "sessions",
  new RegExp("^payload-.*"),
];
const filterCollections = (
  req: PayloadRequest,
  excludeCollections: (string | RegExp)[]
) => {
  const collections = Object.values(req.payload.collections).filter((x) => {
    const slug = x.config.slug;
    return !excludeCollections.some((pattern) => {
      if (typeof pattern === "string") {
        return pattern === slug;
      } else {
        return pattern.test(slug);
      }
    });
  });
  return collections;
};

(Bug) Hidden fields are shown

Hidden fields are used in the models. Perhaps for fields one could check field.hidden property when generating the response objects. Haven’t had time to test this aspect of it yet though.

Activity

  1. janbuchar commented on Sep 18, 2026

    @janbuchar
    Owner

    Two of the three parts are done in 0.3.0:

    The main ask is untouched: there is no way to hide a single operation on a collection you otherwise want. Entity-level exclusion was #72; this stays open as the tracker for per-operation control.

  2. changed the title [-]Add filtering logic on collection level to hide endpoints[/-] [+]Support hiding individual operations[/+] on Sep 18, 2026
  3. janbuchar commented on Oct 3, 2026

    @janbuchar
    Owner

    The remaining part — hiding individual operations — is covered by adjustGeneratedSpec (#82), which gets the finished document before it is serialized:

    openapi({
      metadata: { title: 'My API', version: '1.0' },
      adjustGeneratedSpec: spec => {
        delete spec.paths['/api/posts'].post
    
        for (const path of Object.keys(spec.paths)) {
          if (/^\/api\/legacy-/.test(path)) {
            delete spec.paths[path]
          }
        }
      },
    })

    Path keys are built from the slug and routes.api, so they are stable to address this way. That also covers the RegExp exclusion you asked for, as a loop rather than an option.

    Caveat: removing an operation leaves its components behind, so dropping post orphans PostRequestBody. Still valid, but delete the component too if unused models bother you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions