Writing / Netsuite

Export a NetSuite Custom-Field Data Dictionary With SuiteScript

Build a SuiteScript export of custom-field definition metadata with SuiteQL: catalog-confirmed columns, pagination, CSV escaping and per-account query adaptation.

A custom-field data dictionary is a snapshot of definitions: script IDs, labels, types, owning records—not the values stored on transactions or custom-record rows. SuiteScript plus SuiteQL can produce a CSV (or file cabinet object) once the Records Catalog confirms which analytical records and columns exist in your account.

This guide supports How to Query NetSuite Custom Record Fields With SuiteQL. It is a pattern for export, not a universal schema dump.

The exact FROM record and selected columns must be adapted to the account’s Records Catalog under the SuiteScript and REST Query API channel for the role that runs the script. Do not treat CustomRecordCustomField or any column list below as guaranteed.

Scope the export

Decide explicitly:

Include Exclude (unless you add separate jobs)
Field-definition metadata for chosen families Instance row values
Catalog-confirmed columns only Guessed joins from UI labels
One family per query (recommended) A single invented mega-UNION

Custom-record fields, transaction body fields, entity fields and other families may require separate queries. See NetSuite Custom Field Metadata Tables Explained.

Confirm the query offline first

  1. Open Setup → Records Catalog as the script execution role.
  2. Select the SuiteScript and REST Query API channel.
  3. Locate each metadata record you will export.
  4. Copy analytical field IDs for at least: internal id, script id, label, field type (if present), owning type/parent (if present), inactive-style flag (if present).
  5. Run a three-column probe in a query tool before coding the export (column reference).

If the definition record is missing, fix visibility first (missing CustomRecordCustomField).

Illustrative SuiteQL (adapt every identifier)

SELECT
    field_definition.id,
    field_definition.scriptid,
    field_definition.label,
    field_definition.fieldtype,
    field_definition.recordtype
FROM
    CustomRecordCustomField field_definition
ORDER BY
    field_definition.scriptid,
    field_definition.id

Notes:

  • fieldtype and recordtype are illustrative. Drop or replace them if the catalog does not expose them.
  • The ORDER BY must be unique enough for paging—include id as a final tie-breaker.
  • Filter inactive or single-type subsets only with confirmed predicates and bind parameters (dynamic WHERE and binds).

Paginate: do not rely on runSuiteQL alone

Large accounts exceed the non-paged SuiteQL row cap. Use query.runSuiteQLPaged() with a deterministic order. Details and limits: SuiteQL Result Limits and Pagination.

Illustrative SuiteScript pattern (Scheduled or Map/Reduce friendly):

/**
 * @NApiVersion 2.1
 * @NScriptType ScheduledScript
 */
define(['N/query', 'N/file', 'N/log'], (query, file, log) => {
  // Replace this SQL with catalog-confirmed IDs for the execution account/role.
  const SUITEQL = `
    SELECT
      field_definition.id,
      field_definition.scriptid,
      field_definition.label,
      field_definition.fieldtype,
      field_definition.recordtype
    FROM
      CustomRecordCustomField field_definition
    ORDER BY
      field_definition.scriptid,
      field_definition.id
  `;

  const HEADERS = ['id', 'scriptid', 'label', 'fieldtype', 'recordtype'];

  function escapeCsvCell(value) {
    if (value === null || value === undefined) {
      return '';
    }
    const text = String(value);
    if (/[",\r\n]/.test(text)) {
      return `"${text.replace(/"/g, '""')}"`;
    }
    return text;
  }

  function rowToCsv(map) {
    return HEADERS.map((key) => escapeCsvCell(map[key])).join(',');
  }

  function execute(context) {
    const lines = [HEADERS.join(',')];

    const paged = query.runSuiteQLPaged({
      query: SUITEQL,
      pageSize: 1000,
    });

    for (const pageRange of paged.pageRanges) {
      const page = paged.fetch({ index: pageRange.index });
      for (const result of page.data.results) {
        lines.push(rowToCsv(result.asMap()));
      }
    }

    const csvFile = file.create({
      name: 'custom_field_data_dictionary.csv',
      fileType: file.Type.CSV,
      contents: lines.join('\n'),
      // Set folder to a File Cabinet folder internal ID available to the role.
      folder: -15,
    });

    const fileId = csvFile.save();
    log.audit({
      title: 'Custom field dictionary exported',
      details: `fileId=${fileId}, rows=${lines.length - 1}`,
    });
  }

  return { execute };
});

What you must change before production

  • SQL record and columns — catalog IDs for the account and role.
  • HEADERS — must match the selected column aliases / field IDs returned in asMap().
  • folder — a real File Cabinet folder the role can write to (-15 is only a placeholder pattern; use your folder ID).
  • Script deployment — role, schedule, and governance-friendly volume.
  • Families — add separate queries or script parameters for other custom-field families.

CSV escaping rules used above

  • Null/undefined → empty cell.
  • Values containing comma, quote or newline → wrap in double quotes.
  • Embedded quotes → doubled (""").

That is enough for a practical dictionary file. If labels can contain leading zeros or spreadsheet-sensitive text, keep the file as pure CSV and open it with explicit text import options in spreadsheet tools.

Governance and volume

  • Prefer one scheduled run over interactive Suitelets for full-account extracts.
  • Stay within paged-result ceilings; if you need more than SuiteQL paging allows, filter by family, owning type or script-ID prefix and run multiple exports.
  • Log counts and file IDs, not full field dumps that may include sensitive labels in shared logs.

What this export is not

  • Not a substitute for SDF object XML in source control.
  • Not proof of instance-level data access for each field.
  • Not a cross-account schema contract—re-verify columns after major customisation or role changes.
  • Not automatically complete for every custom-field family.

For deployment definitions, use SuiteCloud import (import custom record objects) and compare purposes in SuiteQL Metadata vs SuiteCloud XML.