Writing Extensions

Extensions are shared libraries (.so) written in C that register new JavaScript functions and objects into the runtime.

Extension Structure

Every extension needs:

  1. Include js-cgi-module.h
  2. An init function that registers globals
  3. An optional shutdown function for cleanup
  4. The JSCGI_MODULE macro to export the entry point

Minimal Example

#include "js-cgi-module.h"

static JSValue js_hello(JSContext *ctx, JSValueConst this_val,
                        int argc, JSValueConst *argv) {
    return JS_NewString(ctx, "Hello from my extension!");
}

static int my_ext_init(JSContext *ctx, JSValue global) {
    JS_SetPropertyStr(ctx, global, "hello",
        JS_NewCFunction(ctx, js_hello, "hello", 0));
    return 0;
}

static int my_ext_shutdown(void) {
    return 0;
}

JSCGI_MODULE(my_ext, "1.0.0", my_ext_init, my_ext_shutdown);

Building

gcc -shared -fPIC -O2 -I/usr/include/js-cgi -o my_ext.so my_ext.c

The -I flag points to the directory containing js-cgi-module.h (installed to /usr/include/js-cgi/ or included in the download archive).

Installing

sudo cp my_ext.so /usr/lib/js-cgi/modules/

Add to /etc/js-cgi/js-cgi.ini:

extension = my_ext.so

The JSCGI_MODULE Macro

JSCGI_MODULE(name, version, init_function, shutdown_function);
ParameterDescription
nameExtension identifier (no quotes, valid C identifier)
versionVersion string
init_functionCalled on startup, receives JSContext and global object
shutdown_functionCalled on shutdown, can be NULL

Registering Functions

The init function receives the js-cgi context and global object. Use the js-cgi C API to register functions:

static JSValue js_add(JSContext *ctx, JSValueConst this_val,
                      int argc, JSValueConst *argv) {
    int a, b;
    JS_ToInt32(ctx, &a, argv[0]);
    JS_ToInt32(ctx, &b, argv[1]);
    return JS_NewInt32(ctx, a + b);
}

static int math_init(JSContext *ctx, JSValue global) {
    JSValue math = JS_NewObject(ctx);
    JS_SetPropertyStr(ctx, math, "add",
        JS_NewCFunction(ctx, js_add, "add", 2));
    JS_SetPropertyStr(ctx, global, "math", math);
    return 0;
}

Working with Arguments

// Get string argument
const char *str = JS_ToCString(ctx, argv[0]);
// ... use str ...
JS_FreeCString(ctx, str);

// Get integer
int32_t num;
JS_ToInt32(ctx, &num, argv[0]);

// Get float
double d;
JS_ToFloat64(ctx, &d, argv[0]);

// Check argument count
if (argc < 2) {
    return JS_ThrowTypeError(ctx, "Expected 2 arguments");
}

Returning Values

// String
return JS_NewString(ctx, "hello");

// Integer
return JS_NewInt32(ctx, 42);

// Float
return JS_NewFloat64(ctx, 3.14);

// Boolean
return JS_NewBool(ctx, 1);

// Null
return JS_NULL;

// Object
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "name", JS_NewString(ctx, "Alice"));
JS_SetPropertyStr(ctx, obj, "age", JS_NewInt32(ctx, 30));
return obj;

// Array
JSValue arr = JS_NewArray(ctx);
JS_SetPropertyUint32(ctx, arr, 0, JS_NewString(ctx, "a"));
JS_SetPropertyUint32(ctx, arr, 1, JS_NewString(ctx, "b"));
return arr;

// Error
return JS_ThrowTypeError(ctx, "Something went wrong: %s", detail);

Linking External Libraries

If your extension uses an external library, link it at compile time:

# Example: extension using libpq (PostgreSQL)
gcc -shared -fPIC -O2 -I/usr/include/js-cgi -o pg.so pg.c -lpq

The main js-cgi binary exports all required symbols, so extensions resolve them at runtime without additional linking.

Shutdown Function

The shutdown function is called after script execution, before the runtime is freed. Use it to clean up resources:

static int my_ext_shutdown(void) {
    // Close connections, free memory, etc.
    return 0;
}
Was this page helpful?