5

I’m extending a REST endpoint with register_rest_field();

The field I’m registering should accept an email address or an empty string.

I’m using the JSONschema to check and validate the format, like the below code:

register_rest_field(
    'my_custom_post', 
    'my_email_field', 
    array(
        'schema' => array(
        'type' => array('string','null'),
            'format' => 'email',
        'description' => 'Email address to use for contact',
        'context' => array('view','edit','embed'),
         ),
     'get_callback' => 'myemail_custom_get_callback',
     'update_callback' => 'myemail_custom_update_callback'
     )
);

The issue I have is that even if I put in the schema 'type' => array('string','null') I can’t POST a request with this field left empty, because WP always respond with rest_invalid_param and rest_invalid_email .

Only if I remove the'format' => 'email' argument, WP accepts both an empty field or a string as expected, but this way I can’t check if it’s a valid email anymore.

By reading here https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/#format (especially the last example in that paragraph) I can’t see whats wrong with my schema. Could it be a bug?

2 Answers 2

4

even if I put in the schema 'type' => array('string','null') I can’t POST a request with this field left empty, because WP always respond with rest_invalid_param and rest_invalid_email

Actually, adding the 'null' there does not mean that any empty-ish values like '' and 0 will be allowed. It just means that an actual null value is allowed, apart from string. E.g. (these are JSON-encoded data)

  • {"my_email_field":"[email protected]"}
  • {"my_email_field":null}
  • {"my_email_field":""} ❌ (value is empty, but it's not a null)
  • {"my_email_field":"null"} ❌ (invalid email address..)

So if you had sent a null, you would have not received those errors.

But then, a null will unfortunately not pass the isset( $request[ $field_name ] ) validation in WP_REST_Controller::update_additional_fields_for_object(), hence the update callback will never be called. (See source on GitHub)

  • So maybe the WordPress core team should do something about that.. e.g. use array_key_exists() instead..

But as stated in @birgire's answer, you can use the anyOf or oneOf property to achieve what you were trying to do. (See source on GitHub)

Here's an example which requires the value to be an empty string where the length is exactly 0, or a non-empty string which must be a properly-formatted email address:

'schema' => array(
    'type'        => 'string',
    'oneOf'       => array(
        // Requires an empty string.
        array(
            'type'      => 'string',
            'maxLength' => 0,
        ),
        // Or if it's not empty, then it must be a valid email address.
        array(
            'type'   => 'string',
            'format' => 'email',
        ),
    ),
    'description' => 'Email address to use for contact',
    // Your other args.
),

Another one that you might want to try is, a custom validate callback which can be passed via arg_options.validate_callback like so:

  • Supplying the custom validate callback:

    'schema'          => array(
        'type'        => 'string',
        'arg_options' => array(
            'validate_callback' => 'myemail_custom_validate_callback',
        ),
        'description' => 'Email address to use for contact',
        // Your other args.
    ),
    
  • Sample callback:

    function myemail_custom_validate_callback( $value, $request, $param ) {
        // Requires an empty string or a valid email address.
        if ( 0 === strlen( $value ) || is_email( $value ) ) {
            return true;
        }
    
        return new WP_Error(
            'rest_invalid_param',
            sprintf( '%s is not a valid email address', $param )
        );
    }
    

Note: Specifying a custom validate callback means the default/core ones (e.g. rest_validate_value_from_schema()) would no longer be used, unless explicitly called from within your callback.

7
  • Great, thanks for checking it in source, as I wrote my answer on mobile I trusted the line I stumbled up on in the linked docs, mentioning "If allowing an “empty” value is desired, add null as a possible type." :-)
    – birgire
    Commented Jan 19 at 7:29
  • 1
    Yes, I noticed that too, but the quotes as in "empty" made me curious (that urged me to check the source) and think that maybe what they meant was that a null will not throw the errors in question? Or maybe, in the past, null would still be accepted in that the field's update callback would be called.
    – Sally CJ
    Commented Jan 19 at 7:37
  • yes, I remember I thought by the quote it could behave like the empty() function validation.
    – birgire
    Commented Jan 19 at 7:52
  • @birgire yep, I had the same thought, until I tried the code in question. Hence, I checked the source when I realized nothing was being updated. :)
    – Sally CJ
    Commented Jan 19 at 8:49
  • @brigire @sally-cj thank you for yours helpful answer. My current implementation relies on custom validate callback but I would like to streamline my code transitioning that validation to the schema. I had not thought to the use of oneOf that indeed looks like a valid solution. In the meantime a come up with another possible solution: it involved specifying null on both format and type. I'll post it as another aswer, do you mind looking at it and tell me what you think about it?
    – Stefano
    Commented Jan 19 at 8:54
3

It looks like is_email() will return false for input less than 6 chars according to docs. Try instead for example anyOf or oneOf in the schema to separate the null type and the is_email check: (untested)

array(
    'schema' => array(
        'anyOf' => array(
            array(
                'type'   => 'string',
                'format' => 'email',
                …etc
            ),
            array(
                'type' => 'null',
                …etc
            ),
          ),
       ),
    ),
),

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.