[MYSTERY SOLVED] REST API: How to simply GET a record usinf frappe-ui?

import { createResource } from 'frappe-ui'
let todo = createResource({
        url: ???

Using frappe v15-dev (v16), I tried frappe.Desk.ToDo.get_list and ToDo/ and other things.
According to frappe-ui, it needs an url, while createListResource doesn’t.

Can someone please let me know how to get a ToDo record, knowing it’s name (0489l150rq)?

Just do a get request to an API URL like:

http(s)://yourerpnextserver/api/resource/Customer

which returns something like:

{
    "data": [
        {
            "name": "Grant Plastics Ltd."
        },
        {
            "name": "West View Software Ltd."
        },
        {
            "name": "Palmer Productions Ltd."
        },
        {
            "name": "Imkerpartner Muhammad Ali"
        },
        {
            "name": "Imker Huckepack"
        }
    ]
}

You can use the python library requests which is easy to use.

In the request you need to set some headers for authentication (reflecting the values you enter for the ERPNExt user, in the name of whom you want the request to work) and format:

import requests
api_url = "http(s)://yourerpnextserver:maybewithport/api/"
url = api_url + "resource/Module Def"
headers = {
    "authorization": f"token {api_key}:{api_secret}",
    "accept": "application/json",
    "content-type": "application/json"
}
_querystring = {"limit":"1000", "fields": '["module_name","app_name","custom","package","restrict_to_domain"]'}
response = requests.get(url, headers=headers, params=_querystring)

_j = response.json()

print(_j)

Just a simple python thing, which you can use from any host allowing you to use python.

Thanks @Peer but I’d like to use the frappe-ui library. Any idea how to do it?

The infos seem to be here:
Getting Started | Frappe UI
and here:
GitHub - netchampfaris/frappe-ui-starter: A Vite + Vue 3 + TailwindCSS + Frappe UI starter template for building frontends for Frappe Apps

and then here:
Resource | Frappe UI

but so far I never used it myself.

Unfortunately, the doc at frappe-ui and starter are old (2022).
It looks like REST API v2 is not documented yet (finalized in Nov 2023 though), and I don’t know which frappe version frappe-ui can handle.

The frappe-ui looks somewhat more high-level to me (than python requests which are just … REST API), but it (frappe-ui) might be more difficult to use if you start from scratch.

So I’d say you could still use my script to get used to the REST API in itself, and then start with frappe-ui with a better understanding of the underlying API.

But not knowing what you know and don’t know, that’s all up to you of course.

Another strategy would be to find some sample app (e.g. a real frappe app which works, which you know, and which you have on github) and then study it to see what’s needed and what’s still missing in your own code.

Or maybe try Hussains Videos:
VueJS, TailwindCSS & FrappeUI Training - Day 1 | DOM Manipulation & VueJS Basics
VueJS, TailwindCSS & FrappeUI Training - Day 2 | VueJS with API & TailwindCSS Basics
VueJS, TailwindCSS & FrappeUI Training - Day 3 | Getting started with FrappeUI

That’s the one I’m following and it misses the ONE record vue between Day 4 and Day 5.

Currently, I’m getting a new error:
You are not permitted to access this resource.Function frappe.get_list is not whitelisted

So, now, my question is how to whitelist frappe.get_list?
Only example in frappe-ui is about sendEmail which is a POST request.
How to whitelist a GET method?

You could try a method from these:

Script API

Still same issue:
frappe.exceptions.PermissionError: You are not permitted to access this resource.Function frappe.get_doc is not whitelisted.

In the logs, I have:
"GET /api/method/frappe.get_doc?doctype=ToDo&name=0489l150rq " 403

I’m pretty sure reading ToDos using get_doc is whitelisted,

Creating them is whitelisted in frappe-bench/apps/frappe/frappe/desk/doctype/todo/todo.py:

@frappe.whitelist()
def new_todo(description):
	frappe.get_doc({"doctype": "ToDo", "description": description}).insert()

get_list is whitelisted and is the only example used in frappe-ui createResource:
https://ui.frappe.io/story/docs-resources-resource-story-js

So , what’s wrong with get_doc???

See the sequence around https://youtu.be/nnnfMc7h5wE?t=4223
We have the id (route.params.id), the comment is entered to load the record, then … let’s do something else.

Additionally, since you simply read the id from the URL, this mean that anyone can brute force with forging multiple URLs to read more records.

Well, unless there is some other setup needed, like authorization key/secret.

The routing etc. might contain mechanisms against abuse, at least the framework should protect somehow against it.

Maybe @buildwithhussain can help with the missing bit?

It’s been a while since I used FrappeUI, but doesn’t createResource just poll any arbitrary endpoint and parse the JSON that gets returned? Are you getting errors when you call something like this?

const todo = createResource({
   url: '/api/resource/ToDo/0489l150rq'
})

OKAY, I fixed it.
I misunderstood multiple things:

  • when you want to get_doc, you get_list instead. Yeah, a list of one record. In this realm get_doc doesn’t work, period. The reply about whitelist is misleading since it’s not available at all from there.
  • despite being clearly a GET request called get_list, it’s a POST, so forcing method: 'GET' (which is allowed but not working) didn’t help.
  • frappe.get_list doesn’t exist in this vue realm, but frappe.client.get_list does. I thought client was an example of the called app/module, it’s actually another endpoint made for the JS client, AFAIU.
  • filters:'{name: route.params.id}' as in the doc, actually breaks frappe (Internal Server Error)
  • you actually don’t even need to read the id from the URL (but it will do it internally), so remove import { useRoute } from 'vue-router' and route = useRoute() altogether.
  • also forget about onMounted as presentend in the tutorial. Not necessary to read data.
  • if you get error subtree is null, this only mean you need to restart bench! (Insane)

I had to doubt the tutorial and the frappe-ui doc (filters) to make it work! :laughing:
Very weird.

Here’s the final version:

import { createResource } from 'frappe-ui'
    let todo = createResource({
        url: 'frappe.client.get_list',
        params:{
            doctype: 'ToDo',
            fields: ['description', 'status', 'date', 'name'],
        }
    })
    todo.fetch()

And simply <pre>{{ JSON.stringify(todo.data[0],null,2) }}</pre> to see it

    {
      "description": "test1",
      "status": "Open",
      "date": "2025-01-14",
      "name": "0489l150rq"
    }

url: ‘/api/resource/ToDo/0489l150rq’

Thanks @peterg but it throws Internal server error (which error exactly is a mystery, no error.log on dev?).

1 Like

Hi @frappefr:

Why not using createDocumentResource ?
It will bring the whole document.

const todo=createDocumentResource({
  doctype: "ToDo",
  name: 0489l150rq,
  auto: true,
});

createListResource is avaliable too for lists …

Anyway, I think more data components would be coming :crazy_face:
Hope this helps.

2 Likes

Thank you @avc
AFAIK, with createDocumentResource you can’t define which fields you want (fields: ['description', 'status', 'date', 'name']) it, so you get all the fields:

{
  "doctype": "ToDo",
  "name": "0489l150rq",
  "doc": {
    "name": "0489l150rq",
    "owner": "Administrator",
    "creation": "2025-01-14 10:39:57.545285",
    "modified": "2025-01-14 10:39:57.545285",
    "modified_by": "Administrator",
    "docstatus": 0,
    "idx": 0,
    "status": "Open",
    "priority": "Medium",
    "color": null,
    "date": "2025-01-14",
    "allocated_to": null,
    "description": "test1",
    "reference_type": null,
    "reference_name": null,
    "role": null,
    "assigned_by": null,
    "assigned_by_full_name": null,
    "sender": null,
    "assignment_rule": null,
    "doctype": "ToDo"
  },
  "originalDoc": {
    "name": "0489l150rq",
    "owner": "Administrator",
    "creation": "2025-01-14 10:39:57.545285",
    "modified": "2025-01-14 10:39:57.545285",
    "modified_by": "Administrator",
    "docstatus": 0,
    "idx": 0,
    "status": "Open",
    "priority": "Medium",
    "color": null,
    "date": "2025-01-14",
    "allocated_to": null,
    "description": "test1",
    "reference_type": null,
    "reference_name": null,
    "role": null,
    "assigned_by": null,
    "assigned_by_full_name": null,
    "sender": null,
    "assignment_rule": null,
    "doctype": "ToDo"
  },
  "isDirty": false,
  "auto": true,
  "get": {
    "url": "frappe.client.get",
    "data": {
      "name": "0489l150rq",
      "owner": "Administrator",
      "creation": "2025-01-14 10:39:57.545285",
      "modified": "2025-01-14 10:39:57.545285",
      "modified_by": "Administrator",
      "docstatus": 0,
      "idx": 0,
      "status": "Open",
      "priority": "Medium",
      "color": null,
      "date": "2025-01-14",
      "allocated_to": null,
      "description": "test1",
      "reference_type": null,
      "reference_name": null,
      "role": null,
      "assigned_by": null,
      "assigned_by_full_name": null,
      "sender": null,
      "assignment_rule": null,
      "doctype": "ToDo"
    },
    "previousData": null,
    "loading": false,
    "fetched": true,
    "error": null,
    "promise": {},
    "params": {
      "doctype": "ToDo",
      "name": "0489l150rq"
    }
  },
  "setValue": {
    "url": "frappe.client.set_value",
    "data": null,
    "previousData": null,
    "loading": false,
    "fetched": false,
    "error": null,
    "promise": null,
    "params": null
  },
  "setValueDebounced": {
    "url": "frappe.client.set_value",
    "data": null,
    "previousData": null,
    "loading": false,
    "fetched": false,
    "error": null,
    "promise": null,
    "params": null
  },
  "save": {
    "url": "frappe.client.set_value",
    "data": null,
    "previousData": null,
    "loading": false,
    "fetched": false,
    "error": null,
    "promise": null,
    "params": null
  },
  "delete": {
    "url": "frappe.client.delete",
    "data": null,
    "previousData": null,
    "loading": false,
    "fetched": false,
    "error": null,
    "promise": null,
    "params": null
  }
}

So far, I don’t get what’s the use case of it.

createListResource (list of one) works and also use get_list. You need to import { useRoute } from 'vue-router';

I really wonder why 3 different and so identical functions, 1 doesn’t implement fields, another need url and params, and this last one seems to work for all cases.
Hmmm, createResource might be just in case to want to fetch external public data, which is super niche.
createDocumentResource for:
- edit (setValue.submit({ status: ‘Closed’ }))
- whitelistedMethods

We use that for get the document and update it from client side easily.

Try createListResource instead …

let todos = createListResource({
  doctype: 'ToDo',
  fields: ['name', 'description', 'status'],
  orderBy: 'creation desc',
  start: 0,
  pageLength: 5,
})
1 Like

Umm … like get_list vs get_doc

Many times you need just a list, other you need the whole document (child tables data included …) . I see both needed :slight_smile:

good point.

Can you share how you do it using frappe-ui?