---
title: CustomScript Design Best Practices
slug: customscript-design-best-practices
createdAt: 2026-01-14T01:12:55.592Z
---

CustomScripts allow Activate services to integrate with external systems during provisioning. When services are processed immediately, scripts often run within the context of the original request. However, when services are processed on a future effective date, the execution context changes.

This guide explains how CustomScripts are executed in Activate, why effective-dated services behave differently, and how to design scripts that work reliably in all scenarios.

# Purpose

This document provides guidance on writing CustomScripts that are safe to use with:

- Effective-dated services&#x20;
- System-initiated provisioning&#x20;
- Reprocessing and retries&#x20;

It highlights common design mistakes and provides clear guidance to prevent them.

# How CustomScripts Are Executed

When a service is provisioned, Activate invokes any CustomScript defined on the service parameters. The script is called by Activate Orchestrator using the current ServiceInstance as the execution context.

Example invocation:

```csharp
object r = ProvisioningSystem.Evaluator.ScriptEngine.Call(
    this,
    Service.GetParameters("Script"),
    "Create",
    new object[] { this }
);
```

In this call:

- this represents the current **ServiceInstance**&#x20;
- The Create, Update, or Delete method receives the ServiceInstance as an argument&#x20;

The ServiceInstance represents the service being provisioned and contains the data required to perform provisioning actions.

# Effective-Dated Services and Job Context

When a service has a future effective date, it is not processed immediately. Instead, Activate Orchestrator processes it automatically on the effective date using a **system-generated job**.

This is expected behaviour.

Key implications:

- The job executing the service is **not** the original user-initiated job&#x20;
- User request payloads are **not available** on the Job&#x20;
- Job arguments may be empty or system-generated&#x20;

Effective-dated services always run under a system job when processed by Activate Orchestrator.

# Common Design Pitfalls

## Relying on Job Context for Service Data

A common mistake is writing CustomScripts that extract provisioning data from the Job context.

Example of incorrect usage:

```csharp
string userPrincipalName =
    Job.Arguments.GetString("=//Job/services/instance/0/user[userprincipalname]");
```

This approach may appear to work when:

- The service is processed immediately&#x20;
- The script runs in the same job that captured the request&#x20;

However, it fails when:

- The service is processed on its effective date&#x20;
- The service is reprocessed&#x20;
- The service runs under a system job&#x20;

In these cases, the expected data is no longer present on the Job.

## Referencing ServiceInstances by Index

Another error-prone pattern is referencing ServiceInstances by position within a job, for example:

:::BlockQuote
\=//Job/services/instance/0
:::

This is unsafe because:

- A job may contain multiple ServiceInstances&#x20;
- The order of services in the job is not guaranteed&#x20;
- The service being processed may not be at the expected index&#x20;

This can result in scripts reading data from the wrong service instance.

ServiceInstances should never be identified by index within a Job.

# Correct Data Access Pattern

CustomScripts must retrieve all required data from the **ServiceInstance** or related objects, not from the Job.

Recommended examples:

```csharp
string userPrincipalName = srv.User.UserPrincipalName;
string role = Evaluator.GetString(srv, "=/Data/role");
```

Why this works:

- The ServiceInstance reflects the current state of the service&#x20;
- Service data is populated consistently regardless of job type&#x20;
- The script behaves the same for immediate and effective-dated processing&#x20;

The ServiceInstance is the authoritative source of provisioning data for CustomScripts.

# Using srv.Job (With Caution)

The ServiceInstance provides a reference to the most recent job that processed or otherwise interacted with it via srv.Job.

This can be useful for:

- Diagnostic logging&#x20;
- Correlation identifiers&#x20;
- Advanced scenarios where job metadata is explicitly required&#x20;

However, this reference must be used sparingly.

A service may be:

- Reprocessed&#x20;
- Updated&#x20;
- Evaluated&#x20;
- Included in background or system jobs&#x20;

As a result, srv.Job may not represent the original request or the job you expect.

If data logically belongs to the service, retrieve it from the ServiceInstance rather than from srv.Job.

# Why Scripts May Appear Not to Run

When a CustomScript relies on Job data, failures can be misleading.

From an operator’s perspective, it may appear that:

- The script did not run&#x20;
- The CustomScript was skipped&#x20;

In reality:

- The script was executed&#x20;
- Required data was missing&#x20;
- Internal logic did not complete as expected&#x20;

Early return statements can further obscure this behaviour by preventing later logic and logging from executing.

# Best Practices for CustomScript Design

Follow these guidelines when authoring CustomScripts:

- Always use the ServiceInstance (srv) for service-related data&#x20;
- Avoid referencing Job arguments for provisioning logic&#x20;
- Never identify ServiceInstances by index within a Job&#x20;
- Use srv.Job only when job metadata is explicitly required&#x20;
- Assume scripts may run under system jobs&#x20;
- Design scripts to be idempotent where possible&#x20;
- Avoid early return statements that bypass core logic&#x20;
- Log clearly when required service data is missing&#x20;

# CustomScript Rules of Thumb

Use these rules as a quick check when writing or reviewing CustomScripts.

## Data Ownership

- If the data belongs to the service, read it from the ServiceInstance&#x20;
- If the data belongs to the user, read it from srv.User&#x20;
- If the data belongs to the job, question whether you really need it&#x20;

Most CustomScripts should not need to read from the Job at all.

## Job Usage

- Do not rely on Job arguments for provisioning logic&#x20;
- Expect services to run under system jobs, not user-initiated jobs&#x20;
- Use srv.Job only for logging, diagnostics, or correlation metadata&#x20;
- Never assume srv.Job represents the original request&#x20;

## Service Identification

- Never reference ServiceInstances by index within a Job&#x20;
- Do not assume a service is at position 0 or any fixed index&#x20;
- Always work with the ServiceInstance passed into the script&#x20;

## Execution Assumptions

- Assume scripts may run:&#x20;
  - On an effective date&#x20;
  - As part of reprocessing&#x20;
  - Under background or system jobs&#x20;
- Write scripts that behave consistently regardless of execution context&#x20;

## Control Flow and Logging

- Avoid early return statements that bypass core logic&#x20;
- Log clearly when required service data is missing&#x20;
- Prefer explicit validation over silent failure&#x20;

## Mental Model

- The ServiceInstance is the unit of provisioning&#x20;
- Jobs are an execution mechanism, not a data source&#x20;
- Scripts should describe what to do for this service, not how the request was submitted&#x20;

# Summary

Effective-dated services are processed by Activate Orchestrator using system jobs. This is expected and by design.

To ensure CustomScripts run reliably:

- Treat the ServiceInstance as the authoritative source of service data&#x20;
- Avoid Job-based assumptions in script logic&#x20;
- Design scripts to function independently of how or when they are executed&#x20;

Following these principles ensures consistent provisioning behaviour across immediate, scheduled, and system-initiated service processing.
