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
- In Abstra Console, go to Connectors → Add Connection
- Select NetSuite
- Enter your credentials
- Enable these resources:
suiteQL- Query datafileCabinet- File operationssoap- Attachment operationsrest/purchaseOrder- Purchase order managementrest/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
datawrapper: Required for REST API operations- Reference fields: Use
{"id": "value"}format - Line items: Go in
item.itemsarray - 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
PDFfor PDF files, notPLAINTEXT)
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:
- Go to your NetSuite connection settings
- Check the
soapcheckbox under Resources - 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
fileTypeparameter 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 ONLYinstead ofLIMIT Nfor 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 connectionaction_name(str): Action to execute (query,post_purchase_order,add_file,attach)params(dict): Action-specific parameters
Returns: Dict with action results
Common Actions
| Action | Purpose | Key Parameters |
|---|---|---|
query | Run SuiteQL queries | query |
post_purchase_order | Create PO | data (PO structure) |
get_purchase_order_by_id | Fetch PO details | id, expandSubResources |
add_file | Upload file | name, content, fileType, folder |
get_file | Retrieve file details | fileId or internalId |
update_file | Update file properties | fileId, name, content, description |
delete_file | Delete file | fileId |
attach | Link 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