Wp-graphql: Get Single Nodes (posts, terms, users, etc) by more than ID

Created on 16 Oct 2017  路  17Comments  路  Source: wp-graphql/wp-graphql

We should be able to fetch a single term by it's slug, possibly other attributes?

Similar to how we can now get post objects by URI, ID and Global ID.

Architecture Breaking Change Enhancement Needs Discussion Needs Info New Feature

Most helpful comment

The next release I'm working on (#1086) addresses this (the ability to fetch single nodes by various forms of unique identifiers).

Posts

We're deprecating the $postType.'By' entry points, and have added a new idType field and enum for single entry points.

This means queries such as the following will still work, but will not show in the Schema documentation, and may be formally removed from the codebase at a later date:

Deprecated Queries

{
  postBy( slug: "some-slug" ) {
    id
    title
  }
}
{
  pageBy( uri: "some-uri" ) {
    id
    title
  }
}



md5-c6d4746a011ba27ae782804b11738c62



{
  post(id: 1739, idType: DATABASE_ID) {
    id
    title
    uri
    slug
    postId
  }
}



md5-bc831581545daf991e54f021294b1bed



{
  post(id: "/test-5/", idType: URI) {
    id
    title
    uri
    slug
    postId
  }
}



md5-0dcf7135f497d8bab000d14bbdc47a69



{
  post(id: "cG9zdDoxNzM5", idType: ID) {
    id
    title
    uri
    slug
    postId
  }
}



md5-7210c2f1c6c778c9666401ca718781cb



{
  post(id: "cG9zdDoxNzM5") {
    id
    title
    uri
    slug
  }
}



md5-000f991f2ddaa95210d1b7c84564c017



{
  post(id: "test-5", idType: SLUG) {
    id
    title
    uri
    slug
  }
}



md5-b11a4b739ba7ee55e94ef32b4244f508



{
  contentNodes {
    nodes {
      __typename
      id
      title
      link
      uri
      isRevision
      ... on Page {
        isFrontPage
      }
    }
  }
}



md5-f3de54ed5ae0a97baf6316a9baba7040



{
  contentNodes(where: {search: "test"}) {
    nodes {
      __typename
      id
      title
      link
      uri
      isRevision
      ... on Page {
        isFrontPage
      }
    }
  }
}



md5-df0ace421f3b7d69de9548b91584b75a



{
  contentNode(id: "cG9zdDoxNzM5") {
    __typename
    id
    title
    link
    uri
    isRevision
    ... on Page {
      isFrontPage
    }
  }
}



md5-5be7a1e8044ea4d3d40044a78b186b77



{
  contentNode(id: "/2019/12/05/test-5/", idType: URI) {
    __typename
    id
    title
    link
    uri
    isRevision
    ... on Page {
      isFrontPage
    }
  }
}



md5-87845a7367dc998f352e391a985a6d01



{
  contentNode(id: "/test/", idType: URI) {
    __typename
    id
    title
    link
    uri
    isRevision
    ... on Page {
      isFrontPage
    }
  }
}



md5-cf51e4f27f1f84743cd949dd35863fb5



{
  tag(id: "cG9zdF90YWc6Mw==") {
    id
    name
    slug
    tagId
  }
}



md5-c105fb601a376dd541711de3d2db5ed2



{
  tag(id: 3, idType: DATABASE_ID) {
    id
    name
    slug
    tagId
  }
}



md5-5943b966a1c88bcb238fa4af48fc9321



{
  tag(id: "Another Test", idType: NAME) {
    id
    name
    slug
    tagId
  }
}



md5-6bb1b3342d8a2dcfa7c691f84a214b1f



{
  tag(id: "another-test", idType: SLUG) {
    id
    name
    slug
    tagId
  }
}



md5-da0c06836397d71c7f382f2657cff82a



{
  tag(id: "tag/another-test/", idType: URI) {
    id
    name
    slug
    tagId
    uri
  }
}



md5-8268813c2c68e1fce5bbe24f734f089b



{
  terms {
    nodes {
      id
      __typename
      name
      uri
    }
  }
}



md5-24336542e1fbb87f248697cfcc0a9157



{
  terms(where: {search: "test"}) {
    nodes {
      id
      __typename
      name
      uri
      ... on Tag {
        tagId
      }
      ... on Category {
        categoryId
      }
    }
  }
}



md5-90dbae521816525be1442d730f2dae23



{
  termNode(id: "cG9zdF90YWc6Mw==") {
    __typename
    id
    name
    link
    slug
    uri
  }
}



md5-1e59c2fd516581c09d1693af4dfb9824



{
  termNode(id: 3, idType: DATABASE_ID) {
    __typename
    id
    name
    link
    slug
    uri
    databaseId
  }
}



md5-315baa31cbae1abe887bdb0f58559816



{
  termNode(id: "Another Test", idType: NAME, taxonomy: TAG) {
    __typename
    id
    name
    link
    slug
    uri
    databaseId
  }
}



md5-ed21cf4054c4132ee2a3a03d17f702cb



{
  termNode(id: "another-test", idType: SLUG, taxonomy: TAG) {
    __typename
    id
    name
    link
    slug
    uri
    databaseId
  }
}



md5-c7a2ee25271f29585bcea057fdf2350b



{
  termNode(id: "tag/another-test/", idType: URI) {
    __typename
    id
    name
    link
    slug
    uri
    databaseId
  }
}



md5-5c4f6353d411156d2bd1ec80afcd1610



{
  page: nodeByUri(uri: "about/") {
    ...URI
  }
  post: nodeByUri(uri: "2019/12/05/test-5/") {
    ...URI
  }
  tag: nodeByUri(uri: "tag/8bit/") {
    ...URI
  }
  category: nodeByUri(uri: "category/alignment/") {
    ...URI
  }
  user: nodeByUri(uri: "author/jasonbahl/") {
    ...URI
  }
}

fragment URI on UniformResourceIdentifiable {
  __typename
  ... on Page {
    pageId
  }
  ... on Post {
    postId
  }
  ... on Category {
    categoryId
  }
  ... on Tag {
    tagId
  }
  ... on User {
    userId
  }
}

Screen Shot 2020-01-01 at 4 05 25 PM

All 17 comments

We should also think about what other objects we should be able to get by something other than ID. I think Users would probably be another good candidate. The way I'm thinking about it, is anything that would have it's own route in an SPA should be able to be retrieved by slug.

@CodeProKid agreed. Which also makes me think the singular queries for nodes just need to be more flexible. Right now we have post( id: "someId" ) or postBy() which has the following args: id, postId, slug, uri. . .

Ideally, I'd like one singular entry point per node, instead of post(), and postBy(), especially if we're going to add it for each type of node. . .might be best to make a breaking change and make the existing singular entries more flexible where id is not a required input $arg.

I think it would be good to instead support a single where field, similar to how we do connections, and can nest other inputs on it. . .like:

query getNodes ( $postWhere: postWhere!, $userWhere: userWhere ) {  
  post( where: $postWhere ) {
    id
    title
  }
  user ( where: $userWhere ) {
    id
    firstName
    lastName
  }
}
$variables = {
  'postWhere': {
    id: null,
    slug: "some-post",
    uri: null,
    postId: null
  },
  'userWhere': {
      id: null,
      username: null,
      email: "[email protected]"
  }
};

This shape brings some consistency to the shape of the args for singular nodes and connections now, and it gives more flexibility to using variables individually or in combination to narrow the resulting node, and more flexibility to plugins to extend the shape with their own fields.

I imagine all sorts of ways folks would want to get a users (or other nodes) by some unique value, like user(where: {wooCommerceOrderNumber: 123} ) or something to that tune.

Changing the shape of the root singular entries will be a breaking change, but I think now is the time to do it.

Also, the postBy() field only makes semantic sense if there are no additional fields in the schema. . .for example, posts _can_ have passwords, so we will need to add a password input arg, and postBy( password: "123", slug: "some-slug") isn't as nice (to me) as post( where: { slug: "some-slug" }, password: "123456" )

Definitely looking forward to this one! I agree that it feels like a good time to introduce this breaking change ahead of 1.0.

Hi, I wonder if you're still working on this feature?

I'm implementing a SPA of my Wordpress website using WPGraphQL and React. When I go to the blog page I'm trying to filter posts by categories. So I can have the same functionality as in Wordpress.

I haven't found an equivalent to postBy (I thought it would be something like categoryBy), so I ended up using the category ID field to filter posts, which works ok but it makes the URL not so friendly :)

This is what I'm currently doing:
/blog/category/mobile?id=Y2F0ZWdvcnk6Mw%3D%3D

This is what I'd like to have.
/blog/category/mobile

This is my current query:

query GetPostsByCategory($categoryId: ID!) {
    category(id: $categoryId) {
      id
      name
      link
      slug
      posts {
        edges {
          node {
            id
            title
            date
            excerpt
            slug
            author {
              name
            }
            featuredImage {
              sourceUrl
            }
          }
        }
      }
    }
  }

Is there any alternative to do this? Or should I wait until this issue is resolved?

@matiasalvarez87 I do plan on working on this at some point, but I don't have an ETA right now.

For what you're trying to do, you could do this:

query GetCategoryPosts($first: Int, $where: RootCategoriesTermArgs!) {
  categories(first: $first, where: $where) {
    edges {
      node {
        id
        name
        slug
        count
        posts {
          edges {
            node {
              id
              title
            }
          }
        }
      }
    }
  }
}

and variables

{
  "first": 1,
  "where": {
    "slug": "alignment"
  }
}

You can actually test this over at https://playground.wpgraphql.com 馃槃

So now you can get the slug from the route, and use that in your variables.

This is probably better than getting a single node anyway via category { ... } as the edge data will likely come in handy at some point. . .for example, providing next/prev links. . .that's edge data based on the context of the list you're paginating through

Hi @jasonbahl! I was not aware of the WPGraphQL playground, it looks really nice!
The query you sent works. Thanks for your comments.

@jasonbahl I was having a similar issue, and I am using a query close to what you suggested. Which worked perfectly. However, I am not getting any results when I query with a slug that is a child of another term. Any ideas on what I could be missing?

@CorneliusIV can you provide the exact query you're trying (along with variables, if any) and provide some info on the term hierarchy so I can try and replicate the scenario and see what I can find.

@jasonbahl Sure
I have custom Taxonomy called Regions
Under Regions, I have a parent term North America and a child Canada
So:

North America
-- Canada

Using a query very similar to what you suggested

query GetRegionsSlug($first: Int, $where: RootRegionsTermArgs!) {
  regions(first: $first, where: $where) {
    edges {
      node {
        id
        name
        slug
        count
        posts {
          edges {
            node {
              id
              title
              date
              content
            }
          }
        }
      }
    }
  }
}

Variable

{
  "first": 1,
  "where": {
    "slug": "canada"
  }
}

Result:

{
  "data": {
    "regions": {
      "edges": []
    }
  }
}

If I try to search for North America

Variable

{
  "first": 1,
  "where": {
    "slug": "north-america"
  }
}

I get somewhat of an expected result, but posts returns a post that is tagged with Canada

{
  "data": {
    "regions": {
      "edges": [
        {
          "node": {
            "id": "cmVnaW9uczo1NQ==",
            "name": "North America",
            "slug": "north-america",
            "count": null,
            "posts": {
              "edges": [
                {
                  "node": {
                    "id": "cG9zdDo2NzY=",
                    "title": "Test Post",
                    "date": "2018-06-27 15:07:16",
                    "content": ""
                  }
                }
              ]
            }
          }
        }
      ]
    }
  }
}

@CorneliusIV thanks for the info!

So, Test Post is tagged with Canada but _not_ tagged with North America?

@jasonbahl correct

@jasonbahl this seems like something that should happen relatively soon. I know we have talked about changing the shape of the single object queries pretty radically.

Also, fwiw I think the args for getting a single object should be limited to those that are considered "unique" for the object. I know that technically a lot of them aren't, but querying by something like slug (aka guid) seems legit to me.

I think there will also have to be some performance considerations when implementing this. I'm not crazy about doing some of these queries with WP_Query and the like, but I'm not sure that there is a better option.

I think being able to get nodes by permalink/URL would be a really useful feature. For example I'm looking at pulling MediaItem nodes based on URL's found in post content or other html, but I'm not sure I can reliably use the URL to get the URI to make the query.

Sorry, just realized I actually mean querying MediaItem nodes by sourceUrl which is a totally different thing than what I wrote

The next release I'm working on (#1086) addresses this (the ability to fetch single nodes by various forms of unique identifiers).

Posts

We're deprecating the $postType.'By' entry points, and have added a new idType field and enum for single entry points.

This means queries such as the following will still work, but will not show in the Schema documentation, and may be formally removed from the codebase at a later date:

Deprecated Queries

{
  postBy( slug: "some-slug" ) {
    id
    title
  }
}
{
  pageBy( uri: "some-uri" ) {
    id
    title
  }
}



md5-c6d4746a011ba27ae782804b11738c62



{
  post(id: 1739, idType: DATABASE_ID) {
    id
    title
    uri
    slug
    postId
  }
}



md5-bc831581545daf991e54f021294b1bed



{
  post(id: "/test-5/", idType: URI) {
    id
    title
    uri
    slug
    postId
  }
}



md5-0dcf7135f497d8bab000d14bbdc47a69



{
  post(id: "cG9zdDoxNzM5", idType: ID) {
    id
    title
    uri
    slug
    postId
  }
}



md5-7210c2f1c6c778c9666401ca718781cb



{
  post(id: "cG9zdDoxNzM5") {
    id
    title
    uri
    slug
  }
}



md5-000f991f2ddaa95210d1b7c84564c017



{
  post(id: "test-5", idType: SLUG) {
    id
    title
    uri
    slug
  }
}



md5-b11a4b739ba7ee55e94ef32b4244f508



{
  contentNodes {
    nodes {
      __typename
      id
      title
      link
      uri
      isRevision
      ... on Page {
        isFrontPage
      }
    }
  }
}



md5-f3de54ed5ae0a97baf6316a9baba7040



{
  contentNodes(where: {search: "test"}) {
    nodes {
      __typename
      id
      title
      link
      uri
      isRevision
      ... on Page {
        isFrontPage
      }
    }
  }
}



md5-df0ace421f3b7d69de9548b91584b75a



{
  contentNode(id: "cG9zdDoxNzM5") {
    __typename
    id
    title
    link
    uri
    isRevision
    ... on Page {
      isFrontPage
    }
  }
}



md5-5be7a1e8044ea4d3d40044a78b186b77



{
  contentNode(id: "/2019/12/05/test-5/", idType: URI) {
    __typename
    id
    title
    link
    uri
    isRevision
    ... on Page {
      isFrontPage
    }
  }
}



md5-87845a7367dc998f352e391a985a6d01



{
  contentNode(id: "/test/", idType: URI) {
    __typename
    id
    title
    link
    uri
    isRevision
    ... on Page {
      isFrontPage
    }
  }
}



md5-cf51e4f27f1f84743cd949dd35863fb5



{
  tag(id: "cG9zdF90YWc6Mw==") {
    id
    name
    slug
    tagId
  }
}



md5-c105fb601a376dd541711de3d2db5ed2



{
  tag(id: 3, idType: DATABASE_ID) {
    id
    name
    slug
    tagId
  }
}



md5-5943b966a1c88bcb238fa4af48fc9321



{
  tag(id: "Another Test", idType: NAME) {
    id
    name
    slug
    tagId
  }
}



md5-6bb1b3342d8a2dcfa7c691f84a214b1f



{
  tag(id: "another-test", idType: SLUG) {
    id
    name
    slug
    tagId
  }
}



md5-da0c06836397d71c7f382f2657cff82a



{
  tag(id: "tag/another-test/", idType: URI) {
    id
    name
    slug
    tagId
    uri
  }
}



md5-8268813c2c68e1fce5bbe24f734f089b



{
  terms {
    nodes {
      id
      __typename
      name
      uri
    }
  }
}



md5-24336542e1fbb87f248697cfcc0a9157



{
  terms(where: {search: "test"}) {
    nodes {
      id
      __typename
      name
      uri
      ... on Tag {
        tagId
      }
      ... on Category {
        categoryId
      }
    }
  }
}



md5-90dbae521816525be1442d730f2dae23



{
  termNode(id: "cG9zdF90YWc6Mw==") {
    __typename
    id
    name
    link
    slug
    uri
  }
}



md5-1e59c2fd516581c09d1693af4dfb9824



{
  termNode(id: 3, idType: DATABASE_ID) {
    __typename
    id
    name
    link
    slug
    uri
    databaseId
  }
}



md5-315baa31cbae1abe887bdb0f58559816



{
  termNode(id: "Another Test", idType: NAME, taxonomy: TAG) {
    __typename
    id
    name
    link
    slug
    uri
    databaseId
  }
}



md5-ed21cf4054c4132ee2a3a03d17f702cb



{
  termNode(id: "another-test", idType: SLUG, taxonomy: TAG) {
    __typename
    id
    name
    link
    slug
    uri
    databaseId
  }
}



md5-c7a2ee25271f29585bcea057fdf2350b



{
  termNode(id: "tag/another-test/", idType: URI) {
    __typename
    id
    name
    link
    slug
    uri
    databaseId
  }
}



md5-5c4f6353d411156d2bd1ec80afcd1610



{
  page: nodeByUri(uri: "about/") {
    ...URI
  }
  post: nodeByUri(uri: "2019/12/05/test-5/") {
    ...URI
  }
  tag: nodeByUri(uri: "tag/8bit/") {
    ...URI
  }
  category: nodeByUri(uri: "category/alignment/") {
    ...URI
  }
  user: nodeByUri(uri: "author/jasonbahl/") {
    ...URI
  }
}

fragment URI on UniformResourceIdentifiable {
  __typename
  ... on Page {
    pageId
  }
  ... on Post {
    postId
  }
  ... on Category {
    categoryId
  }
  ... on Tag {
    tagId
  }
  ... on User {
    userId
  }
}

Screen Shot 2020-01-01 at 4 05 25 PM

Beautiful! Thanks @jasonbahl

So stoked for this. This is a really great feature!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nikosolihin picture nikosolihin  路  5Comments

TylerBarnes picture TylerBarnes  路  4Comments

jasonbahl picture jasonbahl  路  4Comments

cr101 picture cr101  路  4Comments

saulirajala picture saulirajala  路  3Comments