Skip to main content
All CollectionsGeneral QuestionsDeveloper Hub and Scripts
How to create Scripts on miniExtensions
How to create Scripts on miniExtensions

Learn how to leverage the Developer Hub to create your own automations!

Updated over a month ago

The Developer Hub has been hidden from the menu. If you have previously created scripts that you need to adjust, you can do so by following this link.

The miniExtensions Developer Hub allows users to create powerful automations. By leveraging TypeScript, you can manipulate data, create custom interfaces, and extend the functionality of your Airtable bases. This guide will walk you through the various components of the scripting environment, providing detailed explanations and examples along the way.

Overview

Within the miniExtensions Developer Hub, you have access to several global objects and functions:

  • record: The current Airtable record being processed

  • context: Information about the current script execution context

  • airtable: Functions for interacting with Airtable, like fetching records

  • cache: Functions for caching data across script runs

  • storage: Functions for uploading temporary files

  • ui: Namespace for creating user interfaces for your scripts

We will go into each of these elements in the sections below. You can use the navigation on the right or the links above to jump straight to the corresponding sections! At the very end of this article you will find further sections on handling of Airtable field types, advanced techniques and best practices as well as a comprehensive example which combines many of the concepts explained in this guide.

The Record Object

The record object represents the current Airtable record being processed. It provides methods to get and set field values.

Key Methods:

  • record.get(fieldName: string): any

    • Retrieves the value of a specified field

    • Throws an error if the field is not found

  • record.set(fieldName: string, value: any): void

    • Sets the value of a specified field

    • Throws an error if the field is not found or is a computed field

Properties:

  • record.id: string

    • The unique identifier of the record

Examples:

// Get the value of the 'Name' field
const name = record.get('Name');

// Set the value of the 'Status' field
record.set('Status', 'Completed');

// Get the record's ID
const recordId = record.id;

The Context Object

The context object provides information about the current script execution context. It includes details about the table, view, and fields being used.

Properties:

  • context.tableId: string

    • The ID of the table used by the automation

  • context.viewId?: string

    • The ID of the view used by the automation (if any)

  • context.runId: string

    • A unique identifier for the current run of the automation

  • context.feedbackField?: string

    • The field used for feedback by the automation (if any)

  • context.fields: AirtableField[]

    • An array of all fields in the Airtable table

  • context.fieldsMap: Record<string, AirtableField>

    • A map of field names to their corresponding Airtable fields

Examples:

// Get the table ID
console.log(`Current table ID: ${context.tableId}`);

// Check if a view is being used
if (context.viewId) {
console.log(`Running in view: ${context.viewId}`);
}

// Access all fields in the table
const allFields = context.fields;
console.log(`Number of fields: ${allFields.length}`);

// Get a specific field
const nameField = context.fieldsMap['Name'];
console.log(`Name field type: ${nameField.type}`);

Airtable Object: Fetching Records

The airtable object allows you to interact with your Airtable base, primarily for fetching additional records.

Key Method:

  • airtable.fetchRecords(tableId: string, options: AirtableSelectOptions): Promise<AirtableRecordsResult>

    • Fetches records from a specified table

    • Returns a promise that resolves to an object containing the fetched records and an optional offset

Examples:

async function fetchActiveUsers() {
try {
const result = await airtable.fetchRecords('tblUsers', {
fields: ['Name', 'Email', 'Status'],
filterByFormula: "Status='Active'",
maxRecords: 100
});

console.log(`Fetched ${result.records.length} active users`);

for (const user of result.records) {
console.log(`User: ${user.getCellValue('Name')}, Email: ${user.getCellValue('Email')}`);
}

if (result.offset) {
console.log('More records available. Use the offset for pagination.');
}
} catch (error) {
console.error('Error fetching records:', error);
}
}

Caching with the Cache Namespace

The cache namespace provides functions for caching data across script runs, which can significantly improve performance for frequently accessed data.

Key Methods:

  • cache.get(key: string): Promise<string | null>

    • Retrieves the value of a cache key

  • cache.multipleGet(keys: string[]): Promise<(string | null)[]>

    • Retrieves values for multiple cache keys

  • cache.set(key: string, value: string, expiration?: number): Promise<void>

    • Sets the value of a cache key with an optional expiration time (in seconds)

  • cache.del(key: string): Promise<void>

    • Deletes a cache key

Examples:

async function cacheExample() {
// Set a cache value
await cache.set('lastUpdateTime', Date.now().toString(), 3600); // Expires in 1 hour

// Get a cache value
const lastUpdateTime = await cache.get('lastUpdateTime');
if (lastUpdateTime) {
console.log(`Last update was at: ${new Date(parseInt(lastUpdateTime))}`);
} else {
console.log('No previous update recorded');
}

// Get multiple cache values
const [value1, value2] = await cache.multipleGet(['key1', 'key2']);

// Delete a cache key
await cache.del('oldKey');
}

File Storage

The storage object allows you to upload temporary files, which can be useful for generating reports or processing data.

Key Method:

  • storage.uploadTempFile(filename: string, content: string | Buffer, mimeType: string): Promise<string>

    • Uploads a file to temporary storage

    • Returns a promise that resolves to the URL of the uploaded file

Example:

async function generateAndUploadReport() {
const reportContent = 'This is the content of the report...';
try {
const fileUrl = await storage.uploadTempFile('report.txt', reportContent, 'text/plain');
console.log(`Report uploaded successfully. URL: ${fileUrl}`);
// You can now use this URL to reference the file in your Airtable record
record.set('Report URL', fileUrl);
} catch (error) {
console.error('Error uploading file:', error);
}
}

Define User Inputs with the UI Namespace

The ui namespace provides functions for creating user interfaces for your scripts. This can be used instead of the user input fields provided in the script settings and allows for more flexibility.

Key Functions:

  • ui.createField(type: string, propertyKey: string, config: object): MetaBulkRunnerUIFields

    • Creates a field for the UI

  • ui.createSection(name: string, config: { fields: MetaBulkRunnerUIFields[] }): MetaBulkRunnerUISection

    • Creates a section in the UI containing multiple fields

  • ui.buildSections(sections: MetaBulkRunnerUISection[]): object

    • Builds the final UI configuration from the defined sections

Field Types:

  • 'text': For text input

  • 'number': For numeric input

  • 'airtableFields': For selecting Airtable fields

  • 'workspaceSecrets': For accessing workspace secrets

Example:

const sections = [
ui.createSection('User Details', {
fields: [
ui.createField('text', 'userName', {
title: 'User Name',
required: true
}),
ui.createField('number', 'userAge', {
title: 'User Age',
required: true
}),
ui.createField('airtableFields', 'fieldsToUpdate', {
title: 'Fields to Update',
inputOrOutput: 'input',
selectSingle: false
})
]
}),
ui.createSection('Additional Settings', {
fields: [
ui.createField('workspaceSecrets', 'apiKey', {
title: 'API Key',
required: true
})
]
})
];

const config = ui.buildSections(sections);

function run() {
const { userName, userAge, fieldsToUpdate, apiKey } = config;
// Use these values in your script...
}

Working with Airtable Fields

When working with Airtable fields, it's important to understand the different field types and how to interact with them.

Common Field Types:

  • singleLineText

  • multilineText

  • number

  • checkbox

  • singleSelect

  • multipleSelects

  • date

  • dateTime

  • attachment

  • multipleRecordLinks

Handling Different Field Types:

function handleField(fieldName: string) {
const fieldType = context.fieldsMap[fieldName].type;
const value = record.get(fieldName);

switch (fieldType) {
case 'number':
return value as number;
case 'checkbox':
return value as boolean;
case 'multipleSelects':
return (value as string[]).join(', ');
case 'date':
case 'dateTime':
return new Date(value as string).toLocaleString();
default:
return value as string;
}
}

Advanced Techniques and Best Practices

  1. Error Handling: Always use try-catch blocks to handle potential errors gracefully.

    try {
    // Your code here
    } catch (error) {
    console.error('An error occurred:', error);
    // Handle the error appropriately
    }
  2. Asynchronous Operations: Use async/await for cleaner asynchronous code.

    async function processRecord() {
    try {
    const result = await someAsyncOperation();
    // Process the result
    } catch (error) {
    console.error('Error in async operation:', error);
    }
    }
  3. Modularization: Break your code into smaller, reusable functions for better maintainability.

  4. Type Safety: Leverage TypeScript's type system to catch errors early and improve code quality.

    interface UserData {
    name: string;
    age: number;
    email: string;
    }

    function processUser(user: UserData) {
    // Process user data
    }
  5. Performance Optimization: Use caching for expensive operations and limit the number of API calls.

  6. Consistent Naming: Use clear and consistent naming conventions for variables, functions, and UI elements.

  7. Documentation: Add comments to explain complex logic or non-obvious decisions in your code.

Comprehensive Example: User Data Updater

Let's put everything together in a comprehensive example that updates user data and logs the changes, demonstrating the following concepts:

  1. Creating a user interface for input

  2. Using the cache to store and retrieve the last update time

  3. Updating multiple fields in the record

  4. Generating and uploading a report file

  5. Error handling and logging

Happy scripting!

// Define UI
const sections = [
ui.createSection('User Update', {
fields: [
ui.createField('text', 'newStatus', {
title: 'New Status',
required: true
}),
ui.createField('airtableFields', 'fieldsToUpdate', {
title: 'Fields to Update',
inputOrOutput: 'input',
selectSingle: false
})
]
})
];

const config = ui.buildSections(sections);

// Helper function to get last update time
async function getLastUpdateTime(recordId: string): Promise<string | null> {
return await cache.get(`lastUpdate_${recordId}`);
}

// Helper function to set last update time
async function setLastUpdateTime(recordId: string): Promise<void> {
await cache.set(`lastUpdate_${recordId}`, new Date().toISOString(), 86400); // Cache for 24 hours
}

// Main function
async function run() {
const { newStatus, fieldsToUpdate } = config;

try {
// Check last update time
const lastUpdateTime = await getLastUpdateTime(record.id);
if (lastUpdateTime) {
console.log(`Last updated: ${lastUpdateTime}`);
}

// Update fields
for (const field of fieldsToUpdate) {
record.set(field, newStatus);
}

// Log the change
console.log(`Updated ${fieldsToUpdate.length} fields to "${newStatus}"`);

// Set new update time
await setLastUpdateTime(record.id);

// Generate and upload report
const reportContent = `User Update Report\\n\\nRecord ID: ${record.id}\\nUpdated Fields: ${fieldsToUpdate.join(', ')}\\nNew Status: ${newStatus}\\nUpdate Time: ${new Date().toISOString()}`;
const reportUrl = await storage.uploadTempFile('update_report.txt', reportContent, 'text/plain');
record.set('Last Update Report', reportUrl);

} catch (error) {
console.error('Error updating user data:', error);
}
}
Did this answer your question?