Extension Review Guidelines

Backend Development

Error Codes

All error codes should describe the error itself and not the source of the error. The frontend should be able to decide what to do.

See also: General Error Codes

❌ Wrong

`ESOMEFEATURE`, `EBIGAPI`
✅ Right

`ETIMEOUT`, `EINVALIDNAME`

State

Do not save any state into the step file (except caching).

❌ Wrong

let persistentStorage = {}
module.exports = async (context, input) => {
  persistentStorage['foo'] = 'bar'
}
✅ Right

module.exports = async (context, input) => {
  await context.storage.extension.set('foo', 'bar')
}

Simplify Steps

Do not put all logic into one step. Instead, split it into smaller, logical parts.

Do not use console.log

Do not use any console output. The output is not forwarded to the log cluster and will be lost forever.

❌ Wrong

module.exports = async (context, input) => {
    console.log('I am wrong')
}
✅ Right

module.exports = async (context, input) => {
    context.log.debug('I am right')
}

Do not use get function to pass output

Do not pass any object where the key needs to be fetched via a get function. If you need to implement a get function, also implement the toJSON function.

❌ Wrong

class MyDTO {
  constructor(value1, value2) {
    this._value1 = value1
    this._value2 = value2
  }


  get value1() {
    return this._value1
  }

  get value2() {
    return this._value2
  }

}

module.exports = async (context, input) => {
  const dto = new MyDTO('foo', 'bar')
  return dto
}
✅ Right

module.exports = async (context, input) => {
  return {value1: 'foo', value2: 'bar'}
}
✅ Right

class MyDTO {
  constructor(value1, value2) {
    this._value1 = value1
    this._value2 = value2
  }


  get value1() {
    return this._value1
  }

  get value2() {
    return this._value2
  }

  toJSON() {
    return {
      value1: this._value1,
      value2: this._value2
    }
  }
}


module.exports = async (context, input) => {
  const dto = new MyDTO('foo', 'bar')
  return dto
}

Do not return null

Always return an empty object.

❌ Wrong

module.exports = async (context, input) => {
  return null
}
✅ Right

module.exports = async (context, input) => {
  return {}
}

Do not return an error, throw it

Do not return an error. It will be handled as a normal object.

❌ Wrong

module.exports = async (context, input) => {
  return new Error()
}
✅ Right

module.exports = async (context, input) => {
  throw new Error()
}

Do not overuse storage

Keep the count of storage as low as possible. Every request takes time to get answered, which delays the response time of the actual pipeline request.

Store only the essentials.

❌ Wrong

module.exports = async (context, input) => {
  const response = await request('https://awesomeDomain/', { resolveWithFullResponse: true })
  await context.storage.device.set('foo', response)
}
✅ Right

module.exports = async (context, input) => {
  const response = await request('https://awesomeDomain/', { resolveWithFullResponse: true })
  await context.storage.device.set('foo', response.body.keyToStore)
}

Do not share a logger instance between requests

The logger instance given in the context includes the request ID to improve log tracing.

Storing the logger instance statically and reusing it in another request destroys the traceability.

❌ Wrong

let logger = null

module.exports = async (context, input) => {
  logger = context.log

  logger.info('Do not do this')
}
✅ Right

module.exports = async (context, input) => {
  context.log.info('Do this')
}

Use automatic step insertion

Use automatic step insertion where possible. This approach makes it easier for merchants to install the app.

If a hook does not exist where needed, please provide Shopgate with this feedback.