Skip to main content

Creating Purchase Orders and Attaching Files in NetSuite

Learn how to automate NetSuite purchase order creation and file attachments using Abstra connectors.

What you'll learn​

  • Configure NetSuite connector in Abstra
  • Query vendors and items using SuiteQL
  • Create purchase orders via REST API
  • Upload files to NetSuite File Cabinet
  • Attach files to purchase orders using SOAP API

Prerequisites​

  • NetSuite account with API access
  • NetSuite credentials (Account ID, Consumer Key/Secret, Token ID/Secret)
  • Abstra project

Step 1: Configure NetSuite Connection​

  1. In Abstra Console, go to Connectors → Add Connection
  2. Select NetSuite
  3. Enter your credentials
  4. Enable these resources:
    • suiteQL - Query data
    • fileCabinet - File operations
    • soap - Attachment operations
    • rest/purchaseOrder - Purchase order management
    • rest/vendor - Vendor management

Test your connection:

from abstra.connectors import run_connection_action

result = run_connection_action(
"netsuite",
"query",
{"query": "SELECT id FROM vendor WHERE isinactive = 'F'", "limit": 1}
)
print(f"Connection works! Found vendor: {result['data']['items'][0]['id']}")

Step 2: Get Required IDs​

NetSuite purchase orders need vendor, item, subsidiary, department, and class IDs:

from abstra.connectors import run_connection_action

# Get vendor ID
vendors = run_connection_action(
"netsuite",
"query",
{"query": "SELECT id FROM vendor WHERE isinactive = 'F'", "limit": 1}
)
vendor_id = vendors['data']['items'][0]['id']

# Get item ID (purchasable item)
items = run_connection_action(
"netsuite",
"query",
{
"query": "SELECT id FROM item WHERE isinactive = 'F' AND purchasedescription IS NOT NULL",
"limit": 1
}
)
item_id = items['data']['items'][0]['id']

# Get subsidiary ID
subsidiaries = run_connection_action(
"netsuite",
"query",
{"query": "SELECT id FROM subsidiary", "limit": 1}
)
subsidiary_id = subsidiaries['data']['items'][0]['id']

# Get department ID
departments = run_connection_action(
"netsuite",
"query",
{"query": "SELECT id FROM department", "limit": 1}
)
department_id = departments['data']['items'][0]['id']

# Get classification (BU) ID
classes = run_connection_action(
"netsuite",
"query",
{"query": "SELECT id FROM classification WHERE isinactive = 'F'", "limit": 1}
)
class_id = classes['data']['items'][0]['id']

print(f"✓ Vendor: {vendor_id}")
print(f"✓ Item: {item_id}")
print(f"✓ Config: {subsidiary_id}, {department_id}, {class_id}")

Step 3: Create Purchase Order​

Create a purchase order with the IDs from Step 2:

from abstra.connectors import run_connection_action
from datetime import datetime

po_result = run_connection_action(
"netsuite",
"post_purchase_order",
{
"data": {
"entity": {"id": vendor_id},
"subsidiary": {"id": subsidiary_id},
"department": {"id": department_id},
"class": {"id": class_id},
"currency": {"id": "1"},
"tranDate": datetime.now().strftime("%Y-%m-%d"),
"memo": "Automated PO from Abstra",
"item": {
"items": [
{
"item": {"id": item_id},
"quantity": 10,
"rate": 50.00,
"department": {"id": department_id},
"class": {"id": class_id}
}
]
}
}
}
)

po_id = po_result['data']['id']
print(f"✓ Created Purchase Order: {po_id}")

Understanding the Structure​

  • data wrapper: Required for REST API operations
  • Reference fields: Use {"id": "value"} format
  • Line items: Go in item.items array
  • Required fields: Vary by NetSuite configuration; add custom fields as needed

Step 4: Upload File​

Upload a file to NetSuite's File Cabinet:

from abstra.connectors import run_connection_action
import base64

# Read and encode file
with open("invoice.pdf", "rb") as f:
file_content = base64.b64encode(f.read()).decode('utf-8')

# Upload to NetSuite
file_result = run_connection_action(
"netsuite",
"add_file",
{
"name": "invoice.pdf",
"content": file_content,
"fileType": "PDF",
"folder": {"name": "SuiteFiles"} # Optional - defaults to SuiteFiles root
}
)

file_id = file_result['data']['id']
print(f"✓ Uploaded file: {file_id}")

Folder Options​

You can specify different folder destinations:

# Upload to SuiteScripts folder
file_result = run_connection_action("netsuite", "add_file", {
"name": "script.js",
"content": file_content,
"fileType": "JAVASCRIPT",
"folder": {"name": "SuiteScripts"}
})

# Upload to default SuiteFiles (folder parameter optional)
file_result = run_connection_action("netsuite", "add_file", {
"name": "document.pdf",
"content": file_content,
"fileType": "PDF"
})

Supported File Types​

PDF, EXCEL, WORD, PLAINTEXT, JAVASCRIPT, IMAGE, CSV, JPGIMAGE, PNGIMAGE

Important Notes:

  • 10MB file size limit via SOAP API
  • File content must be base64-encoded
  • File type must match actual content (e.g., use PDF for PDF files, not PLAINTEXT)

Step 5: Attach File to Purchase Order​

Use the SOAP attach action to link the file:

from abstra.connectors import run_connection_action

attach_result = run_connection_action(
"netsuite",
"attach",
{
"attachTo": {
"type": "purchaseOrder",
"internalId": po_id
},
"attachedRecord": {
"type": "file",
"internalId": file_id
}
}
)

if attach_result['data']['success']:
print(f"✓ Attached file {file_id} to PO {po_id}")

Other Record Types​

Attach files to different records by changing the type:

# Invoice
run_connection_action("netsuite", "attach", {
"attachTo": {"type": "invoice", "internalId": "12345"},
"attachedRecord": {"type": "file", "internalId": file_id}
})

# Vendor Bill
run_connection_action("netsuite", "attach", {
"attachTo": {"type": "vendorBill", "internalId": "67890"},
"attachedRecord": {"type": "file", "internalId": file_id}
})

# Customer
run_connection_action("netsuite", "attach", {
"attachTo": {"type": "customer", "internalId": "11111"},
"attachedRecord": {"type": "file", "internalId": file_id}
})

Step 6: Working with Files​

Retrieve File Information​

Get details about an uploaded file:

from abstra.connectors import run_connection_action

file_details = run_connection_action(
"netsuite",
"get_file",
{"fileId": file_id}
)

print(f"File name: {file_details['data']['name']}")
print(f"File size: {file_details['data']['fileSize']} bytes")
print(f"File URL: {file_details['data']['url']}")
print(f"Content (base64): {file_details['data']['content'][:50]}...")

Update File Properties​

Update an existing file's metadata or content:

from abstra.connectors import run_connection_action
import base64

# Update file name and description
update_result = run_connection_action(
"netsuite",
"update_file",
{
"fileId": file_id,
"name": "updated_invoice.pdf",
"description": "Updated invoice for PO " + po_id
}
)

# Update file content
with open("new_document.pdf", "rb") as f:
new_content = base64.b64encode(f.read()).decode('utf-8')

update_result = run_connection_action(
"netsuite",
"update_file",
{
"fileId": file_id,
"content": new_content
}
)

print(f"✓ File updated: {update_result['data']['success']}")

Search for Files​

Find files in the File Cabinet using SuiteQL:

from abstra.connectors import run_connection_action

# Search by name
files = run_connection_action(
"netsuite",
"query",
{
"query": "SELECT id, name, filesize, filetype, folder, createddate FROM File WHERE name LIKE '%invoice%' ORDER BY createddate DESC FETCH FIRST 10 ROWS ONLY"
}
)

for file in files['data']['items']:
print(f"File: {file['name']} (ID: {file['id']}, Size: {file['filesize']} bytes)")

# Search by date range
files = run_connection_action(
"netsuite",
"query",
{
"query": "SELECT id, name, createddate FROM File WHERE createddate >= TO_DATE('2025-01-01', 'YYYY-MM-DD') ORDER BY createddate DESC FETCH FIRST 20 ROWS ONLY"
}
)

Delete File​

Remove a file from the File Cabinet:

from abstra.connectors import run_connection_action

delete_result = run_connection_action(
"netsuite",
"delete_file",
{"fileId": file_id}
)

if delete_result['data']['success']:
print(f"✓ File {file_id} deleted successfully")

Complete Example​

Here's the entire workflow in one script:

from abstra.connectors import run_connection_action
from datetime import datetime
import base64

# Step 1: Get all required IDs
vendors = run_connection_action("netsuite", "query",
{"query": "SELECT id FROM vendor WHERE isinactive = 'F'", "limit": 1})
vendor_id = vendors['data']['items'][0]['id']

items = run_connection_action("netsuite", "query",
{"query": "SELECT id FROM item WHERE isinactive = 'F' AND purchasedescription IS NOT NULL", "limit": 1})
item_id = items['data']['items'][0]['id']

subsidiaries = run_connection_action("netsuite", "query",
{"query": "SELECT id FROM subsidiary", "limit": 1})
subsidiary_id = subsidiaries['data']['items'][0]['id']

departments = run_connection_action("netsuite", "query",
{"query": "SELECT id FROM department", "limit": 1})
department_id = departments['data']['items'][0]['id']

classes = run_connection_action("netsuite", "query",
{"query": "SELECT id FROM classification WHERE isinactive = 'F'", "limit": 1})
class_id = classes['data']['items'][0]['id']

# Step 2: Create Purchase Order
po_result = run_connection_action("netsuite", "post_purchase_order", {
"data": {
"entity": {"id": vendor_id},
"subsidiary": {"id": subsidiary_id},
"department": {"id": department_id},
"class": {"id": class_id},
"currency": {"id": "1"},
"tranDate": datetime.now().strftime("%Y-%m-%d"),
"memo": "Automated PO from Abstra",
"item": {
"items": [{
"item": {"id": item_id},
"quantity": 10,
"rate": 50.00,
"department": {"id": department_id},
"class": {"id": class_id}
}]
}
}
})
po_id = po_result['data']['id']

# Step 3: Upload File
with open("contract.pdf", "rb") as f:
file_content = base64.b64encode(f.read()).decode('utf-8')

file_result = run_connection_action("netsuite", "add_file", {
"name": "contract.pdf",
"content": file_content,
"fileType": "PDF",
"folder": {"name": "SuiteFiles"}
})
file_id = file_result['data']['id']

# Step 4: Attach File to PO
run_connection_action("netsuite", "attach", {
"attachTo": {"type": "purchaseOrder", "internalId": po_id},
"attachedRecord": {"type": "file", "internalId": file_id}
})

print(f"✓ Complete! PO {po_id} created with attached file {file_id}")

Troubleshooting​

"Please enter a value for [field]"​

Your NetSuite configuration requires additional fields. Check an existing PO to see its structure:

po = run_connection_action("netsuite", "get_purchase_order_by_id", {
"id": "12345",
"expandSubResources": True
})
print(po['data'])

"Action attach not found" or "WSDL-based action not yet implemented"​

Make sure you have enabled the soap resource in your NetSuite connection configuration:

  1. Go to your NetSuite connection settings
  2. Check the soap checkbox under Resources
  3. Save the connection

The attach action requires SOAP operations to be enabled.

"Invalid field value"​

Verify the ID exists and the record is active:

# Check if vendor exists and is active
vendor = run_connection_action("netsuite", "query", {
"query": f"SELECT id, isinactive FROM vendor WHERE id = '{vendor_id}'"
})
print(vendor['data']['items'][0])

File Upload Issues​

"File size exceeds limit"

  • NetSuite SOAP API has a 10MB file size limit
  • For larger files, consider using NetSuite's REST API or SuiteTalk file upload endpoints

"Invalid file type"

  • Ensure the fileType parameter matches the actual file content
  • Use uppercase for file types: PDF, PLAINTEXT, EXCEL, etc.
  • Common types: PDF, EXCEL, WORD, PLAINTEXT, JAVASCRIPT, CSV, JPGIMAGE, PNGIMAGE

"File content must be base64 encoded"

import base64

# Correct way to encode file content
with open("document.pdf", "rb") as f:
file_content = base64.b64encode(f.read()).decode('utf-8')

# Now use file_content in add_file action

SuiteQL Query Issues​

"Unknown identifier" errors

  • NetSuite field names may differ from UI labels
  • Use FETCH FIRST N ROWS ONLY instead of LIMIT N for pagination
  • NetSuite doesn't support all SQL functions - refer to SuiteQL documentation

Query Examples:

# ✅ Correct - using FETCH FIRST
"SELECT id FROM vendor FETCH FIRST 10 ROWS ONLY"

# ❌ Incorrect - LIMIT not supported
"SELECT id FROM vendor LIMIT 10"

# ✅ Correct - filtering inactive records
"SELECT id FROM vendor WHERE isinactive = 'F'"

# ✅ Correct - date filtering
"SELECT id FROM transaction WHERE trandate >= TO_DATE('2025-01-01', 'YYYY-MM-DD')"

Finding Purchase Orders with Attachments​

To find which purchase orders have files attached, use a query:

# Search for purchase orders
pos = run_connection_action("netsuite", "query", {
"query": "SELECT id, tranid, foreigntotal FROM Transaction WHERE type = 'PurchOrd' FETCH FIRST 10 ROWS ONLY"
})

# Note: NetSuite doesn't expose file attachments directly via SuiteQL
# You'll need to check individual PO details or search files by naming convention

Reference​

run_connection_action(connection_name, action_name, params)​

Executes an action on a configured connector.

Parameters:

  • connection_name (str): Name of your NetSuite connection
  • action_name (str): Action to execute (query, post_purchase_order, add_file, attach)
  • params (dict): Action-specific parameters

Returns: Dict with action results

Common Actions​

ActionPurposeKey Parameters
queryRun SuiteQL queriesquery
post_purchase_orderCreate POdata (PO structure)
get_purchase_order_by_idFetch PO detailsid, expandSubResources
add_fileUpload filename, content, fileType, folder
get_fileRetrieve file detailsfileId or internalId
update_fileUpdate file propertiesfileId, name, content, description
delete_fileDelete filefileId
attachLink records (e.g., file→PO)attachTo (record), attachedRecord (file)

Next Steps​

  • Explore other NetSuite record types (invoices, vendor bills, customers)
  • Add error handling and validation
  • Create workflows with Abstra Forms for user input
  • Set up scheduled jobs to automate PO creation

Resources​