I recently worked with a client on a NetSuite user notes integration project to track A/R collection notes. They had a good reason for managing the notes in an external tool instead of directly in NetSuite. The ask was simple: Set up a secure integration with NetSuite for reading and writing user notes. In building out that integration, we encountered several nuances that made an otherwise routine request much more involved. This article captures my most important learnings. These findings are relevant regardless of whether one is building an integration or programmatically working with user notes directly in NetSuite.
Table of Contents
TL;DR
- NetSuite user notes are great but not very easy to work with: APIs are inconsistent, role permission structure is nuanced, and some integration options are not viable.
- Understanding the constraints captured in this article beforehand will increase your chances of a successful project.
The “What?” and “Why?” of NetSuite User Notes
User notes in NetSuite are records that allow users to attach freeform text commentary or annotations directly to other records—such as transactions, customers, items, support cases, or custom records—within the NetSuite system. These notes are designed to capture important contextual information, reminders, or internal discussions that aren’t necessarily part of the structured data of a record but are essential for providing additional context or tracking communications over time.
When used properly and systematically, user notes offer a means to capture the “why?” as opposed to the “what?” for which System Notes are a great tool.

User notes are typically accessible via the Communications subtab of a record though they may be exposed elsewhere. They may be created manually via the NetSuite user interface or programmatically using SuiteScript or other integration methods like SuiteTalk (SOAP Web Services). User notes are searchable. Thus, they have the general characteristics one would expect of NetSuite records. However, there are important nuances that make them stand out (for good or bad) from the rest.
Key Considerations When Working with NetSuite User Notes
1. Access to User Notes is Controlled By Role Permissions
Unsurprisingly, the user notes functionality is controlled by role permissions; but, which one(s)? Roles must have the List > Notes Tab
permission to view, create, edit, and/or delete (FULL access level) user notes.
Additionally, to expose user notes on custom records, the “Show Notes” option must be checked as illustrated below. If not, the functionality will be hidden even if users have the requisite role permissions.

2. Standalone User Notes are Not Permitted
User notes, by design, must be associated with another record. Attempting to create a standalone user note will result in an error like: You cannot create a standalone note record.
Tip: In the UI, you can access the list of user notes via the URL
https://<account_id>.app.netsuite.com/app/crm/common/notelist.nl
. Although a “New Note” button is visible on that page and can be clicked, attempting to save the note will result in an error as illustrated below.


Another implication of the fact that standalone user notes are not allowed is that the “Remove” option in the user notes sublist does not just detach the note as is the case for other sublist records but also deletes it to avoid orphaned notes in the database.

3. User Notes Cannot be Inactivated
Unlike most record types which have an “Inactive” checkbox, user notes (like transactions) lack this option and cannot be inactivated. Deletion is the only option to get rid of an undesired note, provided the role in question has “Full” access level for the permission Lists > Notes Tab
.
4. The Author of a User Note Can Only be Changed Programmatically
The author of a user note defaults to the currently logged-in user and cannot be changed from the user interface. However, when creating a note programmatically, the author can be changed.

If a note is created via RESTlet and the author is not specified, the user selected when creating an access token for the integration will be the author.
5. Programmatically Attaching a User Note to a Custom Record is Tricky
Please refer to my earlier article that goes into detail on this topic: Learn How to Programmatically Attach a User Note to a Custom Record Without Hardcoding the Record ID
6. Only a Limited Subset of Record Types Support User Notes
Unfortunately, user notes are exposed to only a subset of record types and there is no easy way to find out all supported record types. A quick way to find out the most common ones is to create a user notes saved search and inspect the available joins:

However, this approach does not expose all record types. Using a combination of SuiteScript Record Browser, SuiteQL Record Catalog, and the approach mentioned above, I came up with a longer (inexhaustive) list of supported record types: Entities (customer, vendor, employee, etc.), transactions, items, CRM records (activity, call, event, task), support cases, workflows, and all custom record types. Even file cabinet files, folders, and accounting periods can have user notes associated with them!
Notable record types where user note capacity is missing include accounts, subsidiaries, roles, segments (department, location, class), and custom lists. Bottom line: When in doubt, check.
In our case, the target record was invoices which are a type of transaction, thus, we knew the functionality was available before embarking on the journey that exposed some interesting oddities.
6. User Notes Are No Longer Exposed via the REST API
As explained in this StackOverflow thread, apparently, the User Notes (note
) REST API that was present in the 2023.1 release got removed for unclear reasons in the 2024.1 release. Thus, at the time of writing, it is not possible to manipulate user notes via the REST API. While that might change in the future, for now, we need to consider other integration options when interacting with user notes from external systems.
Note that the SuiteTalk SOAP API has a user notes endpoint but unless you are already using SOAP for other purposes, why would you go backward to a technology that is outdated?
For my integration project, I opted for a RESTlet which was very trivial to set up and offers a robust authentication mechanism.
7. Working with User Notes in SuiteQL is Not Very Intuitive
Inspecting the Records Catalog reveals a Note
record. However, it appears to only expose the note’s internal ID and joins but not the actual note data like author, date, title, memo, type, etc.

Accessing user notes in SuiteQL is best done via joins. For example, looking at the Entity
record in the Records Catalog you will see an entityNote
join which can be used to retrieve the user notes.

The following query will retrieve all notes associated with the vendor in our running example:
SELECT
n.*,
TO_CHAR(n.notedate, 'YYYY-MM-DD HH12:MI PM') as formatted_notedatetime
FROM
entity e
JOIN entityNote n
ON (n.entity = e.id AND e.id = 5807)

A few more observations about SuiteQL:
- The
entityNote
table and similar ones (e.g.TransactionNote
for transactions) can be queried directly to retrieve user notes (e.g.SELECT * FROM entityNote
will return all notes associated with an entity). Of course, this approach requires knowledge of the record type the note is associated with and - Although the
notedate
field is of type date/time, it is returned as a date by default and requires formatting to retrieve the time value (see fieldformatted_notedatetime
in the query results above). Thanks to Tim Dietrich’s article “NetSuite / SuiteScript / SuiteQL: Working With User Notes” for this tip.
8. Saved Search Results Are Limited to Current User’s Notes
As mentioned above, it is possible to create a user notes saved search. However, it turns out that only user notes where the author is the current user will be returned unless the search is run from the Administrator role.
Consider the following user notes saved search that lists all user notes in the system. When executed in the admin role, two results are returned – one owned by me and the other by Bob Smith:

However, when executed from a non-admin role, only my note is returned!

I even tried enabling the “Run Unrestricted” option on the search but it did not make a difference. If you are not familiar with this option, see my article series titled “Understanding the “Run Unrestricted” Saved Search Mode” for its usage and potential risks.

This was an unpleasant discovery in our project as our initial approach for retrieving all user notes associated with an invoice was to create a User Notes saved search and join to the desired invoice. It took a bit of debugging to understand why zero results were returned by the RESTlet although the same search run in the UI returned multiple results. Per recommended practices, the integration had been set up using a non-admin integration user which had no notes associated with it. It was only after running the search in different roles that it became clear what the issue was.
9. N/search and N/record Return Different Values For Multiple Fields
Let’s write some code. The following snippet (run in the browser console; thus using the require
instead of define
pattern) retrieves one of the user notes we have shown earlier:
require(['N'],
(N) => {
for (let n in N) window[n] = N[n]
const noteRec = record.load({
type: record.Type.NOTE,
id: 1
})
const data = {}
['author', 'direction', 'notetype'].forEach(fieldId => {
data[fieldId] = noteRec.getText(fieldId)
data[`${fieldId}Id`] = noteRec.getValue(fieldId)
})
['notedate', 'time', 'note'].forEach(fieldId => {
data[fieldId] = noteRec.getValue(fieldId)
})
console.log(JSON.stringify(data))
}
)
The following snippet retrieves the same data via N/search
. Notice that we are joining from the entity instead of searching the user note record directly to ensure that our search will work consistently in admin and non-admin roles per our earlier discussion. Also, note that I am using the shorthand join syntax for both the filters and columns. Refer to my article “Understanding SuiteScript 2.x Joins” if you’re unfamiliar with this syntax.
require(['N'],
(N) => {
for (let n in N) window[n] = N[n]
const data = {}
search.create({
type: search.Type.ENTITY,
filters: [
['usernotes.internalid', search.Operator.ANYOF, 1]
],
columns: [
'usernotes.title',
'usernotes.note',
'usernotes.notedate',
'usernotes.author',
'usernotes.direction',
'usernotes.notetype',
'usernotes.internalid'
]
}).run().each(result => {
//console.log(JSON.stringify(result))
const join = 'usernotes'
['author', 'direction', 'notetype'].forEach(name => {
data[name] = result.getText({ name, join })
data[`${name}Id`] = result.getValue({ name, join })
})
['title', 'notedate', 'time', 'note'].forEach(name => {
data[name] = result.getValue({ name, join })
})
})
console.log(JSON.stringify(data))
}
)
Here are the results from N/search
and N/record
side-by-side:
N/search Results | N/record Results |
---|---|
{ “author”: “Chidi Okwudire”, “authorId”: “579”, “direction”: null, “directionId”: “Incoming”, “notetype”: null, “notetypeId”: “A hand written note.”, “title”: “Wisdom”, “notedate”: “4/16/2025 6:32 am”, “time”: null, “note”: “One note a day keeps the apathy away.” } | { “author”: “Chidi Okwudire”, “authorId”: “579”, “direction”: “Incoming”, “directionId”: “1”, “notetype”: “Note”, “notetypeId”: “7”, “title”: “Wisdom”, “notedate”: “2025-04-16T04:00:00.000Z”, “time”: “2025-04-21T10:32:00.000Z”, “note”: “One note a day keeps the apathy away.” } |
It should be pretty easy to spot the differences… what’s going on here with the direction
, notetype
, notedate
and time
fields?! Inconsistency is the name of the game (a.k.a. make the developer look stupid :D).
10. Modules Return Inconsistent Values for Field Internal IDs and Texts
- Calling
getValue()
on anN/search
result for the direction returns the textual value instead of the internal ID. On the contrary,N/record
behaves as we would expect –getValue()
returns the internal ID andgetText()
returns the textual equivalent. - Something weirder happens with the
notetype
field where the textual values returned byN/search
andN/record
are different! (“A hand written note.” versus “Note”). We’ll get back to this in a moment.
11. N/search uses the Note Type Description as Textual Value
It was particularly intriguing that the textual value of the notetype
field was inconsistent between the APIs. Investigating further, we traced the mystery to the Note Type list definition (Setup > Sales > CRM Lists):
N/record
uses the Note Type value (“Note” in our example) whereas N/search
uses the Description (“A hand written note.”). If the description field is empty, N/search will default to the Note Type. What drama!
It is unreasonable to expose such inconsistencies to the external world. Thus, we addressed this issue by adding logic to convert N/search results to match the expected output from N/record. For the direction
, we hardcoded the mappings (1 = “Incoming”, 2 = “Outgoing”) as there appears to be no API to get these values and it is fair to assume they will be stable.
For the Note Type field, hardcoding was not a good idea as the list is mutable by users. Instead, we used a query to perform the mapping. Here’s a snippet for inspiration.
query.runSuiteQL({
query: 'select name, description, id from notetype'
})
.asMappedResults()
.forEach(result => {
noteTypeMapping[result.name] = {
name: result.name,
id: result.id
}
if (result.description && result.description !== result.name) {
// N/search falls back to the name if the description is not set; hence, we capture both
noteTypeMapping[result.description] = {
name: result.name,
id: result.id
}
}
})
// The results were cached in a noteTypeMapping object which was then used during processing N/search results e.g.
// const noteType = noteTypeMapping[searchResult.getValue({ name: 'notetype': join: 'usernotes'})]
12. Note Date and Time Field Values Are Inconsistent
How about date timestamps – the bane of developers’ existence?
Well, N/search
returns the date and time combined as a string (e.g. “4/16/2025 6:32 am”) in the notedate
field whereas N/record
returns only the date component in the notedate
field but as a datetime value though only the date component is accurate. The time
field contains the time component but noteRec.getValue('time')
returns as a full datetime value relative to the current date (e.g. “2025-04-21T10:32:00.000Z”) instead of just the time of day (e.g. “6:32 am”)! Interestingly, noteRec.getText('time')
returned just the time of day.
It is mindblowing how much dev effort is spent on date time inconsistencies in general. NetSuite adds an extra layer of complexity with server time vs. time settings in the account or user preferences, vs. client script time behavior. This topic definitely deserves an article of its own (perhaps a booklet!). When working with date times in NetSuite, N/format
is usually your friend.
To address the date time inconsistencies, we used the N/format
module as illustrated in the code snippet below. For results coming from N/record
, we extracted the date and time components as strings and concatenated them to match the N/search
result format. We then used format.parse()
to convert the concatenated string into to an ISO date. There might be a more elegant solution.
let noteDateTimeStr
if (/*User note record load*/) {
const noteDate = noteRec.getValue('notedate')
const noteTime = noteRec.getText('time') // getValue returns the time along with the current date!
const dateStr = format.format({ value: noteDate, type: format.Type.DATE })
noteDateTimeStr = `${dateStr} ${noteTime}`
//console.log(`Date str ${dateStr} | time: ${noteTime} | Combined: ${noteDateTimeStr}`)
} else {
// N/search returns date time as a string
noteDateTimeStr = searchResult.getValue({ name: 'notedate', join: 'usernotes'})
}
// Expose date time as ISO string for consistency
data['notedate'] = format.parse({value: noteDateTimeStr, type: format.Type.DATETIME}).toISOString()
Parting Words
Who would have thought that a simple User Notes integration would turn out this complicated? For my future self and any developers out there, I hope this article will come to mind the next time you work with this record. Also, some of the knowledge especially around date time manipulation is useful in other contexts. For any business analyst reading this article, I suspect I may have lost you in some details. However, the bottom line is: User Notes integrations are not as simple as they might appear. Budget accordingly and be kind to your developers.
Lastly, if you know some other oddities about NetSuite user notes, share them in the comments section below. And, if you dare to be honest, share your score of how many of these details you knew before reading the article where 12/12 means you knew them all.
NetSuite Insights is on a mission to raise the standards around NetSuite practices, one insight at a time. If that resonates with you, learn how you can become an author/collaborator here.
Don’t miss a beat – subscribe to our no-nonsense email list to have these game-changing insights hit your inbox as soon as they’re published. Ignorance? Nah, you’ve got a smarter option. Choose wisdom, choose insights!
Awesome job with this article Chidi! To be honest, I’ve only had a couple of customizations which involved the user notes record. Most of these nuances I’ve not had to deal with. I’m sure I’ll be referring back to this article for my next user notes customization. Thank you for your contributions to the developer community.
Hey Jaime, thanks for stopping by. It’s always my pleasure to produce something meaningful.
I knew about 9/12 of those nuances. We created an accounts receivable notes custom recordback in the day so we could’ve more control of the record, such as system notes and permissions. Great job on this write up!
Good for you, Roy! Always a pleasure.