Gold Motion logo Gold Motion Custom FX Docs

Add your own effects to Gold Motion

Gold Motion has a Custom category with a + button on it. Tap it, hand the app an XML file, and that effect shows up next to the built-in ones. No server, no account, no rebuild. The whole file is a header, a list of sliders, and a shader — and the header is the part everybody gets wrong.

1 fileper effect
77in the pack
9categories
V7.0.2+required

01 — Overview

What the Custom category actually does

Open the effects browser and you'll find a category called Custom. It ships with one entry and a + button above the list. Everything you add through that button lands here and behaves exactly like a stock effect — it goes on a layer, it has sliders, it animates, it deletes cleanly.

What happens when you tap +

Behind that one button the app does five things. Knowing the order saves you a lot of confusion later.

StepWhat the app does
1Opens a file picker. You choose a .xml file.
2Parses it with the same parser it uses for its own effects. If that fails you get “That XML is not a valid Gold Motion effect.”
3Copies the file into the app's private custom_effects/ folder and writes a small .meta.json beside it.
4Asks you for a thumbnail image. This is a required step, not an optional extra.
5Writes its own content:// URI back into the header's thumb attribute.
Step 5 is the whole story

The app does not just read your header — it edits it. That single fact is the source of the one mistake nearly everybody makes, and it's covered in full in the next section.

Version note

The Custom + button does not exist in Gold Motion V6.x. This feature arrived in V7. If you are on an older build you will not find the button at all — update to V7.0.2 or newer before anything on this page applies.



03 — Import

Importing an effect, step by step

This is the real order of operations, including the thumbnail prompt most guides forget to mention.

Get the .xml onto your phone

Copy the file anywhere you can browse to from a file picker — Downloads, Drive, Telegram, a folder in your gallery. There is no special folder to drop it in; the + button asks you to find it each time.

Open Gold Motion and select a layer

Any layer — photo, video, shape, text. An effect needs a layer to attach to, so have one selected before you go into the effects browser.

Open the effects browser and find Custom

Scroll the category list until you reach Custom. If you can't find it, you're on a build older than V7 — this category only exists from V7 onward.

Tap the + button

It sits at the top of the Custom category. That opens a document picker filtered to XML files.

Pick your .xml file

The app parses it straight away. If the header or the XML is broken you get “That XML is not a valid Gold Motion effect” and nothing is added — go back and check the header section above.

Pick a thumbnail when it asks

Immediately after a successful import, the app asks you for an image. Choose anything square — a frame from the clip, a poster, a screenshot. This is the picture that will appear on the effect's tile.

This step is why the thumb attribute has to exist. The app writes the path to the image you pick into that attribute. No attribute, no picture.

The effect appears in the Custom list

Tap it and it applies to your selected layer, with all your sliders live in the parameter panel.

Leave the Custom page and come back in

Don't skip this. The grid is built when you enter the page. Backing out to the category list and re-entering Custom forces it to rebuild and register the new effect properly — count, thumbnails and all.

Do this every time

Import → back out → re-enter. If an effect looks like it didn't take, this is almost always why.

Repeat from step 4 for each effect. Deleting is the same in reverse — the app removes the file and its metadata from its own folder.


04 — Controls

Controls — the sliders your users get

Everything inside <params> becomes a control in the parameter panel. Each one has an id, and that id is the variable name you use in the shader. That's the entire contract — same word in both places.

the link
<spinner id="amount" default="0.5" min="0.0" max="1.0" step="0.01" label="Amount" />
                 ^^^^^^
                 this becomes a variable called 'amount'

<shader type="fragment"><![CDATA[
void main(){
  float v = amount;   // use it directly - no declaration needed
}
]]></shader>
The most common shader crash

Using a name in the shader that isn't declared in <params> fails to compile, and the effect quietly does nothing. Declare it, or don't use it.

The control types

spinner — a number slider

spinner
<spinner id="amount" default="0.7" min="0.0" max="2.0" step="0.01" label="Amount" />
<spinner id="angle"  default="0"   min="-180" max="180" step="0.5" label="Angle" type="angle" />
<spinner id="alpha"  default="1.0" min="0.0" max="1.0" step="0.01" label="Alpha" type="percent" />
<spinner id="count"  default="8"   min="2"   max="64"  step="1"   label="Count" type="integer" />

type is cosmetic: percent shows 0–1 as a percentage, angle adds a dial, integer snaps to whole numbers. ticks="0, 0.5, 1" adds notch marks. slider is an older alias with identical attributes.

color — a colour picker

color
<color id="tint" default="#FFFFC24B" label="Tint" alpha="true" />

Format is #AARRGGBB — alpha first. In the shader it's a vec4, so use tint.rgb for the colour.

switch — an on/off toggle

switch
<switch id="vignetteOn" default="true" label="Add Vignette" />

Arrives as a boolean. Use it directly: if (vignetteOn) { … }

selector — a dropdown or radio list

selector
<selector id="mode" style="dropdown" default="0" label="Mode">
  <choice value="0" label="Normal" />
  <choice value="1" label="Multiply" />
  <choice value="2" label="Screen" />
</selector>

The id arrives as an integer holding the selected value.

texture — an image input

texture
<texture id="inputImg" srcType="content" />
<texture id="comp"     srcType="comp" />
<texture id="tmp"      srcType="buffer" downsample="2" />
<texture id="grain"    srcType="image" src="resource/grain.png" />
srcTypeMeaning
contentThe layer the effect sits on. Your inputImg. You almost always need exactly one.
compThe whole composition underneath — for blend effects.
bufferAn internal render target for multi-pass work. downsample="2" halves the resolution.
imageA bundled image, referenced with src="…".

point / xyz — vectors

point and xyz
<point id="center" default="0.5,0.5" label="Centre" />
<xyz   id="rotate" default="0,0,0"   label="Rotation" />

A point is a vec2, an xyz is a vec3.

section — a group header

section
<section text="Channel Gain" />

Pure layout. It splits the panel into labelled groups. Fifteen ungrouped sliders is unusable — use these.


05 — The shader

The shader — where the pixels happen

GLSL ES 1.00, the same language the app's own effects use. It runs once per pixel, on the GPU, every frame. You get the pixel position, you read the layer, you write a colour.

the smallest working effect
void main(){
  vec2 uv  = acScreenNorm;                       // where am I on screen, 0..1
  vec4 src = texture2DCv(inputImg.texture, uv);  // read the layer
  gl_FragColor = src;                            // write it back
}

That is a complete, valid effect that does nothing. Start from it and add arithmetic in the middle.

Four rules that decide whether it works

RuleWhy
Read with texture2DCv()The engine's sampler clamps safely at the edges. texture2D() also works but doesn't clamp.
Output is premultipliedWrite vec4(rgb * alpha, alpha). Skip the multiply and semi-transparent pixels glow wrong.
Un-premultiply before gradingThe input is premultiplied too. Do src.rgb / max(src.a, 1e-4) first, re-multiply at the end.
Helpers before main()gmLum() is not built in — the app's own effects define it in the shader with float gmLum(vec3 c){ return dot(c, vec3(0.2126, 0.7152, 0.0722)); }. Calling it without defining it fails silently.

An effect with everything in the right order

annotated
// Helper functions must be declared BEFORE main().
float luma(vec3 c){
  return dot(c, vec3(0.2126, 0.7152, 0.0722));   // Rec.709
}

void main(){
  vec2 uv  = acScreenNorm;
  vec4 src = texture2DCv(inputImg.texture, uv);

  float a = max(src.a, 1e-4);       // guard the divide
  vec3  c = src.rgb / a;            // un-premultiply -> straight colour

  // 'amount' and 'speed' are ids declared in .
  float pulse = 0.5 + 0.5 * sin(acTime * speed * 6.2831853);
  c += amount * pulse * 0.4;

  gl_FragColor = vec4(clamp(c, 0.0, 1.0) * a, a);   // re-premultiply
}
Loops

Loop bounds must be constant. for (int i = 0; i < 8; i++) compiles everywhere; for (int i = 0; i < count; i++) with a slider as the bound fails on many GPUs. Use a fixed bound and multiply the result by the slider.

Values the app hands you

These exist automatically in every shader. You never declare them. They all start with ac, which is how you tell engine values apart from your own parameters.

NameTypeWhat it is
acScreenNormvec2This pixel's position, 0..1. The one you'll use most.
acScreenSizevec2Render target size in pixels. Use for aspect ratio.
acLayerNormvec2Position in the layer's own space.
acLayerSizevec2Layer size in pixels.
acLayerToScreenmat4Layer space to screen space.
acScreenToLayermat4The inverse.
acTimefloatSeconds. Feed it into a sin() to animate anything.
acPassintWhich render pass is running. Only matters for multi-pass.
acLayerCentervec2Layer centre in pixels.
acLayerCenterNormvec2Layer centre, normalised.
acLayerPivotvec2The layer's anchor point.
acPreviewSizevec2Preview surface size.
acVelocityvec2Layer motion — great for velocity-driven blur.
acScaleVelocityfloatRate of scale change.
acAngularVelocityfloatRate of rotation.
acShowGuidesboolTrue while the user is dragging a control.
Aspect ratio, the correct way

float ar = acScreenSize.x / max(acScreenSize.y, 1.0); then work in (uv - 0.5) * vec2(ar, 1.0). This is what keeps circles round on a tall phone.


06 — Working practice

Four things that save you an evening

Check the thumb attribute every single time

Before you import anything you wrote, look at the header and confirm thumb="…" is there. It costs two seconds and it is the cause of more "my effect has no picture" messages than everything else combined. Every file in the pack has it — use one as your starting point.

Keep 5–10 reference effects open while you work

Don't write GLSL into a blank page. Keep a handful of effects you like open in another window and copy their structure — how they declare controls, how they guard the alpha divide, how they lay out a loop. Strong effects are almost always a known-good skeleton with new maths in the middle. The 77-effect pack exists for exactly this: pick the one closest to your idea and change it.

If it isn't good, fix it again — don't ship it

Nobody gets a nice effect on the first pass. The loop is: edit → import → look at it on a real clip → change one number → import again. Two dozen passes on one effect is normal. If the result is muddy, the fix is usually a narrower slider range or a clamp() you forgot, not a rewrite.

Back out of Custom and re-enter after every import

The grid is built when you enter the page. Leave the Custom category and come back in, and the app rebuilds it — new effect counted, thumbnail loaded, everything in place. If an import seems to have done nothing, this is the first thing to try.


07 — Troubleshooting

When it fails

Find the symptom you're seeing. Each one has a single cause.

“That XML is not a valid Gold Motion effect”

The app's parser rejected the file. In order of likelihood: a missing or mismatched closing tag, a raw & or < inside an attribute value, a missing <?xml … ?> first line, or a quote left open. Open the file in a browser — if it shows an XML error, that's your answer.

It imports, but the tile has no picture

The header is missing thumb="…". The app had nothing to write the image path into. Add the attribute, re-import, and pick the thumbnail again.

“<name>” is already in your Custom effects.

You've imported an effect with the same name before. The app checks duplicates by name, not by id. Either choose replace, or change the name in the header before importing.

The effect is in the list but does nothing

The shader didn't compile. Nine times out of ten a variable is used in the shader that isn't declared in <params>, or the spelling differs between the two. Check every name matches exactly.

The sliders move but nothing changes

You declared the parameter but never used its id in the shader. The slider is wired to a variable nothing reads.

The labels are blank

You copied a stock effect and kept label="@am:string/…". Those strings live inside the app and don't resolve in your file. Write literal text: label="Amount".

Edges look dark, or transparent areas look wrong

Premultiplied alpha. Un-premultiply with src.rgb / max(src.a, 1e-4) on the way in and output vec4(rgb * a, a) on the way out.

It looks stretched on a tall phone

Aspect ratio. Work in (uv - 0.5) * vec2(acScreenSize.x / acScreenSize.y, 1.0) and convert back before sampling.

It flickers, or the preview crawls

Too many texture reads. A 24-tap loop is 24 reads per pixel per frame. Cut the tap count, or declare a buffer texture with downsample="2" and work at half resolution.

It worked yesterday and now it's gone

Custom effects live in the app's private storage. Clearing the app's cache or data removes them, as does reinstalling. Keep your original .xml files somewhere safe — they are the master copies, and re-importing takes seconds.


08 — AI prompt

Generate a custom effect without guessing the format

This prompt is based on the schema used by all 77 XML effects in the pack: one content texture, grouped controls, a single fragment shader and the same premultiplied output convention. It gives the model the exact contract instead of asking it to invent Gold Motion attributes.

How to use it

Copy the full block, add your effect description after EFFECT REQUEST:, and ask for one XML file only. If the result fails to render, send the error back with “compare the shader with a working effect from the pack and repair it”.

copy the whole block
ROLE
You are a Gold Motion custom-effect developer. Produce a production-ready,
single-file XML effect that follows the exact conventions of the 77-effect
Gold Motion pack. Do not explain the file, add markdown, or output anything
besides the XML.

REQUIRED OUTPUT
Return exactly ONE complete file in this order:
<?xml version='1.0' encoding='UTF-8' ?>
<effect id="gm.custom.SLUG" name="NAME" desc="ONE SENTENCE"
        category="custom"
        thumb="content://com.goldmotion.pnkj_com.alightcreative.app.motion.fileprovider/share/gmcustom/SLUG.png"
        tags="gm,custom,TAGS" experimental="false">
  <params>
    <texture id="inputImg" srcType="content" />
    ...grouped controls...
  </params>
  <shader type="fragment" precision="high"><![CDATA[
  ...complete GLSL ES 1.00...
  ]]></shader>
</effect>

HEADER RULES
1. Use lowercase id="gm.custom.SLUG". SLUG may contain letters, numbers and
   underscores only; keep it short and unique.
2. Use category="custom" and experimental="false".
3. Include the thumb attribute exactly as shown. Gold Motion replaces its
   value with the file URI for the image selected during import.
4. desc is one short sentence that explains the visual result, not the code.
5. tags is comma-separated and starts with gm,custom.

PARAMS RULES
1. Every file starts with <texture id="inputImg" srcType="content" />.
2. Use only these controls, because they match the pack:
   <spinner id="x" default="0.5" min="0.0" max="1.0" step="0.01" label="Amount" />
   Optional spinner types: type="percent", type="angle", type="integer".
   <color id="tint" default="#FFFFC24B" label="Tint" alpha="true" />
   <switch id="enabled" default="true" label="Enabled" />
   <selector id="mode" style="dropdown" default="0" label="Mode">
     <choice value="0" label="First mode" />
     <choice value="1" label="Second mode" />
   </selector>
   <section text="Effect name" />
3. Give the effect 3 to 6 useful controls with realistic defaults and ranges.
   Put its main controls first, then an Output group containing
   <spinner id="mixAmt" default="1.0" min="0.0" max="1.0" step="0.01"
           label="Effect Strength" />.
4. End params with
   <section text="GOLD MOTION - CUSTOM FX" />.
5. Use literal labels. Never use "@am:string/..." or "@string/...".
6. Every shader variable must have a matching params id with exactly the
   same spelling. Do not declare ac* engine values.

SHADER RULES
1. Use one fragment shader in GLSL ES 1.00. No #version, precision statement,
   passes, multiple main functions, or external files.
2. Read the layer with:
   vec2 uv = acScreenNorm;
   vec4 src = texture2DCv(inputImg.texture, uv);
3. The sample is premultiplied. For normal colour processing use:
   float a = max(src.a, 1e-4);
   vec3 c = src.rgb / a;
   ...edit c...
   gl_FragColor = vec4(clamp(c, 0.0, 1.0) * src.a, src.a);
4. Use texture2DCv(), never texture() or a guessed sampler function.
5. Every helper you call — including gmLum() — must be declared before
   main(). Define gmLum as
   float gmLum(vec3 c){ return dot(c, vec3(0.2126, 0.7152, 0.0722)); }
   unless you use your own luma function. Loop bounds must be compile-time
   constants. Keep loops short enough for phones; prefer 8-16 taps.
6. For circular coordinates use
   vec2 p = (uv - 0.5) * vec2(acScreenSize.x / acScreenSize.y, 1.0);
7. Animated effects use acTime multiplied by a speed control. Static effects
   must still be clearly visible at the default settings.
8. Multiply the final look by mixAmt. The default must show a useful,
   obvious result rather than an empty or extremely subtle one.

QUALITY CHECK BEFORE ANSWERING
- The XML opens and closes correctly.
- No tags, attributes, ids or labels were invented outside this contract.
- Every control is used and every used variable is declared.
- The shader has no compile-time names that are undefined.
- The output has valid alpha and cannot produce transparent black edges.
- The file is suitable for a phone: modest texture sampling and no heavy
  nested loops.

EFFECT REQUEST
Replace this line with the effect to create:
[Describe the visual result, intended use, animation, and controls here.]

Follow-ups that work

Say thisTo fix
“It renders black.”“Compare the shader with a working pack effect. Check every helper such as gmLum() is defined before main(), the texture read uses texture2DCv, every params id matches, and the output re-multiplies the colour by alpha.”
“The tile is blank.”“Restore the full thumb="content://…/gmcustom/SLUG.png" attribute in the effect header before import.”
“Edges look wrong.”“The input is premultiplied. Un-premultiply with src.rgb / max(src.a, 1e-4) before colour maths and re-premultiply on output.”
“Make it move.”“Drive it with acTime and add a speed control that multiplies it.”
“Too slow on my phone.”“Cut the texture samples — replace the 24-tap loop with 8 taps and scale the result.”

09 — Resources

77 effects, ready to import

Nine categories, every header correct, every shader commented. Import them as they are, or open one and use it as the skeleton for your own. Nothing here is obfuscated.

The strongest Pro example from every category appears first.

Download all 77 ZIP · 77 XML effects + install guide · 79 KB

10 — FAQ

Questions people actually ask

Do custom effects need internet or an account?

No. The file is parsed and stored on your device. There's no server, no key and no login.

Which Gold Motion version do I need?

V7.0.2 or newer. The Custom category and its + button don't exist in V6.x — that's the whole reason this feature is new.

Can one file hold several effects?

No. One file is one effect. Five effects means five files, imported one at a time.

Do I need to ship a thumbnail image with my effect?

Not in the file. You pick the thumbnail from your own gallery during import, and the app copies it into its own storage. The only requirement is that thumb="…" exists in the header so the app has somewhere to put the path.

Where do my imported effects actually live?

In the app's private custom_effects/ folder, each with a small .meta.json beside it. They're not in a shared folder you can browse to — which is why you should keep your original XML files safe.

Will my effects survive an app update?

Updating normally keeps app data, so yes. Clearing the app's data or uninstalling removes them. Re-importing from your saved XML files takes seconds.

Can a custom effect read the whole composition?

Yes — declare <texture id="comp" srcType="comp" />. Reading an arbitrary other layer isn't part of the custom schema.

Why does my effect look stretched on a tall phone?

Aspect ratio. Use acScreenSize.x / acScreenSize.y and work in normalised space, as shown in the shader section.

Can I sell effects I make with this?

The files you write are yours. The 77-effect pack here is free to use and learn from — if you pass it on, keep the files intact so nobody gets a broken import.