Anyone knows a good idea to attach extra info to field definitions?
The goal is to be able to read that information through a plugin.
E.g. something like:
{
name: 'textArea',
type: 'textarea',
extraInfo: {
someKey: "val"
}
},
So I can do stuff like:
Find all fields of a
CollectionConfigwhich have value XX for key XY
It's possible to "mark" fields using hooks like @exo3718 did in his plugin here:
https://github.com/TimHal/pcms-backpop/blob/main/demo/src/collections/Foo.collection.ts#L22which is then "read out" here:
https://github.com/TimHal/pcms-backpop/blob/main/demo/src/backpopulated-relationship.plugin.ts#L17But I cannot add any extra information to that which I can later extract
This PR would help here:
https://github.com/payloadcms/payload/pull/2449I created a pr for exactly this 2 days ago:
https://github.com/payloadcms/payload/pull/2436, after creating a feature request 2 weeks ago here:
https://github.com/payloadcms/payload/discussions/2369Thank you!! Just closed my PR. I completely missed that one. Funny that we both thought of making the same PR in a span of 2 days (I didn't even know yours existed) when this feature was missing for years
Really looking forward to it getting merged!
yes. I need it for my payload-swagger plugin to properly document custom endpoint, but thought it wouldn't hurt to also add it to the other levels in the config.
for instance for adding example data
Definitely, thank you for that! So you wanna allow users to document their own custom endpoint? Or use it inside your own plugin to programmatically mark stuff or something like that?
I'd like for the developer of an endpoint to be able to document it. I can already derive the endpoint, method and route parameters, but I cannot e.g. generate a proper description, supported query parameters, returntype. And you, what did you intend to use this for?
That's interesting! Makes sense since endpoints don't have a field like "description".
I'd like to use it for my new
https://github.com/AlessioGr/payload-plugin-aiplugin. Currently using hooks to mark fields where "embeddings" should be generated for a field after it's been saved. Apart from being ugly, I also want to let users specify additional options. E.g. should the new embeddings field be hidden or not?
So something like
custom: {
...genEmbeddings({hidden: false}),
}
instead of just
hooks: {beforeChange: [genEmbeddings]}
Or for the future, allow users to run GPT prompts for fields. E.g.
custom: {
...runPrompt({
when: 'update',
prompt: """
Summarize this text:
%fieldvalue%
""",
outputField: '%fieldname%_summary'
}),
}
So, would be quite a useful feature to me 😄
Nice one 🙂
I see that using this custom field would be nice, but since you are already looping through all the fields, you can already just add a field and then just remove it in your plugin. Also, did you know that since your hook is a function, you can allready add properties to it and the joy validation does not fail on it or strip them?
did you know that since your hook is a function, you can allready add properties to it and the joy validation does not fail on it or strip them?
Hmm could you make an example? I experimented with it a lot. Only solution I found was returning the data I wanna specify from the hook. But that way I need to call the hook of every single field which is not optimal
you can already just add a field and then just remove it in your plugin
Wouldn't that have typescript and/or joy complain?
I think this would be the way to go for you:
type EmbeddingsHook = FieldHook & Required<Options>;
type AllowedField = TextField; // assuming not all fields support this
export const genEmbeddings = ({ hidden = false }: Options = {}): EmbeddingsHook => {
const hook = (args => {
return args.value;
}) as EmbeddingsHook;
// So here we add the options to the hook. You'll be able to read them in your plugin, yup ignores them
hook.hidden = hidden;
return hook;
};
// Users of your plugin can use the below function as a wrapper where they create the field.
// The typesystem just sees a field that is returned from the function and expects a function, so you're good there
export const createEmbeddingsField = (field: AllowedField & { options: Options }): Field => {
const { options, ...payloadField } = field;
return {
...payloadField,
hooks: {
...field.hooks,
beforeChange: [...(field.hooks?.beforeChange || []), genEmbeddings(options)],
},
};
};
Of course if you have the
custom
field you don't need to use the trick of adding the options to the function. But then only users with a Payload version higher than the one where that get's merged can use your plugin.
Alternatively you can just cleanup the config in your plugin and take the approach below:
You could do this, but that's not ideal, but users of you plugin wouldn't be able to do use multiple libraries with config extensions at the same time.
import { CollectionConfig as PayloadCollectionConfig, Field as PayloadField } from 'payload/types';
export type Field = PayloadField & { description: string };
export type CollectionConfig = Omit<PayloadCollectionConfig, 'fields'> & { fields: Field[] };
You can solve this by passing the config types as generic params, but it's a bit ugly
import { CollectionConfig as PayloadCollectionConfig, Field as PayloadField } from 'payload/types';
export type Field<BaseField extends PayloadField = PayloadField> = BaseField & { description: string };
export type CollectionConfig<BaseCollection extends PayloadCollectionConfig = PayloadCollectionConfig> = Omit<BaseCollection, 'fields'> & { fields: Field[] };
Hope this makes sense
Thank you!!! Those are very creative solutions! And people say programmers aren't creative... The wrapper for creating a function is certainly an interesting solution.
Honestly I'm not sure what to prefer. The first one with the wrapper for the field itself might be a bit annoying for the user. Especially when eventually there would be multiple plugins doing that. Similar to the if-condition hell (if(){if(){if(){ } } })
Last one would be nicer - but then I'd rather wait for your PR to be approved. In the end, it would have the same effect
Don't understand what you think would cause an
if-condition hell
. You could just use it like
createEmbeddingsField(createDocumentedField(createFancyField({ type: 'text', name: 'bla' })))
Yeah that's what I mean - nested functions like these. I think those would make it look quite confusing/complex, especially when there are multiple functions and plugins like in your example.
Also not sure if there's any benefit compared to your
custom
field
It's just a basic decorator pattern. Don't think that's confusing. Maybe naming can be better. Like
withEmbeddings(withDocumentation(withFancyStuff({ type: 'text', name: 'bla' })))
It can just be used like this:
withEmbeddings(withDocumentation(withFancyStuff({ type: 'text', name: 'bla', hidden: true, description: 'bla bla', fancy: 'something' })))
And I don't think this is instead of the
custom
field. When that field is available, you can still apply this pattern, but then instead of putting the
hidden
property on the function, you remap it to the custom object.
Hm I'm trying to implement it like that right now, but it doesn't allow me to do the following things:
- Return multiple fields (well it does, but then you cannot combine multiple of those)
- Modify another field to add the hook to it
Imma try extending the collectionconfig! Hope that doesn't make joi go mad during validation 😅
hm and yep, that 2nd solution does cause joi to complain
Star
Discord
online
Get help straight from the Payload team with an Enterprise License.