Wagtail API not exposing custom field

Question:

I am enabling wagtail api v2 in my wagtail project. After adding the api.v2 I am getting this json response

    {
        "id": 32,
        "meta": {
            "type": "blog.AddStory",
            "detail_url": "http://localhost/api/v2/pages/32/",
            "html_url": "http://localhost/blog/1st-story/",
            "slug": "1st-story",
            "first_published_at": "2022-07-19T18:06:50.205210Z"
        },
        "title": "1st story"
    }

I want to add my content field after title field. I added this in my models.py file

from wagtail.api import APIField
from rest_framework import serializers
#Exposing Custom Page Fields/content
api_fileds = [
  APIField('content'),
  APIField('custom_title'),
  APIField('blog_image'),
]

Here is my api.py file

from wagtail.api.v2.views import PagesAPIViewSet
from wagtail.api.v2.router import WagtailAPIRouter
from wagtail.images.api.v2.views import ImagesAPIViewSet
from wagtail.documents.api.v2.views import DocumentsAPIViewSet

api_router = WagtailAPIRouter('wagtailapi')

api_router.register_endpoint('pages', PagesAPIViewSet)
api_router.register_endpoint('images', ImagesAPIViewSet)
api_router.register_endpoint('documents', DocumentsAPIViewSet)

Why it’s not exposing custom fields? Here is my full models.py file

"""Blog listing and blog detail pages."""
from django.db import models
from modelcluster.fields import ParentalManyToManyField
from django import forms
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel, MultiFieldPanel,InlinePanel
from wagtail.core.fields import StreamField
#Api Section import
from wagtail.api import APIField
from rest_framework import serializers

from wagtail.images.edit_handlers import ImageChooserPanel

from wagtail.core.models import Page
from wagtail.snippets.models import register_snippet

from . import blocks
from .blocks import InlineVideoBlock

from modelcluster.fields import ParentalKey
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase


@register_snippet
class BlogCategory(models.Model):
    """Blog category for a snippet."""

    name = models.CharField(max_length=255)
    slug = models.SlugField(
        verbose_name="slug",
        allow_unicode=True,
        max_length=255,
        help_text='A slug to identify posts by this category',
    )

    panels = [
        FieldPanel("name"),
        FieldPanel("slug"),
    ]

    class Meta:
        verbose_name = "Blog Category"
        verbose_name_plural = "Blog Categories"
        ordering = ["name"]

    def __str__(self):
        return self.name


class CreateNewPage(Page):
    """Listing page lists all the Blog Detail Pages."""

    # template = "blog/create_new_page.html"

    custom_title = models.CharField(
        max_length=100,
        blank=False,
        null=False,
        help_text='Overwrites the default title',
    )

    content_panels = Page.content_panels + [
        FieldPanel("custom_title"),
    ]

    def get_context(self, request, *args, **kwargs):
        """Adding custom stuff to our context."""
        context = super().get_context(request, *args, **kwargs)

        context["categories"] = BlogCategory.objects.all()
        if request.GET.get('category'):
            context["posts"] = AddStory.objects.live().public().filter(
                categories__slug__in=[request.GET.get('category')])
        else:
            context["posts"] = AddStory.objects.live().public()
        # context["all_posts"] = AddStory.objects.live().public()
        all_posts = AddStory.objects.live().public().order_by('-first_published_at')
        if request.GET.get('tag', None):
            tags = request.GET.get('tag')
            all_posts = all_posts.filter(tags__slug__in=[tags])

        context["posts"] = all_posts
        return context


class BlogPageTag(TaggedItemBase):
    content_object = ParentalKey(
        'AddStory',
        related_name='tagged_items',
        on_delete=models.CASCADE
    )


class AddStory(Page):
    """Blog detail page."""

    custom_title = models.CharField(
        max_length=100,
        blank=False,
        null=False,
        help_text='Overwrites the default title',
    )
    blog_image = models.ForeignKey(
        "wagtailimages.Image",
        blank=False,
        null=True,
        related_name="+",
        on_delete=models.SET_NULL,
    )

    content = StreamField(
        [

            ("full_richtext", blocks.RichtextBlock()),
            ("simple_richtext", blocks.SimpleRichtextBlock()),
            ("Add_video", blocks.VideoRichtextBlock()),
            ('video', InlineVideoBlock()),

        ],
        null=True,
        blank=True,
    )
    tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
    categories = ParentalManyToManyField("blog.BlogCategory", blank=True)
    content_panels = Page.content_panels + [
        FieldPanel("custom_title"),
        ImageChooserPanel("blog_image"),
        StreamFieldPanel("content"),
        FieldPanel('tags'),
        MultiFieldPanel(
            [
                FieldPanel("categories", widget=forms.CheckboxSelectMultiple),

            ],
            heading="Categories"
        ),

    ]
    #Exposing Custom Page Fields/content
    api_fileds = [
      APIField('content'),
      APIField('custom_title'),
      APIField('blog_image'),
    ]

class VideoBlogPage(AddStory):
    """A video subclassed page."""

    template = "blog/video_blog_page.html"

    youtube_video_id = models.CharField(max_length=30)

    content_panels = Page.content_panels + [
        FieldPanel("custom_title"),
        ImageChooserPanel("blog_image"),
        # MultiFieldPanel(
        #     [
        #         InlinePanel("blog_authors", label="Author", min_num=1, max_num=4)
        #     ],
        #     heading="Author(s)"
        # ),
        MultiFieldPanel(
            [
                FieldPanel("categories", widget=forms.CheckboxSelectMultiple)
            ],
            heading="Categories"
        ),
        FieldPanel("youtube_video_id"),
        StreamFieldPanel("content"),
    ]

Answers:

Per the docs:

By default, only a subset of the available fields are returned in the
response. The ?fields parameter can be used to both add additional
fields to the response and remove default fields that you know you
won’t need.

Try adding ?fields=* to the url you are using to retrieve your page data.

Answered By: cnk

I have found my answer, those custom fields was exposing in details page. To get all details in one URL you can short like this
http://127.0.0.1:8000/api/v2/pages/?type=blog.AddStory&fields=*
it will expose all fields but if you want like only content & tags make your URL like this
http://127.0.0.1:8000/api/v2/pages/?type=blog.AddStory&fields=content,tags