Migrate scripts to the V8 runtime Stay organized with collections Save and categorize content based on your preferences.
Page Summary
The Rhino runtime for Apps Script is being retired on or after January 31, 2026, requiring migration to the V8 runtime.
Migrating to the V8 runtime primarily involves enabling it but also requires addressing incompatibilities and other differences in script behavior.
The migration procedure includes enabling V8, reviewing and correcting code for incompatibilities and other differences, updating syntax, thorough testing, and updating deployments and versions.
Several specific incompatibilities exist, such as avoiding
for each...in,Date.prototype.getYear(), reserved keywords as names, reassigningconstvariables, XML literals/objects,__iterator__functions, conditional catch clauses, andObject.prototype.toSource().Other behavioral differences in V8 include changes in locale-specific date/time formatting, lack of
Error.fileName/lineNumber, different handling of stringified enum objects and undefined parameters, adjustments needed for globalthis, and different behavior forinstanceofand non-shared resources in libraries.
The Rhino runtime is turning down on or after January 31, 2026. If you havean existing script using the Rhino runtime, you must migrate the script to V8.
Often the only prerequisite to adding V8 syntax andfeatures to a script isenabling the V8 runtime.However, there is a small set ofincompatibilitiesandother differences that can result in a scriptfailing or behaving unexpectedly in the V8 runtime. As you migratea script to use V8, you must search the script project for these issues andcorrect any you find.
V8 migration procedure
To migrate a script to V8, follow this procedure:
- Enable the V8 runtimefor the script. The
runtimeVersioncan be checked using themanifest for the Apps Script project. - Carefully review the followingincompatibilites.Examine your script to determine if any of theincompatibilities are present; if one or more incompatibilities are present,adjust your script code to remove or avoid the issue.
- Carefully review the followingother differences.Examine your script to determine if any of the listed differences impactyour code's behavior. Adjust your script to correct the behavior.
- Once you have corrected any discovered incompatibilities or otherdifferences, you can begin updating your code to useV8 syntax and other features.
- After finishing your code adjustments, thoroughly test your script to makesure it behaves as expected.
- If your script is a web app or publishedadd-on,you mustcreate a new versionof the script with the V8 adjustments, and point the deployment to the newlycreated version. To make the V8 version available to users, you mustre-publish the script with this version.
- If your script is used as a library then create a new versioned deployment ofyour script. Communicate this new version to all scripts and users thatconsume your library, instructing them to update to the V8-enabled version.Verify that any older, Rhino-based versions of your library are no longer inactive use or accessible.
- Verify that no instances of your script are still operating on the legacyRhino runtime. Verify that alldeploymentsare associated with a version which is on V8. Archive olddeployments. Review all theversions anddelete the versions which are not using V8 Runtime.
Incompatibilities
The original Rhino-based Apps Script runtime unfortunatelypermitted severalnon-standard ECMAScript behaviors. Since V8 is standards compliant, thesebehaviors aren't supported after migration. Failing to correct these issuesresults in errors or broken script behavior once the V8 runtime is enabled.
The following sections describe each of these behaviors and steps you must taketo correct your script code during migration to V8.
Avoidfor each(variable in object)
Thefor each (variable in object)statement was added to JavaScript 1.6, and removed in favor offor...of.
When migrating your script to V8, avoid usingfor each (variable in object)statements.
Instead, usefor (variable in object):
//Rhinoruntimevarobj={a:1,b:2,c:3};//Don't use 'foreach' in V8foreach(varvalueinobj){Logger.log("value =%s",value);} | //V8runtimevarobj={a:1,b:2,c:3};for(varkeyinobj){//OKinV8varvalue=obj[key];Logger.log("value = %s",value);} |
AvoidDate.prototype.getYear()
In the original Rhino runtime,Date.prototype.getYear()returns two-digit years for years from 1900-1999, but four-digit years for otherdates, which was the behavior in JavaScript 1.2 and earlier.
In the V8 runtime,Date.prototype.getYear()returns the year minus 1900 instead as required byECMAScript standards.
When migrating your script to V8, always useDate.prototype.getFullYear(),which returns a four-digit year regardless of the date.
Avoid using reserved keywords as names
ECMAScript prohibits the use of certainreserved keywordsin function and variable names. The Rhino runtime allowed many of these words,so if your code uses them, you must rename your functions or variables.
When migrating your script to V8, avoid naming variable or functionsusing one of thereserved keywords.Rename any variable or function to avoid using the keyword name. Common usesof keywords as names areclass,import, andexport.
functionclass(){}//SyntaxerrorinV8.varobj={class:1};//Allowed.
Avoid reassigningconst variables
In the original Rhino runtime, you can declare a variable usingconst whichmeans the value of the symbol never changes and future assignments to thesymbol are ignored.
In the new V8 runtime, theconst keyword is standard compliant and assigningto a variable declared as aconst results in aTypeError: Assignment to constant variable runtime error.
When migrating your script to V8, do not attempt to reassign the value ofaconst variable:
//Rhinoruntimeconstx=1;x=2;//Noerrorconsole.log(x);//Outputs1 | //V8runtimeconstx=1;x=2;//ThrowsTypeErrorconsole.log(x);//Neverexecuted |
Avoid XML literals and the XML object
Thisnon-standard extensionto ECMAScript allows Apps Script projects to use XML syntax directly.
When migrating your script to V8, avoid using direct XML literals or the XMLobject.
Instead, use theXmlService toparse XML:
//V8runtimevarincompatibleXml1=<container><item/></container>;//Don't usevarincompatibleXml2=newXML('<container><item/></container>');//Don't usevarxml3=XmlService.parse('<container><item/></container>');//OK |
Don't build custom iterator functions using__iterator__
JavaScript 1.7 added a feature to allow adding a custom iterator to any classby declaring an__iterator__ function in that class's prototype; this wasalso added into Apps Script's Rhino runtime as a developerconvenience. However,this feature was never part of theECMA-262 standardand was removed in ECMAScript-compliant JavaScript engines. Scripts using V8can't use this iterator construction.
When migrating your script to V8, avoid__iterator__ function to buildcustom iterators. Instead,useECMAScript 6 iterators.
Consider the following array construction:
//CreateasamplearrayvarmyArray=['a','b','c'];//AddapropertytothearraymyArray.foo='bar';//Thedefaultbehaviorforanarrayistoreturnkeysofallproperties,//including'foo'.Logger.log("Normal for...in loop:");for(variteminmyArray){Logger.log(item);//Logs0,1,2,foo}//Toonlylogthearrayvalueswith`for..in`,acustomiteratorcanbeused. |
The following code examples show how an iterator could be constructed in theRhino runtime, and how to construct a replacement iterator in the V8 runtime:
//RhinoruntimecustomiteratorfunctionArrayIterator(array){this.array=array;this.currentIndex=0;}ArrayIterator.prototype.next=function(){if(this.currentIndex>=this.array.length){throwStopIteration;}return"["+this.currentIndex+"]="+this.array[this.currentIndex++];};//DirectmyArraytousethecustomiteratormyArray.__iterator__=function(){returnnewArrayIterator(this);}Logger.log("With custom Rhino iterator:");for(variteminmyArray){//Logs[0]=a,[1]=b,[2]=cLogger.log(item);} | //V8runtime(ECMAScript6)customiteratormyArray[Symbol.iterator]=function(){varcurrentIndex=0;vararray=this;return{next:function(){if(currentIndex <array.length){return{value:"[${currentIndex}]="+array[currentIndex++],done:false};}else{return{done:true};}}};}Logger.log("With V8 custom iterator:");//Mustusefor...ofsince//for...indoesn't expect an iterable.for(varitemofmyArray){//Logs[0]=a,[1]=b,[2]=cLogger.log(item);} |
for...of when traversing arrays withcustom iterators, sincefor..in doesn't expect iterables.Avoid conditional catch clauses
The V8 runtime doesn't supportcatch..if conditional catch clauses, as theyare not standard-compliant.
When migrating your script to V8, move any catch conditionals inside thecatch body:
// Rhino runtimetry{doSomething();}catch(eifeinstanceofTypeError){// Don't use// Handle exception} | // V8 runtimetry{doSomething();}catch(e){if(einstanceofTypeError){// Handle exception}} |
Avoid usingObject.prototype.toSource()
JavaScript 1.3 contained aObject.prototype.toSource()method that was never part of any ECMAScript standard. It is not supported inthe V8 runtime.
When migrating your script to V8, remove any use ofObject.prototype.toSource()from your code.
Other differences
In addition to the preceding incompatibilities that can cause script failures,thereare a few other differences that, if uncorrected, might result in unexpected V8runtime script behavior.
The following sections explain how to update your script code to avoid theseunexpected surprises.
Adjust locale-specific date and time formatting
TheDatemethodstoLocaleString(),toLocaleDateString(),andtoLocaleTimeString()behave differently in the V8 runtime as compared to Rhino.
In Rhino, the default format is thelong format, and any parameters passed inareignored.
In the V8 runtime, the default format is theshort format and parameterspassed in are handled according to the ECMA standard (see thetoLocaleDateString() documentationfor details).
When migrating your script to V8, test and adjust your code's expectationsregarding the output of locale-specific date and time methods:
//Rhinoruntimevarevent=newDate(Date.UTC(2012,11,21,12));//Outputs"December 21, 2012"inRhinoconsole.log(event.toLocaleDateString());//Alsooutputs"December 21, 2012",//ignoringtheparameterspassedin.console.log(event.toLocaleDateString('de-DE',{year:'numeric',month:'long',day:'numeric'})); | //V8runtimevarevent=newDate(Date.UTC(2012,11,21,12));//Outputs"12/21/2012"inV8console.log(event.toLocaleDateString());//Outputs"21. Dezember 2012"console.log(event.toLocaleDateString('de-DE',{year:'numeric',month:'long',day:'numeric'})); |
Avoid usingError.fileName andError.lineNumber
In the V8 runtime, the standard JavaScriptErrorobject doesn't support thefileName orlineNumber as constructor parametersor object properties.
When migrating your script to V8,remove any dependence onError.fileName andError.lineNumber.
An alternative is to use theError.prototype.stack.This stack is also non-standard, but supported in V8. Theformat of the stack trace produced by the two platforms is slightly different:
// Rhino runtime Error.prototype.stack// stack trace formatatfilename:92(innerFunction)atfilename:97(outerFunction) | // V8 runtime Error.prototype.stack// stack trace formatError:errormessageatinnerFunction(filename:92:11)atouterFunction(filename:97:5) |
Adjust handling of stringified enum objects
In the original Rhino runtime, using the JavaScriptJSON.stringify()method on an enum object only returns{}.
In V8, using the same method on an enum object retuns the enum name.
When migrating your script to V8,test and adjust your code's expectations regarding the output ofJSON.stringify()on enum objects:
//RhinoruntimevarenumName=JSON.stringify(Charts.ChartType.BUBBLE);//enumNameevaluatesto{} | //V8runtimevarenumName=JSON.stringify(Charts.ChartType.BUBBLE);//enumNameevaluatesto"BUBBLE" |
Adjust handling of undefined parameters
In the original Rhino runtime, passingundefined to a method as a parameterresulted in passing the string"undefined" to that method.
In V8, passingundefined to methods is equivalent to passingnull.
When migrating your script to V8,test and adjust your code's expectations regardingundefined parameters:
// Rhino runtimeSpreadsheetApp.getActiveRange().setValue(undefined);// The active range now has the string// "undefined" as its value. | // V8 runtimeSpreadsheetApp.getActiveRange().setValue(undefined);// The active range now has no content, as// setValue(null) removes content from// ranges. |
Adjust handling of globalthis
The Rhino runtime defines an implicit special context for scripts that use it.Script code runs in this implicit context, distinct from the actual globalthis. This means that references to the "globalthis" in the code actuallyevaluate to the special context, which only contains the code and variablesdefined in the script. The built-in Apps Script services andECMAScript objectsare excluded from this use ofthis. This situation was similar to thisJavaScript structure:
//Rhinoruntime//AppsScriptbuilt-inservicesdefinedhere,intheactualglobalcontext.varSpreadsheetApp={openById:function(){...}getActive:function(){...}//etc.};function(){//Implicitspecialcontext;allyourcodegoeshere.Iftheglobalthis//isreferencedinyourcode,itonlycontainselementsfromthiscontext.//Anyglobalvariablesyoudefined.varx=42;//Yourscriptfunctions.functionmyFunction(){...}//Endofyourcode.}(); |
In V8, the implicit special context is removed. Global variables and functionsdefined in the script are placed in the global context, beside the built-inApps Script services and ECMAScript built-ins likeMath andDate.
When migrating your script to V8, test and adjust your code's expectationsregarding the use ofthis in a global context. In most cases the differencesare only apparent if your code examines the keys or property names of theglobalthis object:
//RhinoruntimevarmyGlobal=5;functionmyFunction(){//Onlylogs[myFunction,myGlobal];console.log(Object.keys(this));//Onlylogs[myFunction,myGlobal];console.log(Object.getOwnPropertyNames(this));} | //V8runtimevarmyGlobal=5;functionmyFunction(){//Logsanarraythatincludesthenames//ofAppsScriptservices//(CalendarApp,GmailApp,etc.)in//additiontomyFunctionandmyGlobal.console.log(Object.keys(this));//Logsanarraythatincludesthesame//valuesasabove,andalsoincludes//ECMAScriptbuilt-inslikeMath,Date,//andObject.console.log(Object.getOwnPropertyNames(this));} |
Adjust handling ofinstanceof in libraries
Usinginstanceof in a library on an object that is passed as a parameter in afunction from another project can give false negatives. In the V8 runtime, aproject and its libraries are run in different execution contexts and hence havedifferent globals and prototype chains.
Note that this is only the case if your library usesinstanceof on an objectthat is not created in your project. Using it on an object that is created inyour project, whether in the same or a different script inside your project,should work as expected.
If a project that’s running on V8 uses your script as a library, check if yourscript usesinstanceof on a parameter that will be passed from anotherproject. Adjustthe usage ofinstanceof and use other feasible alternatives as per your usecase.
One alternative fora instanceof b can be to use the constructor ofa incases where you don't need to search the entire prototype chain and just checkthe constructor.Usage:a.constructor.name == "b"
Consider Project A and Project B where Project A uses Project B as a library.
//Rhinoruntime//ProjectAfunctioncaller(){vardate=newDate();//ReturnstruereturnB.callee(date);}//ProjectBfunctioncallee(date){//Returnstruereturn(dateinstanceofDate);} | //V8runtime//ProjectAfunctioncaller(){vardate=newDate();//ReturnsfalsereturnB.callee(date);}//ProjectBfunctioncallee(date){//Incorrectlyreturnsfalsereturn(dateinstanceofDate);//Considerusingreturn(date.constructor.name==//“Date”)instead.//return(date.constructor.name==“Date”)->Returns//true} |
Another alternative can be to introduce a function that checksinstanceof in the main projectand pass the function in addition to other parameters when calling a library function. The passed functioncan then be used to checkinstanceof inside the library.
//V8runtime//ProjectAfunctioncaller(){vardate=newDate();//ReturnsTruereturnB.callee(date,date=>dateinstanceofDate);}//ProjectBfunctioncallee(date,checkInstanceOf){//ReturnsTruereturncheckInstanceOf(date);} |
Adjust passing of non-shared resources to libraries
Passing anon-shared resource from the main script to a library works differently in the V8 runtime.
In the Rhino runtime, passing a non-shared resource won't work. The library uses its own resource instead.
In the V8 runtime, passing a non-shared resource to the library works. The library uses the passed non-shared resource.
Do not pass non-shared resources as function parameters. Always declare non-shared resources in the same script that uses them.
Consider Project A and Project B where Project A uses Project B as a library. In this example,PropertiesService is a non-shared resource.
// Rhino runtime// Project AfunctiontestPassingNonSharedProperties(){PropertiesService.getScriptProperties().setProperty('project','Project-A');B.setScriptProperties();// Prints: Project-BLogger.log(B.getScriptProperties(PropertiesService,'project'));} | // V8 runtime// Project AfunctiontestPassingNonSharedProperties(){PropertiesService.getScriptProperties().setProperty('project','Project-A');B.setScriptProperties();// Prints: Project-ALogger.log(B.getScriptProperties(PropertiesService,'project'));} |
JDBC recommendations in V8 Runtime
With V8 runtime, we have added new features to the JDBC service.
UseexecuteBatch for batch operations
You can useexecuteBatch(params) operations to perform batch database operations.
The following example shows how to insert multiple rows into a database using batching:
Here's the Rhino runtime (old method):
varconn=Jdbc.getCloudSqlConnection("jdbc:google:mysql://...");varstmt=conn.prepareStatement("INSERT INTO employees (name, age) VALUES (?, ?)");varparams=[["John Doe", 30],["John Smith", 25]];for(vari=0;i <params.length;i++){stmt.setString(1,params[i][0]);stmt.setInt(2,params[i][1]);stmt.execute();}
Here's the V8 runtime (new method):
varconn=Jdbc.getCloudSqlConnection("jdbc:google:mysql://...");varstmt=conn.prepareStatement("INSERT INTO employees (name, age) VALUES (?, ?)");varparams=[["John Doe",30],["John Smith",25]];stmt.executeBatch(params);
UsegetRows to fetch result set
You can usegetRows(queryString) to fetch result set data in one call.ThequeryString consists of comma-separated calls to getter methods ofJdbcResultSet, for example:"getString(1), getDouble('price'), getDate(3, 'UTC')".Supported methods include all getter methods which are responsible for reading column data, for example,getHoldability,getMetaData etc are not supported. Arguments can be integer column indexes (1-based) or single or double quoted string column labels.
The following example shows you how to fetch rows from result set:
Here's the Rhino runtime (old method):
varconn=Jdbc.getCloudSqlConnection("jdbc:google:mysql://...");varstmt=conn.createStatement();varrs=stmt.executeQuery("SELECT name, age FROM employees");while(rs.next()){Logger.log(rs.getString('name')+", "+rs.getInt('age'));}
Here's the V8 runtime (new method):
varconn=Jdbc.getCloudSqlConnection("jdbc:google:mysql://...");varstmt=conn.createStatement();varrs=stmt.executeQuery("SELECT name, age FROM employees");varrows=rs.getRows("getString('name'), getInt('age')");for(vari=0;i <rows.length;i++){Logger.log(rows[i][0]+", "+rows[i][1]);}
Update access to standalone scripts
For standalone scripts running on V8 runtime, you need to provide users at leastview access to the script in order for the script's triggers to work properly.
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2025-12-11 UTC.