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 ConnectorsAdd 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